Merge remote-tracking branch 'origin/dev'

This commit is contained in:
plightfield 2024-11-22 19:21:21 +08:00
commit 3790a332c5
68 changed files with 1765 additions and 585 deletions

View File

@ -32,9 +32,11 @@
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"gsap": "^3.12.5",
"js-base64": "^3.7.7",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"lunar-typescript": "^1.7.5",
"milkdown-plugin-placeholder": "^0.2.1",
"oh-vue-icons": "^1.0.0-rc3",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.3",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -19,6 +19,7 @@ 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'
import ModalPgae from './layout/grid/ModalPgae'
const Grid = asyncLoader(() => import('./layout/grid'))
const Fox = asyncLoader(() => import('./fox'))
const settings = useSettingsStore()
@ -30,53 +31,54 @@ const router = useRouterStore()
const layout = useLayoutStore()
</script>
<template>
<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 />
<!-- <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 />
</div>
<DirModal />
<GlobalMenu />
<WelcomePage></WelcomePage>
<TomatoPage></TomatoPage>
<BackupRecovery v-if="router.path === 'global-backup'"></BackupRecovery>
<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 && settings.state.showPet) ||
(layout.state.simple && settings.state.simpleModeShowString.includes('showPet'))
"
>
<Fox />
</div>
</ConfigProvider>
<DirModal />
<GlobalMenu />
<WelcomePage></WelcomePage>
<TomatoPage></TomatoPage>
<BackupRecovery v-if="router.path === 'global-backup'"></BackupRecovery>
</div>
<ModalPgae />
<!-- </ConfigProvider> -->
</template>
<style lang="less">

View File

@ -66,6 +66,10 @@ const Item = defineComponent({
noStyle: {
type: Boolean,
default: false
},
centered: {
type: Boolean,
default: false
}
},
emits: {
@ -75,12 +79,13 @@ const Item = defineComponent({
return () => (
<div
class={clsx(
'px-4 py-2 text-sm tracking-widest w-full overflow-hidden text-ellipsis whitespace-nowrap break-all transition-all rounded-lg cursor-pointer mb-2',
' py-1 text-sm tracking-widest w-full overflow-hidden flex text-ellipsis whitespace-nowrap break-all transition-all rounded cursor-pointer mb-[2px]',
{
'bg-red-500/80 hover:bg-red-500 text-white': props.alert,
'bg-white/80 hover:bg-white text-black/80': !props.alert && !props.noStyle,
' hover:bg-black/20 text-black/80': props.alert,
' hover:bg-black/20 text-black/80': !props.alert && !props.noStyle,
'bg-transparent text-black/60 hover:text-black/80 hover:bg-white/20': props.noStyle
}
},
props.centered ? 'justify-center' : 'pl-2 justify-start'
)}
style={
props.noStyle
@ -114,7 +119,7 @@ export default defineComponent(() => {
v-outside-click={() => {
menu.dismiss()
}}
class="fixed px-2 pt-4 pb-2 bg-white/60 backdrop-blur shadow-lg rounded-lg overflow-hidden"
class="fixed px-2 pt-2 pb-1 bg-white/60 backdrop-blur shadow-lg rounded-lg overflow-hidden"
style={{
zIndex: '999',
left: menu.display.x + 'px',
@ -216,6 +221,7 @@ export default defineComponent(() => {
return (
<>
<Item
centered
onClick={() => {
menu.showEditPage = true
menu.dismiss()
@ -223,26 +229,32 @@ export default defineComponent(() => {
>
</Item>
<Item
alert
onClick={() => {
// 删除链接
console.log(menu.selectPage)
{layout.state.content[layout.state.current].pages.length > 1 && (
<Item
alert
centered
onClick={() => {
// 删除链接
const idx = layout.state.content[layout.state.current].pages.findIndex(
(el) => el.id === menu.selectPage?.id
)
menu.dismiss()
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)
}}
>
</Item>
if (idx < 0) return
layout.state.content[layout.state.current].pages.splice(idx, 1)
if (
layout.state.currentPage >=
layout.state.content[layout.state.current].pages.length
) {
layout.state.currentPage =
layout.state.content[layout.state.current].pages.length - 1
}
}}
>
</Item>
)}
</>
)
}
@ -347,7 +359,7 @@ export default defineComponent(() => {
text: block.text,
background: block.background,
color: block.color,
type: block.color ? 1 : 0
type: !block.icon ? 1 : 0
}
router.go('global-adder')
useAdderPageStore().type = 2

View File

@ -1,6 +1,7 @@
import {
computed,
defineComponent,
onMounted,
onUnmounted,
provide,
ref,
@ -24,6 +25,7 @@ import HotAdder from './HotAdder'
import GameAdder from './GameAdder'
import work_add_main_checked from '/icons/work_add_main_checked.png'
import useAdderPageStore from './useAdderPageStore'
import search from '../header/search'
addIcons(MdKeyboardcommandkey, FaCompass, FaPencilRuler, IoGameController)
const ItemButton = defineComponent({
@ -83,9 +85,11 @@ export default defineComponent(() => {
const addTo = ref(layout.state.currentPage)
provide(AddToToken, addTo)
// onUnmounted(() => {
// store.type = 1
// })
onUnmounted(() => {
store.isSearch = false
})
return () => (
<div
class={clsx(

View File

@ -270,7 +270,6 @@ export default defineComponent(() => {
return
}
if (store.editBlockItem !== null) {
console.log(123)
layout.changeBlock(form, store.editBlockItem.id)
useRouterStore().back()
store.editBlockItem = null
@ -292,6 +291,7 @@ export default defineComponent(() => {
label: form.name
}
layout.addBlock(data, addTo?.value)
globalToast.success('添加成功')
}
}}
>

View File

@ -5,6 +5,7 @@ import useLayoutStore from '../useLayoutStore'
import { AddToToken } from './AdderPage'
import { v4 as uuid } from 'uuid'
import useAdderPageStore, { type HotAppType } from './useAdderPageStore'
import { globalToast } from '@/main'
export const LinkItem = defineComponent({
props: {
@ -83,8 +84,8 @@ export const LinkItem = defineComponent({
}
)}
onClick={() => {
layout.addBlock(
{
if (layout.openDir) {
layout.state.dir[layout.openDir].list.push({
id: uuid(),
link: props.content.url,
name: props.content.name,
@ -95,11 +96,27 @@ export const LinkItem = defineComponent({
color: '',
w: 1,
h: 1
},
addTo?.value
)
if (addTo?.value) {
layout.state.currentPage = addTo?.value
})
globalToast.success('添加成功')
} else {
layout.addBlock(
{
id: uuid(),
link: props.content.url,
name: props.content.name,
label: props.content.name,
icon: props.content.icon,
text: '',
background: '',
color: '',
w: 1,
h: 1
},
addTo?.value
)
if (addTo?.value) {
layout.state.currentPage = addTo?.value
}
}
}}
>

View File

@ -36,7 +36,7 @@ export default defineStore('adderPage', () => {
const isSearch = ref(false)
const widgetSearchWords = ref('')
const gameSearch = ref('')
const editBlockItem = ref<EditBlockItemType | null >(null)
const editBlockItem = ref<EditBlockItemType | null>(null)
request<HotAppCategoryType[]>('GET', '/api/app/hotAppTypes').then((res) => {
categoryList.value = res.map((el) => ({
id: el.id,
@ -50,6 +50,12 @@ export default defineStore('adderPage', () => {
watch(selectType, (val) => {
getApps(val)
})
watch(isSearch, () => {
if (!isSearch.value) {
getApps(selectType.value)
}
})
const getApps = (_selectType: string) => {
loading.value = true
request<HotAppType[]>('GET', `/api/app/hotApps?hotAppsId=${_selectType}`)
@ -64,9 +70,10 @@ export default defineStore('adderPage', () => {
}
const search = (keywords: string) => {
if (keywords === '') {
if (!keywords) {
if (isSearch.value) getApps(selectType.value)
isSearch.value = false
getApps(selectType.value)
return
}
isSearch.value = true

View File

@ -1,4 +1,4 @@
import { Button, 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,26 +23,33 @@ 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
noBg
v-slots={{
label: () => <div></div>
}}
>
<Select
class="w-[100px]"
options={[
{ label: '游戏', value: 0 },
{ label: '工作', value: 1 },
{ label: '休闲', value: 2 }
]}
v-model:value={selected.value}
/>
</SettingItem> */}
<div class="px-4">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<div class="h-[180px]">
{layout.background.video && layout.background.type !== 'own' ? (
<video class="w-full h-full" src={layout.background.video} autoplay={false} controls />
<video
class="w-full h-full"
src={layout.background.video}
loop
autoplay={false}
controls
/>
) : (
<div
class="w-full h-full bg-center bg-no-repeat bg-cover"

View File

@ -166,6 +166,7 @@ export default defineComponent(() => {
<div
onClick={() => {
layout.changeBackground(item.url)
background.state.isCustom = false
}}
class="h-[156px] relative cursor-pointer group w-full flex-grow-0 rounded-xl overflow-hidden"
>

View File

@ -1,17 +1,20 @@
import upload, { localPrefix, uploadLocal } from '@/utils/upload'
import clsx from 'clsx'
import { defineComponent, ref, toRef } from 'vue'
import { computed, defineComponent, ref, toRef } from 'vue'
import useBackgroundStore from './useBackgroundStore'
import useResource from './useResource'
import useLayoutStore from '../useLayoutStore'
import { message } from 'ant-design-vue'
import { BgContent } from '.'
export default defineComponent(() => {
const dragging = ref(false)
const fileInput = ref<HTMLInputElement | null>(null)
const backgroundStore = useBackgroundStore()
const tempFile = ref<File | null>(null)
const layout = useLayoutStore()
const tempBackground = ref('')
const type = ref('img')
const handleDrop = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
@ -20,7 +23,28 @@ export default defineComponent(() => {
if (!file) return
tempBackground.value = URL.createObjectURL(file)
tempFile.value = file
if (file.type.includes('image')) {
type.value = 'img'
} else {
type.value = 'video'
}
}
const showImg = computed(() => {
if (tempBackground.value) {
return tempBackground.value
}
if (backgroundStore.state.isCustom) {
if (layout.background.video) {
type.value = 'video'
return layout.background.video
} else {
return layout.background.image
}
}
return ''
})
return () => (
<div class={'w-full h-full flex flex-col items-center pt-5 pb-10 gap-y-4'}>
<div class="flex flex-col text-center gap-y-1">
@ -47,16 +71,28 @@ export default defineComponent(() => {
dragging.value ? 'border-[#3f80ff] bg-[#6b9dff1a]' : 'border-transparent bg-[#ebebeb]'
)}
style={
tempBackground.value
? {
backgroundImage: `url(${tempBackground.value})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'
}
: {}
}
// style={
// tempBackground.value
// ? {
// backgroundImage: `url(${tempBackground.value})`,
// backgroundSize: 'cover',
// backgroundRepeat: 'no-repeat'
// }
// : backgroundStore.state.isCustom
// ? {
// backgroundImage: `url(${backgroundStore.tag})`,
// backgroundSize: 'cover',
// backgroundRepeat: 'no-repeat'
// }
// : null
// }
>
<div class={'absolute top-0 left-0 w-full h-full'}>
<BgContent
image={showImg.value}
video={type.value === 'video' ? showImg.value : undefined}
></BgContent>
</div>
<div class={'w-full h-full absolute left-0 top-0 bottom-0 right-0 group'}>
<div
class={clsx(
@ -64,7 +100,7 @@ export default defineComponent(() => {
tempBackground.value ? 'bg-black/50 hidden group-hover:flex' : 'flex'
)}
>
<img alt="123" src={new URL('/tab/icons/uploadImg.png', import.meta.url).href}></img>
<img alt="123" src={'/tab/icons/uploadImg.png'}></img>
<span
class={clsx('text-[15px] ', tempBackground.value ? 'text-[#fff]' : 'text-[#333]')}
>
@ -75,12 +111,18 @@ export default defineComponent(() => {
</span>
<input
type="file"
accept="image/jpeg,image/png,image/svg + xml,image/gif,video/mp4"
ref={fileInput}
hidden
onChange={(e: any) => {
const file = e.target?.files?.[0]
if (!file) return
tempFile.value = file
if (file.type.includes('image')) {
type.value = 'img'
} else {
type.value = 'video'
}
tempBackground.value = URL.createObjectURL(file)
}}
/>
@ -96,9 +138,8 @@ export default defineComponent(() => {
if (tempFile.value) {
uploadLocal(tempFile.value).then((res) => {
useLayoutStore().changeBackground(res)
backgroundStore.state.isCustom = true
tempFile.value = null
tempBackground.value = ''
message.success('应用成功')
})
}

View File

@ -2,7 +2,7 @@ import { defineComponent, nextTick, reactive, ref, Transition, watch } from 'vue
import useLayoutStore from '../useLayoutStore'
import useSettingsStore from '@/settings/useSettingsStore'
const BgContent = defineComponent({
export const BgContent = defineComponent({
props: {
image: {
type: String,
@ -29,12 +29,14 @@ const BgContent = defineComponent({
}}
>
{props.video ? (
<video src={props.video} class="w-full h-full" muted />
<video autoplay src={props.video} loop class="w-full h-full object-cover" muted />
) : (
<div
class="w-full h-full bg-center bg-cover bg-no-repeat"
style={{
backgroundImage: `url('${props.image}')`
backgroundImage: `url('${props.image}')`,
backgroundSize: 'cover',
backgroundPosition: 'center center',
}}
></div>
)}
@ -83,9 +85,9 @@ export default defineComponent({
}
}
)
watch(pair, console.log)
return () => (
<div class="absolute left-0 top-0 w-full h-screen z-0">
<Transition name={animate.value ? 'background' : ''}>
{pair.front ? (
<div class="absolute left-0 top-0 w-full h-full">

View File

@ -3,6 +3,9 @@ import { reactive, ref, watch } from 'vue'
export default defineStore('background', () => {
const tag = ref(localStorage.getItem('backgroundTag') || '')
const state = reactive({
isCustom: false,
})
const resource = reactive({
type: 'image',
brief: '',
@ -29,6 +32,9 @@ export default defineStore('background', () => {
)
return {
tag,
resource
resource,
state
}
}, {
persist: true
})

View File

@ -27,10 +27,12 @@ export default function useResource(tag: Ref<string>, type: string) {
}
return
}
if (val.startsWith('http')) {
// 内源
if (val.startsWith(ossCdnBase)) {
// 地址后缀
const suffix = val.split('.').pop()
if (!suffix) {
return
@ -38,6 +40,7 @@ export default function useResource(tag: Ref<string>, type: string) {
if (videoArr.includes(suffix)) {
// 内部视频
// 先显示截图,再去数据库看是否有存货
resource.video = ''
resource.image = val + '?x-oss-process=video/snapshot,t_0,f_jpg,m_fast'
resource.brief = val + '?x-oss-process=video/snapshot,t_0,f_jpg,w_400,h_225,m_fast'
@ -45,10 +48,12 @@ export default function useResource(tag: Ref<string>, type: string) {
if (!res) return
const item = res.find((item) => item.tag === val)
if (item) {
resource.video = URL.createObjectURL(item.file)
resource.type = 'local'
} else {
// 不存在,需要存入
fetch(val)
.then((res) => res.blob())
.then((blob) => {
@ -61,6 +66,7 @@ export default function useResource(tag: Ref<string>, type: string) {
})
}
})
} else {
// 图片
resource.image = val
@ -91,7 +97,10 @@ export default function useResource(tag: Ref<string>, type: string) {
if (!res) return
const item = res.find((item) => item.tag === val)
if (!item) return
const url = URL.createObjectURL(item.file)
resource.image = item.type === 'image' ? url : ''
resource.video = item.type === 'video' ? url : ''
resource.brief = url

View File

@ -5,7 +5,9 @@ import LinkBlock from '../grid/LinkBlock'
import useSettingsStore from '@/settings/useSettingsStore'
import clsx from 'clsx'
import { useMenuStore } from '../GlobalMenu'
import { HiSolidLockOpen, HiSolidLockClosed } from 'oh-vue-icons/icons'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
addIcons(HiSolidLockClosed, HiSolidLockOpen)
export default defineComponent(() => {
const layout = useLayoutStore()
const setting = useSettingsStore()
@ -28,12 +30,16 @@ export default defineComponent(() => {
onMouseleave={() => {
show.value = false
}}
class={'w-3/5 min-w-[800px] h-[120px] fixed left-1/2 bottom-0 -translate-x-1/2'}
class={'w-3/5 min-w-[800px] group h-[120px] fixed left-1/2 bottom-0 -translate-x-1/2'}
>
<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'
'fixed bottom-6 left-1/2 duration-150 -translate-x-1/2 px-5 p-4 rounded-xl bg-white/60 backdrop-blur flex gap-5 shadow-lg',
setting.state.showDock === 'auto'
? show.value
? 'bottom-4'
: 'bottom-[-90px]'
: 'bottom-4'
)}
ref={container}
onMouseleave={() => {
@ -45,6 +51,21 @@ export default defineComponent(() => {
menu.open('dock')
}}
>
<div
onClick={()=> {
setting.state.showDock = setting.state.showDock === 'auto'? 'show' : 'auto'
}}
class={clsx(
'p-1 group-hover:block hidden rounded-lg cursor-pointer hover:bg-black/[.22] absolute bottom-0 -right-8'
)}
>
<OhVueIcon
name={
setting.state.showDock === 'auto' ? HiSolidLockClosed.name : HiSolidLockOpen.name
}
fill="white"
></OhVueIcon>
</div>
{layout.state.dockLabels.split('').map((l, i) => {
const block = layout.state.dock[i]
return (
@ -54,8 +75,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 || ''}
@ -90,8 +111,8 @@ export default defineComponent(() => {
{block && <LinkBlock block={block} dock />}
{!setting.state.disabledShortcut && (
<div
class="absolute z-10 left-0 bottom-0 w-full bg-black/20 text-[12px] text-white text-center"
style={{ boxShadow: block ? 'none' : '0 -4px 14px 0 rgba(0,0,0,.3)' }}
class="absolute z-10 left-0 bottom-0 w-full bg-black/20 text-[12px] backdrop-blur-md text-white text-center"
style={{ boxShadow: block ? 'none' : '0 -4px 14px 0 rgba(0,0,0,.4) ' }}
>
{l}
</div>

View File

@ -0,0 +1,156 @@
import SettingItem from '@/settings/SettingItem'
import useSettingsStore from '@/settings/useSettingsStore'
import { Radio, Switch } from 'ant-design-vue'
import clsx from 'clsx'
import { defineComponent, watch } from 'vue'
import useLayoutStore from '../useLayoutStore'
import { sendParent } from '@/utils/parent'
export default defineComponent({
setup() {
const settings = useSettingsStore()
const layout = useLayoutStore()
watch(
() => ({
autoSearch: settings.state.autoUseAi === 'show',
showTabButton: settings.state.showPetOnTab,
isSearch: settings.state.autoUseAi === ''
}),
(val) => {
sendParent([
'configAI',
{
autoSearch: val.autoSearch,
showTabButton: val.showTabButton,
isSearch: val.isSearch
}
])
},
{
immediate: true
}
)
return () => (
<div class="p-4 flex flex-col ">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
AI助手
</span>
<span class={'text-[13px] text-[#666] '}>AI助手样式</span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<span
class={clsx(
'text-[14px] font-bold my-2',
useLayoutStore().state.current === 0 ? 'text-white' : 'text-[#333]'
)}
>
</span>
<SettingItem
v-slots={{
label: () => <div></div>
}}
noRoundedB
desc="fatfox标签页内的小助手"
>
<Switch
checked={settings.state.showPet}
onUpdate:checked={(e) => {
if (e) settings.state.showPet = true
else settings.state.showPet = false
}}
/>
</SettingItem>
<SettingItem
v-slots={{
label: () => <div></div>
}}
desc="fatfox标签页内的小助手"
noRoundedT
>
<Switch
checked={settings.state.showPetOnTab}
onUpdate:checked={(e) => {
if (e) settings.state.showPetOnTab = true
else settings.state.showPetOnTab = false
}}
/>
</SettingItem>
<span
class={clsx(
'text-[14px] font-bold my-2',
useLayoutStore().state.current === 0 ? 'text-white' : 'text-[#333]'
)}
>
</span>
<SettingItem
v-slots={{
label: () => <div></div>
}}
desc="使用搜索引擎时Fatfox会给您更多有效的结果"
>
<Switch
checked={settings.state.autoUseAi !== ''}
onUpdate:checked={(e) => {
if (e) settings.state.autoUseAi = 'show'
else settings.state.autoUseAi = ''
}}
/>
</SettingItem>
<div
class={clsx('flex w-full py-2 px-3 rounded-lg flex-col', {
'bg-black/5': useLayoutStore().state.current !== 0,
'bg-white/10': useLayoutStore().state.current === 0
})}
>
<span>使</span>
<SettingItem
v-slots={{
label: () => <div></div>
}}
noBg
desc="每次搜索直接询问fatfox"
>
<Radio
checked={settings.state.autoUseAi === 'show'}
onClick={() => {
settings.state.autoUseAi = 'show'
}}
onUpdate:checked={(e) => {
if (e) settings.state.showSider = 'auto'
else settings.state.showSider = 'show'
}}
/>
</SettingItem>
<SettingItem
v-slots={{
label: () => <div></div>
}}
noBg
desc="每次搜索,提示您进行手动查询"
>
<Radio
checked={settings.state.autoUseAi === 'auto'}
onClick={() => {
settings.state.autoUseAi = 'auto'
}}
/>
</SettingItem>
</div>
</div>
)
}
})

View File

@ -2,11 +2,30 @@ import SettingItem from '@/settings/SettingItem'
import useSettingsStore from '@/settings/useSettingsStore'
import { Slider, Switch } from 'ant-design-vue'
import { defineComponent } from 'vue'
import useLayoutStore from '../useLayoutStore'
import clsx from 'clsx'
export default defineComponent(() => {
const settings = useSettingsStore()
return () => (
<div class="p-4">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<SettingItem
v-slots={{
label: () => <div></div>

View File

@ -39,8 +39,11 @@ export default defineComponent({
style={{
gridColumn: `span ${props.block.w}`,
gridRow: `span ${props.block.h}`,
transition: 'border .3s, transform .2s',
border: hover.value ? '2px solid rgba(255,255,255,.5)' : '2px solid rgba(255,255,255,0)'
transition: 'border .3s, transform .2s'
// border: hover.value ? '2px solid rgba(255,255,255,.5)' : '2px solid rgba(255,255,255,0)'
}}
onDblclick={e=> {
e.stopPropagation()
}}
data-transportable={props.block.link && !props.block.link.startsWith('id:') ? '1' : ''}
onDragover={(e) => {
@ -69,7 +72,7 @@ export default defineComponent({
// 小组件无法合并
if (!link) return
//游戏不能合并
if (props.block.w !== 1) return
if (props.block.w === 2 && props.block.h === 1) return
const oldIdx = layout.currentPage.list.findIndex((el) => el.id === dragging.id)
if (oldIdx < 0) return
const oldBlock = layout.currentPage.list[oldIdx]
@ -79,7 +82,9 @@ export default defineComponent({
if (!oldBlock || oldBlock.link.startsWith('id:')) return
if (link.startsWith('id:')) {
// 本身就是文件夹
const id = link.slice(3)
const dir = layout.state.dir[id]
if (!dir) return
dir.list.push(toRaw(oldBlock))
@ -88,6 +93,7 @@ export default defineComponent({
} else {
// 本身不是文件夹
const id = props.block.id
layout.state.dir[id] = {
label: props.block.label,
list: [toRaw(props.block), toRaw(oldBlock)]
@ -163,7 +169,7 @@ export default defineComponent({
{props.block.link ? (
props.block.link.startsWith('id:') ? (
// 文件夹
<DirBlock block={props.block} big={props.block.w !== 1 || props.block.h !== 1} />
<DirBlock block={props.block} big={props.block.w !== 1 || props.block.h !== 1} />
) : (
// 链接
<LinkBlock block={props.block} />

View File

@ -32,8 +32,9 @@ export default defineComponent({
const menu = useMenuStore()
return () => (
<div
onClick={() => {
onClick={(e) => {
layout.openDir = props.block.link.slice(3)
e.stopPropagation()
}}
class={clsx('w-full h-full bg-white/60 backdrop-blur grid', {
'grid-rows-3 grid-cols-3 gap-[6%] p-[8%]': props.big,
@ -46,8 +47,11 @@ export default defineComponent({
{selectedDir.value.list
.filter((_, idx) => idx < (props.big ? 9 : 4))
.map((el) => (
<div class="w-full h-full rounded-lg overflow-hidden">
<LinkBlock block={el} brief />
<div
class="w-full h-full rounded-lg overflow-hidden"
>
<LinkBlock block={el} brief disable={true} />
</div>
))}
</div>

View File

@ -3,9 +3,17 @@ import useLayoutStore from '../useLayoutStore'
import useSortable from './useSortable'
import LinkBlock from './LinkBlock'
import type { Block } from '../layout.types'
import useRouterStore from '@/useRouterStore'
import { globalToast } from '@/main'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { MdAdd } from 'oh-vue-icons/icons'
import useAdderPageStore from '../adder/useAdderPageStore'
addIcons(MdAdd)
export default defineComponent(() => {
const layout = useLayoutStore()
const router = useRouterStore()
const addStore = useAdderPageStore()
const dir = ref({ label: '', list: [] as Block[] })
// 因为有拖拽,关闭弹框不能使内容消失
watch(
@ -18,16 +26,18 @@ export default defineComponent(() => {
computed(() => dir.value.list),
computed(() => layout.openDir)
)
return () => (
<Transition>
<div
class="fixed w-full h-screen left-0 top-0 flex flex-col justify-center items-center"
v-show={layout.openDir}
style="z-index: 300"
style="z-index: 20"
>
<div
class="absolute left-0 top-0 w-full h-full bg-black/40 backdrop-blur"
onClick={() => {
onClick={(e) => {
e.stopPropagation()
layout.openDir = ''
}}
onDragenter={() => {
@ -63,14 +73,33 @@ export default defineComponent(() => {
style={{
borderRadius: `calc(var(--block-radius) * var(--block-size))`
}}
onClick={(e) => {
e.stopPropagation()
}}
>
<LinkBlock block={block} key={block.id} />
<LinkBlock
block={block}
key={block.id}
/>
</div>
<div class="absolute left-0 -bottom-2 text-sm text-black/60 text-center w-full overflow-hidden text-ellipsis whitespace-nowrap break-all">
{block.label}
</div>
</div>
))}
<div class="w-full h-full flex justify-center items-center p-[var(--block-padding)] relative operation-button">
<div
class="w-full h-full overflow-hidden rounded-[calc(var(--block-radius)_*_var(--block-size))] bg-white/60 backdrop-blur flex justify-center items-center cursor-pointer hover:scale-105 transition-all"
onClick={() => {
router.go('global-adder')
}}
>
<span style="filter:drop-shadow(0 0 10px hsl(32, 90%, 65%))">
<OhVueIcon name="md-add" fill="white" scale={2.4} />
</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -33,6 +33,23 @@ export default defineComponent({
})
return () => (
<div class="p-4 flex flex-col gap-y-1 ">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<SettingItem
v-slots={{
label: () => <div></div>

View File

@ -7,6 +7,7 @@ import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { message, Modal, Table } from 'ant-design-vue'
import request from '@/utils/request'
import db from '@/db'
import clsx from 'clsx'
addIcons(MdInbox)
export interface Feedback {
@ -40,22 +41,32 @@ export default defineComponent(() => {
get: () => data.images[2],
set: (val) => (data.images[2] = val)
})
const list = [img1, img2, img3]
return () => (
<div class={'p-4 ' + (isGame.value ? 'text-white' : 'text-black/60')}>
<div class="flex justify-between items-center">
<div>
<span class="text-red-500">*</span>
<div class="flex justify-between items-start">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
</div>
<div
class="cursor-pointer"
class="cursor-pointer text-[14px]"
onClick={async () => {
let his = await db.getItem<Feedback[]>('feedbacks')
if (!his) his = []
const ins = Modal.info({
const ins = Modal.warn({
title: '历史反馈',
width: '1024px',
maskClosable: true,
okText: '关闭',
content: () => (
<Table
class="max-h-[50vh] overflow-y-auto"
@ -70,7 +81,9 @@ export default defineComponent(() => {
{
title: '问题描述',
customRender: ({ record }) => (
<div class="w-[200px] break-all">{record.description}</div>
<div class="w-[200px] break-all overflow-hidden text-ellipsis whitespace-nowrap" title={record.description}>
{record.description}
</div>
)
},
{
@ -95,7 +108,6 @@ export default defineComponent(() => {
></Table>
),
onOk() {
console.log('OK')
ins.destroy()
},
onCancel() {
@ -111,19 +123,46 @@ export default defineComponent(() => {
</div>
</div>
<div class={'mt-2 ' + (isGame.value ? 'bg-white/10 text-white' : 'bg-black/5 text-black/60')}>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
<div>
<span class="text-red-500 ">*</span>
</div>
<div
class={
'mt-2 relative appearance-none rounded-lg h-[124px] ' +
(isGame.value ? 'bg-white/10 text-white' : 'bg-black/5 text-black/60')
}
>
<textarea
placeholder="在此输入您的问题,并可以添加图片,方便更清晰的描述问题,我们会尽快帮您解决,感谢您的反馈"
maxlength={80}
v-model={data.description}
class="bg-transparent no-textarea w-full p-2"
class="bg-transparent no-textarea w-full p-2 appearance-none resize-none placeholder:text-[14px] "
style={{
color: isGame.value ? 'white' : 'black'
}}
/>
<div class="p-2 flex gap-2">
<ImageUploader width={42} v-model:value={img1.value} />
<ImageUploader width={42} v-model:value={img2.value} />
<ImageUploader width={42} v-model:value={img3.value} />
{list
.filter(
(_, idx) =>
idx <=
list.reduce((acc, cur) => {
if (cur.value) acc++
return acc
}, 0)
)
.map((item) => (
<ImageUploader width={38} v-model:value={item.value} />
))}
</div>
<span class={'absolute right-2 bottom-2'}>{data.description.length}/80</span>
</div>
<div class="mt-4"></div>
<div class="text-sm">便</div>
@ -131,9 +170,11 @@ export default defineComponent(() => {
<input
v-model={data.qq}
placeholder="QQ号"
maxlength={15}
class={'p-2 rounded-lg ' + (isGame.value ? 'bg-white/10' : 'bg-black/5')}
/>
<input
maxlength={12}
v-model={data.phone}
placeholder="手机号"
class={'p-2 rounded-lg ' + (isGame.value ? 'bg-white/10' : 'bg-black/5')}
@ -151,13 +192,16 @@ export default defineComponent(() => {
await request('POST', '/api/feedback', {
data: {
content: data.description,
qq: data.qq,
email: data.qq,
phone: data.phone,
imgs: data.images
},
returnType: 'text'
})
message.success('提交成功')
list.forEach((item) => {
item.value = ''
})
Object.assign(data, { ...defaultData })
}}
class="mt-4 text-center rounded py-1 cursor-pointer bg-[#ffa93d] text-white"

View File

@ -16,6 +16,10 @@ export default defineComponent({
dock: {
type: Boolean,
default: false
},
disable: {
type: Boolean,
default: false
}
},
setup(props) {
@ -25,19 +29,22 @@ export default defineComponent({
<div
class="w-full h-full flex justify-center items-center font-bold bg-cover bg-center bg-no-repeat"
onContextmenu={() => {
if (props.disable) return
menu.open(props.block)
}}
onClick={() => {
if (props.disable) return
jump(props.block.link)
}}
style={{
backgroundColor: props.block.text ? props.block.background || 'white' : 'transparent',
backgroundColor: !props.block.icon ? 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)'
}}
>
<div>{props.brief ? props.block.text[0] : props.block.text}</div>
{!props.block.icon && <div>{props.brief ? props.block.text[0] : props.block.text}</div>}
</div>
)
} else {

View File

@ -0,0 +1,10 @@
import useUserStore from '@/user/useUserStore'
import useRouterStore from '@/useRouterStore'
import { defineComponent } from 'vue'
import HeaderSetting from '../header/headerSetting'
export default defineComponent(() => {
const router = useRouterStore()
const user = useUserStore()
return () => <>{router.path === 'custom-header' ? <HeaderSetting></HeaderSetting> : null}</>
})

View File

@ -3,6 +3,9 @@ import { Button } from 'ant-design-vue'
import clsx from 'clsx'
import { computed, defineComponent, ref } from 'vue'
import useLayoutStore from '../useLayoutStore'
import useUserStore from '@/user/useUserStore'
import useTomatoStore from '@/widgets/work/useTomatoStore'
import useNotepadStore from '@/widgets/notepad/useNotepadStore'
export default defineComponent({
setup() {
@ -13,51 +16,101 @@ export default defineComponent({
})
return () => (
<div class="p-4 flex flex-col ">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<SettingItem
v-slots={{
label: () => <div ></div>
label: () => <div></div>
}}
>
<Button type="primary" onClick={() => {
open.value = true
}}></Button>
<Button
type="primary"
onClick={() => {
open.value = true
}}
>
</Button>
</SettingItem>
<p class={'text-[#666] text-[12px]'}></p>
{
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"}>
{open.value && (
<div
class={clsx(
'w-[300px] h-[210px] absolute top-0 rounded-2xl right-[-310px] z-10 ',
isGame.value ? 'bg-[#2c2e3e]' : 'bg-white'
)}
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]' : 'text-[#666]'
)}
>
</span>
<div class={'flex justify-between w-full'}>
<button
onClick={() => {
layout.resetAll()
useUserStore().logout()
useTomatoStore().reset()
useNotepadStore().reset()
open.value = false
}}
class={clsx("w-[118px] rounded-lg py-1 flex justify-center", isGame.value ? "bg-white/20" : "")}></button>
class={clsx(
'w-[118px] rounded-lg py-1 flex justify-center',
isGame.value ? 'bg-white/20' : 'bg-black/20 text-white'
)}
>
</button>
<button
onClick={() => {
open.value = false
}}
class={clsx("w-[118px] rounded-lg py-1 flex justify-center", isGame.value ? "bg-[#ff7372]" : "")}></button>
class={clsx(
'w-[118px] rounded-lg py-1 flex justify-center ',
isGame.value ? 'bg-[#ff7372]' : 'bg-[#ff7372] text-white'
)}
>
</button>
</div>
</div>
</div>
}
</div >
)}
</div>
)
}
})

View File

@ -3,6 +3,8 @@ import useSettingsStore from '@/settings/useSettingsStore'
import { Select, Slider, Switch, type SelectProps } from 'ant-design-vue'
import { defineComponent, ref } from 'vue'
import useSearchConfigStore from '../header/search/useSearchConfigStore'
import clsx from 'clsx'
import useLayoutStore from '../useLayoutStore'
export default defineComponent({
setup() {
@ -16,6 +18,23 @@ export default defineComponent({
)
return () => (
<div class="p-4">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<SettingItem
noRoundedB
v-slots={{

View File

@ -11,6 +11,23 @@ export default defineComponent({
const layout = useLayoutStore()
return () => (
<div class="p-4 flex flex-col gap-y-4 ">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<div
class={'p-5 bg-[#000000]/[.05] rounded-lg w-full grid grid-rows-1 grid-cols-2 gap-x-3'}
>

View File

@ -4,6 +4,7 @@ import { Select, Slider, Switch, type SelectProps } from 'ant-design-vue'
import { defineComponent, ref } from 'vue'
import useSearchConfigStore from '../header/search/useSearchConfigStore'
import clsx from 'clsx'
import useLayoutStore from '../useLayoutStore'
const list: {
label: string
value: TimeUnit
@ -41,6 +42,23 @@ export default defineComponent({
)
return () => (
<div class="p-4">
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<span class={'text-[13px] text-[#666] '}></span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<SettingItem
v-slots={{
label: () => <div></div>

View File

@ -1,33 +1,39 @@
import { computed, defineComponent, onMounted, ref, Transition } from 'vue'
import { computed, defineComponent, onMounted, onUnmounted, ref, Transition } from 'vue'
import WelcomeImg from '~/icons/welcome/welcomeTitle.png'
import DivBgImg from '~/icons/welcome/back.png'
import startUseImg from '~/icons/welcome/startUse.png'
import LeftImg from '~/icons/welcome/welcomeLeft.png'
import RightImg from '~/icons/welcome/welcomeRight.png'
import useLayoutStore from '../useLayoutStore'
import request from '@/utils/request'
import useStatisticStore from '@/utils/useStatisticStore'
import { v4 as uuid } from 'uuid'
import { uploadLocal } from '@/utils/upload'
import { videoArr } from '@/config'
import clsx from 'clsx'
export const DefaultPageSetting = [
{
name: '游戏',
backgroundUrl:
'https://newfatfox.oss-cn-beijing.aliyuncs.com/000/user_upload/1/resource/831a4d4c-61ff-4bae-ad31-9c0f4fa2db1c.webp',
'https://oss.goosetab.com/000/user_upload/1/resource/831a4d4c-61ff-4bae-ad31-9c0f4fa2db1c.webp',
contentUrl:
'https://newfatfox.oss-cn-beijing.aliyuncs.com/000/user_upload/1/resource/b2f3ed2f-f550-499b-8ea1-dfd192cfd388.webp',
'https://oss.goosetab.com/000/user_upload/1/resource/b2f3ed2f-f550-499b-8ea1-dfd192cfd388.webp',
desct: '聚合多类游戏工具,以及 资讯、排行榜等'
},
{
name: '工作',
name: '工作',
backgroundUrl:
'https://newfatfox.oss-cn-beijing.aliyuncs.com/000/user_upload/1/resource/b82fd47c-24c1-4f58-b0db-51414b3bdda4.webp',
'https://oss.goosetab.com/000/user_upload/1/resource/b82fd47c-24c1-4f58-b0db-51414b3bdda4.webp',
contentUrl:
'https://newfatfox.oss-cn-beijing.aliyuncs.com/000/user_upload/1/resource/7e4cf74a-85cb-4e39-9e61-385b222ac8c4.webp',
desct: '结合番茄计时法等效率工具,让您可以高效学'
'https://oss.goosetab.com/000/user_upload/1/resource/7e4cf74a-85cb-4e39-9e61-385b222ac8c4.webp',
desct: '结合番茄计时法等效率工具,让您可以高效学'
},
{
name: '轻娱',
backgroundUrl:
'https://newfatfox.oss-cn-beijing.aliyuncs.com/000/user_upload/1/resource/73164094-cb0d-4366-8d1a-afc84ac119cc.webp',
'https://oss.goosetab.com/000/user_upload/1/resource/73164094-cb0d-4366-8d1a-afc84ac119cc.webp',
contentUrl:
'https://newfatfox.oss-cn-beijing.aliyuncs.com/000/user_upload/1/resource/bcbbffc6-c8a4-4c8e-8ba5-36fa1fbad4f9.webp',
'https://oss.goosetab.com/000/user_upload/1/resource/bcbbffc6-c8a4-4c8e-8ba5-36fa1fbad4f9.webp',
desct: '综合办公、学习、游戏等 属性,功能较为均勺'
}
]
@ -37,6 +43,25 @@ export default defineComponent(() => {
const layout = useLayoutStore()
const isFirst = ref(false)
// 创建一个响应式变量来存储屏幕宽度
const width = ref(window.innerWidth)
const updateWidth = () => {
width.value = window.innerWidth
}
onMounted(() => {
// 监听窗口大小变化
window.addEventListener('resize', updateWidth)
})
onUnmounted(() => {
// 组件卸载时移除事件监听器
window.removeEventListener('resize', updateWidth)
})
const isBig = computed(() => {
return width.value > 1700
})
onMounted(() => {
// 检查 localStorage 是否已经有访问记录
const visited = localStorage.getItem('hasVisited')
@ -47,7 +72,7 @@ export default defineComponent(() => {
useStatisticStore().send({
widget: 'WELCOME',
action: 'OPEN',
space: 'TAB',
space: 'TAB'
})
}
})
@ -57,8 +82,8 @@ export default defineComponent(() => {
<Transition>
{show.value && (
<div
class="w-full h-screen bg-black/60 "
onClick={() => { }}
class="w-full h-screen bg-white "
onClick={() => {}}
style={{
backgroundImage: `url('${DefaultPageSetting[selectMode.value].backgroundUrl}')`,
backgroundSize: '100% 100%',
@ -66,7 +91,7 @@ export default defineComponent(() => {
backgroundRepeat: 'no-repeat'
}}
>
<div class="w-full h-screen bg-black/60 " onClick={() => { }}></div>
<div class="w-full h-screen bg-black/60 " onClick={() => {}}></div>
</div>
)}
</Transition>
@ -83,24 +108,39 @@ export default defineComponent(() => {
>
<span class={'leading-[20px] -mt-2 text-[20px] text-white '}></span>
</div>
<div class={'w-full h-[370px] flex justify-center'}>
<div
class={'w-full flex justify-center'}
style={
isBig.value
? {
width: 844 + 'px',
height: 433 + 'px'
}
: {
width: 709 + 'px',
height: 370 + 'px'
}
}
>
<div class={'w-[868px] h-[370px] '}>
<div class={'relative w-full h-full flex justify-center'}>
{DefaultPageSetting.map((item, idx) => (
<div
class={
' w-[709px] h-[370px] cursor-pointer duration-150 absolute top-0 left-0 bg-[#2c2e3e] p-3 overflow-hidden rounded-2xl'
' cursor-pointer duration-150 absolute top-0 left-0 bg-[#2c2e3e] p-3 rounded-2xl'
}
onClick={() => {
selectMode.value = idx
}}
style={{
transform: `translate(${idx === selectMode.value ? 80 : (((selectMode.value === 2 && idx === 0) || (selectMode.value + 1 === idx)) ? 219 : -52)}px) scale(${idx === selectMode.value ? 1 : 0.85
})`,
transform: `translate(${idx === selectMode.value ? (isBig.value ? 0 : 0) : (selectMode.value === 2 && idx === 0) || selectMode.value + 1 === idx ? (isBig.value ? 460 : 152) : isBig.value ? -382 : -152}px) scale(${
idx === selectMode.value ? 1 : 0.85
})`,
backgroundImage: `url('${DivBgImg}')`,
backgroundSize: '100% 100%',
zIndex: selectMode.value === idx ? 10 : 0
zIndex: selectMode.value === idx ? 10 : 0,
width: (isBig.value ? 844 : 709) + 'px',
height: (isBig.value ? 433 : 370) + 'px'
}}
>
<div
@ -112,6 +152,17 @@ export default defineComponent(() => {
backgroundRepeat: 'no-repeat'
}}
></div>
{selectMode.value !== idx ? (
(selectMode.value === 2 && idx === 0) || selectMode.value + 1 === idx ? (
<div class={'right-[-100px] absolute top-1/2 -translate-y-1/2'}>
<img src={RightImg} class={'w-[40px]'} alt="" />
</div>
) : (
<div class={'left-[-100px] absolute top-1/2 -translate-y-1/2'}>
<img src={LeftImg} class={'w-[40px]'} alt="" />
</div>
)
) : null}
</div>
))}
</div>
@ -122,9 +173,10 @@ export default defineComponent(() => {
<span class={'w-[180px]'}>{DefaultPageSetting[selectMode.value].desct}</span>
</div>
<div
class={
'w-[175px] h-[41px] mt-4 flex items-center justify-center text-[#333] cursor-pointer'
}
class={clsx(
' mt-4 flex items-center justify-center text-[#333] cursor-pointer',
isBig.value ? 'w-[300px] h-[66px] text-[22px]' : 'w-[175px] h-[41px] text-[16px]'
)}
onClick={() => {
localStorage.setItem('hasVisited', 'true')
isFirst.value = false
@ -135,13 +187,32 @@ export default defineComponent(() => {
if (!res) return
layout.state.dir = res.dir
layout.state.content = res.content
layout.state.content.forEach(async (val) => {
const res = await fetch(val.background).then((res) => res.blob())
if (res) {
const affix = val.background.split('.').pop()
if (!affix) return
const file = new File([res], `${uuid()}.${affix}`, {
type: `${res.type}`
})
uploadLocal(file).then((res2) => {
val.background = res2
})
}
})
})
layout.state.current = selectMode.value as 0 | 1 | 2
useStatisticStore().send({
widget: selectMode.value === 0 ? "WELCOME_GAME" : selectMode.value === 1 ? "WELCOME_WORK" : "WELCOME_RELAX",
widget:
selectMode.value === 0
? 'WELCOME_GAME'
: selectMode.value === 1
? 'WELCOME_WORK'
: 'WELCOME_RELAX',
action: 'CLICK',
space: 'TAB',
space: 'TAB'
})
}}
style={{

View File

@ -51,6 +51,10 @@ export default defineComponent(() => {
layout.isCompact = false
}
}}
onDblclick={(e) => {
e.stopPropagation()
layout.state.simple = !layout.state.simple
}}
onDragover={(e) => e.preventDefault()}
onDrop={() => {
// 处理移入
@ -69,6 +73,7 @@ export default defineComponent(() => {
const idx = list.findIndex((el) => el.id === dragging.id)
if (idx < 0) return
const block = list[idx]
layout.currentPage.list.push(toRaw(block))
list.splice(idx, 1)
layout.checkDir(dragging.type)

View File

@ -32,7 +32,7 @@ export default function useSortable(list: Ref<any[]>, type: Ref<string>) {
filter: '.operation-button'
}
: {}),
ghostClass: 'opacity-20',
ghostClass: 'opacity-0',
onStart: (e: any) => {
dragging.type = type.value
dragging.id = e.item.id || ''

View File

@ -3,6 +3,7 @@ import useTimeStore from '@/utils/useTimeStore'
import { Lunar } from 'lunar-typescript'
import { computed, defineComponent, Transition } from 'vue'
import useLayoutStore from '../useLayoutStore'
import clsx from 'clsx'
export default defineComponent({
setup() {
@ -39,78 +40,102 @@ export default defineComponent({
const layout = useLayoutStore()
return () => (
<div
class="absolute z-20 shadow-text tracking-widest font-normal h-[110px] transition-all "
class={clsx(
'absolute z-20 shadow-text tracking-widest font-normal transition-all ',
layout.isCompact && !layout.state.simple
? 'text-[13px] flex z-20 items-center pointer-events-none gap-x-2'
: 'h-[110px]'
)}
onDblclick={(e) => {
e.stopPropagation()
layout.state.simple = !layout.state.simple
}}
style={{
color: 'white',
transitionDuration: '.4s',
left: layout.isCompact ? '20px' : '50%',
top: layout.isCompact ? '20px' : layout.state.simple ? '100px' : '50px',
transform: layout.isCompact ? '' : 'translate(-50%,0)'
left: layout.isCompact && !layout.state.simple ? '20px' : '50%',
top:
layout.isCompact && !layout.state.simple
? '20px'
: layout.state.simple
? '100px'
: '50px',
transform: layout.isCompact && !layout.state.simple ? '' : 'translate(-50%,0)'
}}
>
<Transition>
{
layout.state.simple ?
settings.state.simpleModeShowString.includes('showTime') &&
<div
class={
'transition-all ' +
(layout.isCompact ? 'text-[1.4rem] leading-[1.4rem]' : 'text-[8vh] leading-[4rem]')
}
>
{text.value.timeStr}
</div>
:
settings.state!.showTime &&
<div
class={
'transition-all ' +
(layout.isCompact ? 'text-[1.4rem] leading-[1.4rem]' : 'text-[8vh] leading-[4rem]')
}
>
{text.value.timeStr}
</div>
}
{layout.state.simple
? settings.state.simpleModeShowString.includes('showTime') && (
<div
class={
'transition-all ' +
(layout.isCompact && !layout.state.simple
? 'text-[1.2rem] leading-[1.4rem]'
: 'text-[8vh] leading-[4rem]')
}
>
{text.value.timeStr}
</div>
)
: settings.state!.showTime && (
<div
class={
'transition-all ' +
(layout.isCompact && !layout.state.simple
? 'text-[1.2rem] leading-[1.4rem]'
: 'text-[8vh] leading-[4rem]')
}
>
{text.value.timeStr}
</div>
)}
</Transition>
<Transition>
{
layout.state.simple ?
settings.state.simpleModeShowString.includes('showDate') &&
<div
class={'flex items-center gap-4 mt-4 ' + (layout.isCompact ? '' : 'justify-center')}
>
{settings.state.timeOptions.includes('date') && <div>{text.value.dateStr}</div>}
<Transition>
{!layout.isCompact && settings.state.timeOptions.includes('week') && (
<div>{info.value.dayWeek}</div>
)}
</Transition>
<Transition>
{!layout.isCompact && settings.state.timeOptions.includes('lunal') && (
<div>{info.value.day}</div>
)}
</Transition>
</div>
:
settings.state!.showTime &&
<div
class={'flex items-center gap-4 mt-4 ' + (layout.isCompact ? '' : 'justify-center')}
>
{settings.state.timeOptions.includes('date') && <div>{text.value.dateStr}</div>}
<Transition>
{!layout.isCompact && settings.state.timeOptions.includes('week') && (
<div>{info.value.dayWeek}</div>
)}
</Transition>
<Transition>
{!layout.isCompact && settings.state.timeOptions.includes('lunal') && (
<div>{info.value.day}</div>
)}
</Transition>
</div>
}
{layout.state.simple
? settings.state.simpleModeShowString.includes('showDate') && (
<div
class={
'flex items-center gap-4 ' +
(layout.isCompact && !layout.state.simple
? 'text-[1.2rem]'
: 'justify-center mt-4')
}
>
{settings.state.timeOptions.includes('date') && <div>{text.value.dateStr}</div>}
<Transition>
{!(layout.isCompact && !layout.state.simple) &&
settings.state.timeOptions.includes('week') && (
<div>{info.value.dayWeek}</div>
)}
</Transition>
<Transition>
{!(layout.isCompact && !layout.state.simple) &&
settings.state.timeOptions.includes('lunal') && <div>{info.value.day}</div>}
</Transition>
</div>
)
: settings.state!.showTime && (
<div
class={
'flex items-center gap-4 ' +
(layout.isCompact && !layout.state.simple
? ' text-[1.2rem]'
: 'mt-4 justify-center')
}
>
{settings.state.timeOptions.includes('date') && <div>{text.value.dateStr}</div>}
<Transition>
{!layout.isCompact && settings.state.timeOptions.includes('week') && (
<div>{info.value.dayWeek}</div>
)}
</Transition>
<Transition>
{!layout.isCompact && settings.state.timeOptions.includes('lunal') && (
<div>{info.value.day}</div>
)}
</Transition>
</div>
)}
</Transition>
</div>
)

View File

@ -0,0 +1,130 @@
import useLayoutStore from '@/layout/useLayoutStore'
import useUserStore from '@/user/useUserStore'
import useRouterStore from '@/useRouterStore'
import clsx from 'clsx'
import { computed, defineComponent, ref, Transition } from 'vue'
import { MdArrowbackiosnew, HiSolidPlus } from 'oh-vue-icons/icons'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { ossBase } from '@/config'
import UploadAndCut from '@/utils/UploadAndCut'
addIcons(MdArrowbackiosnew, HiSolidPlus)
export default defineComponent(() => {
const router = useRouterStore()
const { profile } = useUserStore()
const layout = useLayoutStore()
const isGame = computed(() => {
return layout.state.current === 0
})
const list = Array.from({ length: 10 }).map((_, index) => `${ossBase}/admin/${index + 1}.png`)
const selectUrl = ref(profile.avatar)
const customHeader = ref(profile.avatar)
return () => (
<div class="fixed left-0 bottom-0 z-40 w-full rounded-lg">
{/* 背景遮罩 */}
<div
class="w-full h-screen"
onClick={() => {
router.replace('')
}}
></div>
{/* 弹框主体 */}
<Transition name="settings">
<div
class={clsx(
'absolute left-6 bottom-10 w-[300px] 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={'w-full h-full flex flex-col px-5 py-1 '}>
<div class={'relative flex justify-center w-full font-bold text-[14px] py-2'}>
<div
class={'left-2 top-1/2 absolute -translate-y-1/2 cursor-pointer'}
onClick={() => {
router.back()
}}
>
<OhVueIcon name={MdArrowbackiosnew.name} fill="#666"></OhVueIcon>
</div>
</div>
<div class={'h-[1px] w-full bg-[#d5d5d5] mt-1 mb-3'}></div>
<div class={'w-full grid grid-cols-3 gap-3'}>
{list.map((item) => {
return (
<div
key={item}
class={' cursor-pointer'}
onClick={() => {
selectUrl.value = item
}}
>
<img
src={item}
class={clsx(
'w-full h-full rounded-full border-[2px] ',
selectUrl.value === item
? 'border-[2px] border-red-500'
: 'border-[2px] border-transparent'
)}
/>
</div>
)
})}
{!list.includes(customHeader.value) && customHeader.value && (
<div
class={' cursor-pointer'}
onClick={() => {
selectUrl.value = customHeader.value
}}
>
<img
src={customHeader.value}
class={clsx(
'w-full h-full rounded-full border-[2px] ',
selectUrl.value === customHeader.value
? 'border-[2px] border-red-500'
: 'border-[2px] border-transparent'
)}
alt="header"
/>
</div>
)}
<div
class={'flex items-center w-[78px] h-[78px] justify-center bg-[#E5E5E5] rounded-full cursor-pointer'}
>
<UploadAndCut
onUpdate:value={(e) => {
customHeader.value = e
selectUrl.value = e
}}
></UploadAndCut>
</div>
</div>
<div class={'flex justify-center w-full'}>
<button
onClick={() => {
profile.avatar = selectUrl.value
useUserStore().updateProfile()
router.back()
}}
class={
'px-14 mt-14 py-1 bg-[#ff7372] text-white text-[14px] rounded-lg hover:opacity-80'
}
>
</button>
</div>
</div>
</div>
</Transition>
</div>
)
})

View File

@ -30,7 +30,7 @@ export default defineComponent({
'w-full rounded-lg overflow-hidden flex justify-center items-center p-2 hover:scale-110 transition-all cursor-pointer ' +
(searchConfig.current.name === item.name
? 'bg-white'
: 'bg-white/40 hover:bg-white/60')
: 'bg-white ')
}
onClick={() => {
searchConfig.current = { ...item }
@ -45,9 +45,10 @@ export default defineComponent({
}}
>
<div
class="w-6 h-6 bg-center bg-no-repeat bg-contain"
class="w-[28px] h-[28px] bg-center bg-no-repeat bg-contain rounded-[8px]"
style={{
backgroundImage: `url('${item.icon}')`
backgroundImage: `url('${item.icon}')`,
backgroundSize: '100% 100%'
}}
/>
</div>
@ -56,7 +57,7 @@ export default defineComponent({
</div>
</div>
))}
<div class="w-12 h-16">
{/* <div class="w-12 h-16">
<div
class="w-full h-10 rounded-lg overflow-hidden flex justify-center items-center p-2 transition-all cursor-pointer bg-white/40 hover:bg-white/60"
onClick={() => {
@ -66,7 +67,7 @@ export default defineComponent({
>
<OhVueIcon name="fa-plus" scale={1.4} fill="rgba(0,0,0,.4)" />
</div>
</div>
</div> */}
</div>
)
}

View File

@ -17,20 +17,20 @@ export default defineComponent(
return () => (
<div
class={clsx(
!props?.isMini ? 'absolute left-1/2 -translate-x-1/2 z-20 transition-all' : 'w-full'
!props?.isMini ? 'absolute left-1/2 -translate-x-1/2 z-20 duration-300 transition-all' : 'w-full'
)}
style={
props.isMini
? {}
: {
top: layout.isCompact ? '40px' : layout.state.simple ? '230px' : '172px',
top: layout.isCompact && !layout.state.simple ? '50px' : 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 ',
'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 hover:bg-white',
props.isMini ? '' : 'max-w-[90vw] w-full'
)}

View File

@ -6,6 +6,7 @@ import debounce from 'lodash/debounce'
import { aIUrl, translateUrl } from '@/config'
import request from '@/utils/request'
import useStatisticStore from '@/utils/useStatisticStore'
import { Base64 } from "js-base64"
export type SearchAdType = {
name: string
icon: string
@ -39,15 +40,12 @@ export default defineStore('search', () => {
searchStr,
(val) => {
if (!val) return
fetch(
`${import.meta.env.PROD ? 'https://suggestion.baidu.com/su' : '/baiduSuggestion'}?wd=${val}&ie=utf-8&p=3&cb=j`
)
.then((res) => res.text())
.then((res: string) => {
const list = res.match(/(?<=s:\[").*(?=\]\})/g)?.[0]?.split('","')
if (list) {
sugList.value = list
}
request<{
list: string[]
}>('GET', `/api/lookUp/${Base64.encode(val)}`,)
.then(res => {
// const list = res.match(/(?<=s:\[").*(?=\]\})/g)?.[0]?.split('","')
sugList.value = res.list
}) as Promise<string[]>
},
{
@ -55,10 +53,8 @@ export default defineStore('search', () => {
}
)
const debouncedHandler = debounce((newValue) => {
console.log('数值改变并已防抖处理:', newValue)
request<SearchAdType[]>("GET", `/api/app/searchBars?keyword=${newValue || 'undefine'}`).then((res) => {
addList.value = res
console.log(addList.value);
})
}, 500) //
@ -82,7 +78,7 @@ export default defineStore('search', () => {
searchConfig.addHistory(str)
searchStr.value = ''
jump(searchConfig.current.url + str)
useStatisticStore().send({
widget: 'search',
action: 'search',

View File

@ -17,7 +17,6 @@ export default defineComponent(() => {
(val) => {
selected.value = val
hover.value = false
},
{ immediate: true }
)
@ -66,7 +65,8 @@ export default defineComponent(() => {
<div
class={clsx(
'absolute top-[18px] text-white text-[13px] z-20 flex flex-col items-center pointer-events-none',
settings.state.siderDirection === 'right' ? 'right-[105px]' : 'left-[105px] '
settings.state.siderDirection === 'right' ? 'right-[105px]' : 'left-[105px] ',
selected.value === 0 || layout.state.current === 0 ? ' opacity-100' : ' opacity-70'
)}
>
<OhVueIcon name="md-videogameasset-twotone" fill="white" scale={1.3} />
@ -75,7 +75,8 @@ export default defineComponent(() => {
<div
class={clsx(
'absolute top-[80px] text-white text-[13px] z-20 flex flex-col items-center pointer-events-none',
settings.state.siderDirection === 'right' ? 'right-[150px] ' : 'left-[150px] '
settings.state.siderDirection === 'right' ? 'right-[150px] ' : 'left-[150px] ',
selected.value === 1 || layout.state.current === 1 ? ' opacity-100' : ' opacity-70'
)}
>
<OhVueIcon name="md-workhistory-twotone" fill="white" scale={1.3} />
@ -86,11 +87,12 @@ export default defineComponent(() => {
'absolute text-white text-[13px] flex flex-col z-20 items-center pointer-events-none',
settings.state.siderDirection === 'right'
? 'right-[105px] top-[140px]'
: 'left-[105px] top-[140px]'
: 'left-[105px] top-[140px]',
selected.value === 2 || layout.state.current === 2 ? ' opacity-100' : ' opacity-70'
)}
>
<OhVueIcon name="md-stars-twotone" fill="white" scale={1.3} />
</div>
<svg
width="200"
@ -116,7 +118,12 @@ export default defineComponent(() => {
/>
</g>
{/* 判定区块,无颜色 */}
<g class="relative z-10">
<g
class="relative z-10"
onClick={() => {
layout.state.current = selected.value
}}
>
<path
fill="transparent"
d="M155.29,94.09c-7.97-10.81-20.68-17.24-34.1-17.24-4.48,0-8.91,.71-13.17,2.1L85.85,11.74c11.31-3.68,23.12-5.54,35.1-5.54,35.71,0,69.72,17.1,91.07,45.78l-56.72,42.12Z"
@ -156,14 +163,17 @@ export default defineComponent(() => {
<path
class={
'absolute z-30 transition-all opacity-60 ' +
(selected.value === 0
(layout.state.current === 0
? '-rotate-[72deg]'
: selected.value === 1
: layout.state.current === 1
? 'rotate-0'
: 'rotate-[72deg]')
}
onClick={() => {
layout.state.current = selected.value
onMouseenter={() => {
selected.value = layout.state.current
}}
onClick={()=> {
hover.value = false
}}
style="transform-origin: 50% 50%;"
fill="url(#mode-switch-selected)"

View File

@ -107,7 +107,7 @@ export default defineComponent(() => {
<Transition>
{layout.ready && (
<div
onContextmenu={(e)=> {
onContextmenu={(e) => {
e.stopPropagation()
e.preventDefault()
}}
@ -174,6 +174,7 @@ export default defineComponent(() => {
label="添加"
onClick={() => {
menu.showEditPage = true
menu.selectPage = undefined
}}
/>
<Item
@ -256,6 +257,7 @@ export default defineComponent(() => {
name: selected.value.name,
id: uuid()
})
layout.state.currentPage = layout.currentMode.pages.length - 1
}
menu.showEditPage = false

View File

@ -77,7 +77,6 @@ export default defineStore('layout', () => {
}
const pageList = state.content[state.current].pages[page].list
pageList.push(block)
globalToast.success('添加成功')
}
const changeBlock = (item: EditBlockItemType, target: string) => {
const idx = currentPage.value.list.findIndex((el) => el.id === target)
@ -133,7 +132,7 @@ export default defineStore('layout', () => {
if (!res) return
state.dir = res.dir
state.content = res.content
state.dock = res.dock
}).catch(() => {
Object.assign(state, defaultLayout)

View File

@ -265,17 +265,36 @@ body {
.background-enter-active,
.background-leave-active {
transform: scale(1);
opacity: 1;
transition:
transform 0.6s cubic-bezier(0.47, 1.64, 0.41, 0.8),
opacity 0.6s ease-out;
transition: transform 0.6s cubic-bezier(0.47, 1.64, 0.41, 0.8);
}
.background-enter-from {
transform: scale(1.25);
opacity: 0;
transform: scale(1.2);
}
.background-leave-to {
transform: scale(1);
opacity: 0;
}
.eat-enter-active,
.eat-leave-active {
transition: transform 0.25s ease-in;
}
.eat-enter-from {
transform: translateY(0);
}
.eat-leave-to {
transform: translateY(100%);
}
.neweat-enter-active,
.neweat-leave-active {
transition: transform 0.25s ease-out ;
}
.neweat-enter-from {
transform: translateY(0);
}
.neweat-leave-to {
transform: translateY(107%);
}

View File

@ -0,0 +1,47 @@
import useLayoutStore from '@/layout/useLayoutStore'
import useUserStore from '@/user/useUserStore'
import useRouterStore from '@/useRouterStore'
import clsx from 'clsx'
import { computed, defineComponent, Transition } from 'vue'
export default defineComponent(() => {
const router = useRouterStore()
const { profile } = useUserStore()
const show = computed(() => router.path.startsWith('settings-header'))
const layout = useLayoutStore()
const isGame = computed(() => {
return layout.state.current === 0
})
return () => (
<div class="fixed left-0 bottom-0 z-40 w-full rounded-lg">
{/* 背景遮罩 */}
{show.value && (
<div
class="w-full h-screen"
onClick={() => {
router.replace('')
}}
></div>
)}
{/* 弹框主体 */}
<Transition name="settings">
{show.value && (
<div
class={clsx(
'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>
)}
</Transition>
</div>
)
})

View File

@ -15,6 +15,9 @@ export default defineComponent({
noBg: {
type: Boolean,
default: false
},
desc: {
type: String
}
},
slots: {} as SlotsType<{
@ -39,8 +42,17 @@ export default defineComponent({
marginBottom: props.noRoundedB ? 0 : '12px'
}}
>
<div class={clsx('text-sm mr-4', isGame.value ? 'text-white' : 'text-black/60')}>
{ctx.slots.label?.()}
<div
class={clsx('text-sm mr-4 flex flex-col', isGame.value ? 'text-white' : 'text-black/60')}
>
<span> {ctx.slots.label?.()}</span>
{props.desc ? (
<span
class={clsx('text-[12px] ', isGame.value ? 'text-white opacity-50 ' : 'text-black/60')}
>
{props.desc}
</span>
) : null}
</div>
<div class="">{ctx.slots.default?.()}</div>
{/* {ctx.slots.end?.()} */}

View File

@ -10,6 +10,7 @@ import SiderSetting from '@/layout/grid/SiderSetting'
import DockSetting from '@/layout/grid/DockSetting'
import Feedback from '@/layout/grid/Feedback'
import Reset from '@/layout/grid/Reset'
import AiSetting from '@/layout/grid/AiSetting'
export default defineComponent(() => {
const router = useRouterStore()
@ -35,6 +36,8 @@ export default defineComponent(() => {
<Reset />
) : router.path === 'settings-fallback' ? (
<Feedback />
) : router.path === 'settings-ai' ? (
<AiSetting />
) : null}
</ThemeProvider>
</div>

View File

@ -40,7 +40,9 @@ export default defineStore(
// 显示隐藏
showSider: 'show' as VisibleState,
showDock: 'show' as VisibleState,
showPet: 'show' as VisibleState,
showPet: true,
showPetOnTab: true,
autoUseAi: 'show' as VisibleState,
showTop: 'show' as VisibleState,
showTime: true,
timeOptions: ['date', 'week', '12hour', 'lunal', 'second'] as TimeUnit[],

View File

@ -13,8 +13,9 @@ export type SettingStr =
| 'dock'
| 'reset'
| 'fallback'
export type CustomStr = 'background'
export type RouteStr = '' | `widget-${string}` | `global-${GlobalStr}` | `settings-${SettingStr}`
| 'logout'
export type CustomStr = 'header'
export type RouteStr = '' | `widget-${string}` | `global-${GlobalStr}` | `settings-${SettingStr}` | `custom-${CustomStr}`
export default defineStore('router', () => {
const his = ref<RouteStr[]>([])

View File

@ -8,11 +8,7 @@ addIcons(FaUserAlt)
export default defineComponent(() => {
const store = useUserStore()
watch(() => store.profile.avatar, (e) => {
console.log(e);
console.log('avatar change')
})
return () => {
return (
<div

View File

@ -32,7 +32,6 @@ export default defineComponent(() => {
}).then((res) => {
if (res) {
clearInterval(it)
console.log(res)
user.token = res
router.back()
message.success('登录成功')

View File

@ -1,96 +1,192 @@
import useRouterStore from '@/useRouterStore'
import { Button, Modal, Tag } from 'ant-design-vue'
import { defineComponent } from 'vue'
import { Button, Select } from 'ant-design-vue'
import { computed, defineComponent, ref } from 'vue'
import AvatarCircle from './AvatarCircle'
import useUserStore from './useUserStore'
import { EditOutlined, LoginOutlined, LogoutOutlined } from '@ant-design/icons-vue'
import { globalToast } from '@/main'
import { LoginOutlined } from '@ant-design/icons-vue'
import { FaRegularEdit } from 'oh-vue-icons/icons'
import clsx from 'clsx'
import useLayoutStore from '@/layout/useLayoutStore'
const labelStyle = 'w-16'
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import background from '@/layout/background'
addIcons(FaRegularEdit)
export default defineComponent(() => {
const router = useRouterStore()
const layout = useLayoutStore()
const user = useUserStore()
const open = ref(false)
const isGame = computed(() => {
return layout.state.current === 0
})
return () => (
<div class="absolute left-0 top-0 w-full h-full p-4 flex flex-col">
<div class="flex justify-center py-4">
<div class="w-16 h-16 relative">
<AvatarCircle />
</div>
</div>
{user.isLogin && (
<div
class={clsx(
'h-0 flex-grow py-4 px-12 ',
layout.state.current === 0 ? 'text-white' : 'text-[#333]'
)}
>
<div class="flex py-2">
<div class={labelStyle}>:</div>
<div class="w-0 flex-grow overflow-hidden text-ellipsis whitespace-nowrap break-all">
{user.profile.username}
{user.isLogin ? (
<>
<div class={'flex flex-col'}>
<span
class={clsx(
'text-[14px] font-bold',
useLayoutStore().state.current === 0 ? 'text-white' : ' text-[#333]'
)}
>
</span>
<div
class={clsx(
'w-full h-[1px] bg-black/10 mt-1 mb-2',
useLayoutStore().state.current === 0 ? 'bg-white/10' : ' bg-black/10'
)}
></div>
</div>
<div class="flex pl-4 py-4 w-full gap-x-4">
<div class="w-16 h-16 rounded-full group overflow-hidden relative">
<AvatarCircle />
<div
onClick={() => {
router.go('custom-header')
}}
class={
'absolute hidden left-0 cursor-pointer top-0 opacity-60 rounded-full w-full h-full bg-black z-10 group-hover:flex items-center justify-center'
}
>
<OhVueIcon name={FaRegularEdit.name} fill="#ddd"></OhVueIcon>
</div>
</div>
<div class={'flex flex-col h-full justify-between text-[14px] py-1'}>
<div class={'flex gap-x-1 items-center'}>
<span class={clsx(' w-[50px]', isGame.value ? 'text-[#fff9]' : 'text-[#666]')}>
:
</span>
<input
onBlur={() => {
user.updateProfile()
}}
class={
'text-[#666] w-[150px] bg-transparent focus:bg-black/[0.05] border-none outline-none px-1 py-[2px]'
}
v-model={user.profile.username}
></input>
</div>
<div class={'flex gap-x-1 items-center'}>
<span class={clsx(' w-[50px]', isGame.value ? 'text-[#fff9]' : 'text-[#666]')}>
&ensp;&ensp;:
</span>
<Select
defaultValue={user.profile.gender}
v-model={user.profile.gender}
class={
'w-[150px] bg-transparent appearance-none focus:bg-black/[0.05] overflow-hidden border-none outline-none px-1 py-[2px]'
}
style={{
background: 'transparent !important'
}}
onChange={(e: any) => {
user.profile.gender = e
user.updateProfile()
}}
options={[
{
label: '男',
value: 1
},
{
label: '女',
value: 2
},
{
label: '未知',
value: 0
}
]}
></Select>
</div>
</div>
</div>
<div class="flex py-2">
<div class={labelStyle}>:</div> {user.profile.birthday}
<div
onClick={() => {
open.value = true
}}
class={
'w-full h-[32px] mt-5 cursor-pointer hover:opacity-90 flex items-center justify-center text-[13px] rounded bg-[#e5e5e5] text-[#999]'
}
>
退
</div>
<div class="flex py-2">
<div class={labelStyle}>:</div>
<Tag color={user.profile.gender === 1 ? 'blue' : user.profile.gender === 2 ? 'red' : 'gray'}>
{user.profile.gender === 1 ? '男' : user.profile.gender === 2 ? '女' : '未知'}
</Tag>
</>
) : (
<Button
type="primary"
icon={<LoginOutlined />}
size="large"
onClick={() => {
router.go('global-login')
}}
>
</Button>
)}
{open.value && (
<div
class={clsx(
'w-[300px] h-[210px] absolute top-0 rounded-2xl right-[-310px] z-10 ',
isGame.value ? 'bg-[#2c2e3e]' : 'bg-white'
)}
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]' : 'text-[#666]'
)}
>
退
</span>
<div class={'flex justify-between w-full'}>
<button
onClick={() => {
open.value = false
}}
class={clsx(
'w-[118px] rounded-lg py-1 flex justify-center',
isGame.value ? 'bg-white/20' : 'bg-black/20 text-white'
)}
>
</button>
<button
onClick={() => {
user.logout()
open.value = false
router.replace('')
}}
class={clsx(
'w-[118px] rounded-lg py-1 flex justify-center ',
isGame.value ? 'bg-[#ff7372]' : 'bg-[#ff7372] text-white'
)}
>
退
</button>
</div>
</div>
</div>
)
}
<div class="flex justify-around items-center my-10">
{user.isLogin ? (
<>
<Button
onClick={() => {
router.go('global-login')
}}
icon={<EditOutlined />}
type="primary"
>
</Button>
<Button
type="text"
class={'text-[#999] hover:text-[#999]'}
icon={<LogoutOutlined />}
onClick={() => {
Modal.confirm({
title: '退出登录',
content: '确定要退出登录吗?',
onOk: () => {
router.go('')
user.logout()
globalToast.success('已退出登录')
}
})
}}
>
退
</Button>
</>
) : (
<Button
type="primary"
icon={<LoginOutlined />}
size="large"
onClick={() => {
router.go('global-login')
}}
>
</Button>
)}
</div>
</div >
)}
</div>
)
})

View File

@ -32,8 +32,6 @@ function areArraysEqualById(arr1: Block[], arr2: Block[]): boolean {
// 检查 arr1 中的每个 item 是否在 arr2 中存在,并且值是否相同
for (let i = 0; i < arr1.length; i++) {
console.log(arr1[i].id)
console.log(arr2[i].id)
if (arr1[i].id !== arr2[i].id) {
return false
@ -90,7 +88,6 @@ export default defineStore('user', () => {
watch(token, async (val) => {
if (!val) return
console.log(val)
const data = await request<Layout>('GET', '/api/backup')
if (!data) {
@ -126,9 +123,9 @@ export default defineStore('user', () => {
}
const comineData = () => {
if (!remoteAddList.value) return
console.log({ ...remoteAddList.value })
remoteAddList.value.map((item) => {
layout.state.content[layout.state.current].pages[layout.state.currentPage].list.push(item)
if (item.link)
layout.state.content[layout.state.current].pages[layout.state.currentPage].list.push(item)
})
}
const coverageData = () => {
@ -137,10 +134,19 @@ export default defineStore('user', () => {
Object.assign(layout.state.dock, remoteData.value.dock)
Object.assign(layout.state.dir, remoteData.value.dir)
}
const updateProfile = () => {
request('PUT', '/api/profile', {
returnType: 'text',
data: {
...profile
}
})
}
return {
token,
profile,
isLogin,
updateProfile,
logout,
coverageData,
comineData

View File

@ -158,7 +158,7 @@ export default defineComponent({
</div>
</Modal>
<div
class="w-full h-full bg-white flex items-center justify-center"
class="w-full h-full flex items-center justify-center"
onClick={() => {
if (useUserStore().isLogin) {
inputRef.value?.click?.()

View File

@ -14,7 +14,6 @@ const fetchAdverConfig = async () => {
request('GET', '/api/app/adverLinks/params'),
request('GET', '/api/app/adverLinks/link')
]).then((res: any) => {
console.log('----', res)
const result: AdverData = { links: [], params: [], expiration: Date.now() + 1000 * 60 * 60 * 4 }
if (res[0].status === 'fulfilled') {
result.params = res[0].value

View File

@ -7,8 +7,9 @@ export function sendParent(
| [
'configAI',
{
autoSearch: true
showTabButton: true
autoSearch: boolean
showTabButton: boolean
isSearch: boolean
}
]
) {

View File

@ -41,7 +41,7 @@ export async function uploadLocal(file: File) {
list.push({
tag: id,
file: file,
type: 'image'
type: file.type.split('/')[0] === 'video'? 'video' : 'image'
})
await db.setItem('localList', list)
return id

View File

@ -17,7 +17,6 @@ export default defineStore('statistic', () => {
list.value.push(item)
}
const debouncedHandler = debounce((newValue: StatisticType[]) => {
console.log('数值改变并已防抖处理:', newValue)
if (newValue.length === 0) return
request("POST", `/api/app/statistics`, {
data: newValue.map((item) => ({
@ -30,11 +29,9 @@ export default defineStore('statistic', () => {
}).then(() => {
list.value = []
message.success('发送统计成功')
})
}, 2500) //
watch(list, (newValue) => {
console.log(list);
debouncedHandler(newValue)
}, {

View File

@ -31,7 +31,7 @@ export default defineComponent(() => {
}
>
<div class="flex flex-col w-full h-full justify-center gap-y-2 py-2 px-3">
<span class="text-white text-[14px]">{selectItem.value.name}</span>
<span class="text-white text-[14px]">{selectItem.value?.name}</span>
<div>
<span
class={

View File

@ -4,6 +4,7 @@ import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { IoCloseCircleOutline, RiCloseCircleLine } from 'oh-vue-icons/icons'
import useDiscountStore from './useDiscountStore'
import { debounce } from 'lodash'
import jump from '@/utils/jump'
addIcons(RiTimeLine, IoCloseCircleOutline, RiCloseCircleLine)
export default defineComponent(() => {
const store = useDiscountStore()
@ -13,7 +14,7 @@ export default defineComponent(() => {
const container = containerRef.value
if (store.loading) return
if (container.scrollTop + container.clientHeight >= container.scrollHeight - 15) {
store.state.pageIndex += 1;
store.state.pageIndex += 1
store.getNews()
}
@ -25,13 +26,16 @@ export default defineComponent(() => {
store.state.pageIndex = 1
store.noMoreData = false
store.getNews()
}, 500) //
}, 500) //
watch(searchText, (newValue) => {
debouncedHandler(newValue)
}, {
immediate: true
})
onMounted(() => {
store.state.pageIndex = 1
store.getNews()
searchText.value = ''
})
return () => (
<div
@ -57,7 +61,15 @@ export default defineComponent(() => {
}
}}
></input>
<div class={'absolute cursor-pointer hidden group-hover:block right-3 top-1/2 -translate-y-1/2'}>
<div
onClick={(e) => {
e.stopPropagation()
searchText.value = ''
}}
class={
'absolute cursor-pointer hidden group-hover:block right-3 top-1/2 -translate-y-1/2'
}
>
<OhVueIcon name={RiCloseCircleLine.name} fill="#ddd"></OhVueIcon>
</div>
</div>
@ -69,56 +81,61 @@ export default defineComponent(() => {
ref={containerRef}
>
<div class={'grid grid-cols-3 gap-4 '}>
{
(store.state.find ? store.searchList :
store.list).map((item, index) => {
return (
<div
class={
'flex cursor-pointer h-[215px] overflow-hidden bg-[#17212d] items-center flex-col rounded-lg relative '
}
onClick={() => { }}
key={index}
>
<img class={'h-[142px] w-full object-cover'} src={item.commdity[0]?.img}></img>
<div
class={
'absolute bottom-0 w-full h-[100px] rounded-lg bg-[#0003] backdrop-blur-md'
}
>
<div class="flex flex-col w-full h-full justify-between py-2 px-3">
<span class="text-white text-[14px]">{item.name}</span>
<div>
<span
class={
'border-[1px] border-[#f6d1b8] border-solid text-[#f6d1b8] p-1 text-[12px] rounded'
}
>
{item.typename}
</span>
</div>
<div class="bg-white/20 flex rounded items-center gap-x-2">
<div class="bg-[#ef5a41] h-full text-white rounded px-2 text-[18px] font-bold ">
-13%
</div>
<span class="text-[#fffbc2] text-[16px] ml-2">
{item.commdity[0]?.price}
</span>
<span class="text-[12px] text-[#bdbdbd] line-through decoration-current">
{item.commdity[0]?.oldprice}
</span>
<span class="text-[12px] text-[#ebebeb] ">
{1}
</span>
</div>
{(store.state.find ? store.searchList : store.list).map((item, index) => {
return (
<div
class={
'flex cursor-pointer h-[215px] overflow-hidden bg-[#17212d] items-center flex-col rounded-lg relative '
}
onClick={() => {
// jump(item.commdity[0]?.url)
window.open(item.commdity[0]?.url)
}}
key={index}
>
<img class={'h-[142px] w-full object-cover'} src={item.commdity[0]?.img}></img>
<div
class={
'absolute bottom-0 w-full h-[100px] rounded-lg bg-[#0003] backdrop-blur-md'
}
>
<div class="flex flex-col w-full h-full justify-between py-2 px-3">
<span class="text-white text-[14px]">{item.name}</span>
<div>
<span
class={
'border-[1px] border-[#f6d1b8] border-solid text-[#f6d1b8] p-1 text-[12px] rounded'
}
>
{item.typename}
</span>
</div>
<div class="bg-white/20 flex rounded items-center gap-x-2">
<div class="bg-[#ef5a41] h-full text-white rounded px-2 text-[18px] font-bold ">
-13%
</div>
<span class="text-[#fffbc2] text-[16px] ml-2">
{item.commdity[0]?.price}
</span>
<span class="text-[12px] text-[#bdbdbd] line-through decoration-current">
{item.commdity[0]?.oldprice}
</span>
<span class="text-[12px] text-[#ebebeb] ">{1}</span>
</div>
</div>
)
})}
</div>
</div>
)
})}
</div>
{store.loading && (
<div class={'w-full font-bold flex absolute bottom-2 bg-[#17212d] justify-center text-white py-2'}>...</div>
<div
class={
'w-full font-bold flex absolute bottom-2 bg-[#17212d] justify-center text-white py-2'
}
>
...
</div>
)}
{store.noMoreData && (
<div class={'w-full font-bold flex justify-center text-white py-2'}></div>

View File

@ -1,6 +1,6 @@
import clsx from 'clsx'
import dayjs from 'dayjs'
import { defineComponent, ref } from 'vue'
import { defineComponent, reactive, ref, Transition, watch } from 'vue'
export default defineComponent(() => {
const list = [
@ -77,6 +77,11 @@ export default defineComponent(() => {
'炒年糕'
]
const text = ref('')
const open = ref(false)
const pair = reactive({ front: true, back: true })
watch(open, (val) => {
if (!val) text.value = ''
})
return () => (
<div
class="w-full h-full flex flex-col px-[14px] pt-[23px]"
@ -85,7 +90,7 @@ export default defineComponent(() => {
backgroundSize: '100% 100%'
}}
onClick={() => {
text.value = ''
open.value = false
}}
>
<div class={'w-full flex justify-center'}>
@ -95,12 +100,12 @@ export default defineComponent(() => {
<div
class={clsx(
'w-[130px] duration-150 bg-white relative',
text.value ? 'h-[120px]' : 'h-[17px]'
open.value ? 'h-[120px]' : 'h-[17px]'
)}
>
<button
class={
'w-[48px] h-[19px] text-[12px] rounded left-1/2 bottom-[-10px] absolute -translate-x-1/2 rounded-br-[6px] rounded-bl-[6px] text-white'
'w-[48px] z-20 h-[19px] text-[12px] rounded left-1/2 bottom-[-10px] absolute -translate-x-1/2 rounded-br-[6px] rounded-bl-[6px] text-white'
}
style={{
background: 'linear-gradient(180deg,#ffa061 0%,#ffb62f 100%)',
@ -110,26 +115,60 @@ export default defineComponent(() => {
e.stopPropagation()
const index = Math.floor(Math.random() * list.length)
if (text.value) {
text.value = ''
setTimeout(() => {
text.value = list[index]
}, 400)
}else {
pair.front = false
text.value = list[index]
setTimeout(() => {
text.value = list[index]
pair.back = false
setTimeout(() => {
pair.front = true
pair.back = true
}, 250)
}, 250)
} else {
open.value = true
text.value = list[index]
}
}}
>
{text.value ? '换' : '开始'}
{open.value ? '换' : '开始'}
</button>
<span
class={
'text-[12px] text-[#333] absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'
}
>
{text.value}
</span>
<div class={'w-full h-full absolute left-0 top-0 overflow-hidden'}>
<Transition name="eat">
{pair.front && (
<div class={'flex items-center justify-center w-full h-full'}>
<span
class={
'text-[12px] text-[#333] absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'
}
>
{text.value}
</span>
</div>
)}
</Transition>
<Transition name="neweat">
{pair.back && (
<div
class={
'flex items-center justify-center w-full h-full left-0 top-[-130px] absolute '
}
>
<span
class={
'text-[12px] text-[#333] absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'
}
>
{text.value}
</span>
</div>
)}
</Transition>
</div>
</div>
{!text.value && (
<>
<span class={'text-[24px] mt-[24px] text-[#333]'}></span>

View File

@ -29,7 +29,6 @@ export default defineComponent(() => {
watch(
selectType,
(val) => {
console.log(val);
appList.value = []
loading.value = true

View File

@ -0,0 +1,115 @@
import useLayoutStore from '@/layout/useLayoutStore'
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 type { CarouselRef } from 'ant-design-vue/es/carousel'
import { randomNum } from '@/utils/tool'
import jump from '@/utils/jump'
addIcons(FaChevronLeft)
interface Owner {
face: string
mid: number
name: string
}
interface GameData {
_id: string
aid: number
ctime: number
duration: number
owner: Owner
pic: string
rid: string
time: string
title: string
type: string
}
export default defineComponent(() => {
const list = ref<GameData[]>([])
const currentIndex = ref(-1)
const current = computed(() => {
if (currentIndex.value === -1) {
return null
} else {
return list.value[currentIndex.value]
}
})
watch(
() => useLayoutStore().state.current,
(val) => {
const type = val === 0 ? 'game' : val === 1 ? 'know' : 'ent'
request<GameData[]>('GET', `/api/hotVideo?type=${type}`).then((res) => {
list.value = res
currentIndex.value = randomNum(0, res.length)
})
},
{
immediate: true
}
)
onMounted(() => {
setInterval(() => {
currentIndex.value = currentIndex.value === list.value.length - 1 ? 0 : currentIndex.value + 1
}, 7000)
})
return () => (
<div class="w-full h-full p-2 bg-[#17212d] relative flex flex-col ">
<img
src={PlayImg}
class={
'absolute z-10 w-[40px] bg-[#ffffff24] rounded-lg backdrop-blur-sm left-1/2 top-10 -translate-x-1/2'
}
></img>
{
<div
class={'w-full h-[92px] rounded-xl relative group'}
onClick={() => {
jump('https://www.bilibili.com/video/av' + current.value?.aid)
}}
style={{
backgroundImage: `url('${current.value?.pic}')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>
<div
class={
'absolute bottom-0 left-1/2 -translate-x-1/2 pb-2 w-[300px] flex flex-col text-white '
}
></div>
<div
onClick={(e) => {
e.stopPropagation()
currentIndex.value =
currentIndex.value === 0 ? list.value.length - 1 : currentIndex.value - 1
}}
class="absolute hidden bottom-[20px] group-hover:flex items-center justify-center left-[0px] w-[22px] h-[22px] bg-white/30 rounded"
>
<OhVueIcon name={FaChevronLeft.name} class={'text-white/80'}></OhVueIcon>
</div>
<div
onClick={(e) => {
e.stopPropagation()
currentIndex.value =
currentIndex.value === list.value.length - 1 ? 0 : currentIndex.value + 1
}}
class="absolute hidden bottom-[20px] group-hover:flex items-center justify-center right-[0px] rotate-180 w-[22px] h-[22px] bg-white/30 rounded"
>
<OhVueIcon name={FaChevronLeft.name} class={'text-white/80'}></OhVueIcon>
</div>
</div>
}
<span class="text-[14px] mt-2 text-ellipsis overflow-hidden text-white line-clamp-2">
{current.value?.title}
</span>
<span class="text-[12px] opacity-60 text-white">{current.value?.owner.name}</span>
</div>
)
})

View File

@ -0,0 +1,42 @@
import useLayoutStore from '@/layout/useLayoutStore'
import jump from '@/utils/jump'
import { defineComponent } from 'vue'
export default defineComponent(() => {
const layout = useLayoutStore()
return () => (
<div
class="w-full h-full flex items-center px-3"
style={{
background: `rgb(23,33,46)`,
backgroundSize: 'cover'
}}
onClick={() => {
jump(
'https://www.bilibili.com/v' +
(layout.state.current === 0 ? '/game' : layout.state.current === 1 ? '/knowledge' : '/ent')
)
}}
>
<img class={'w-[58px] h-[58px]'} src={'/tab/icons/game_video.png'}></img>
<div class={'flex-1 flex justify-center'}>
<div class="flex-col flex">
<span class={'text-[16px] text-white'}>
{useLayoutStore().state.current === 0
? '游戏'
: useLayoutStore().state.current === 1
? '学习'
: '娱乐'}
</span>
<div class={'flex items-center text-[#fffc] text-[12px] '}>
<div>
<img src="/tab/icons/gt.png"></img>
</div>
</div>
</div>
</div>
</div>
)
})

View File

@ -13,6 +13,18 @@ export default {
h: 2,
label: '大',
component: asyncLoader(() => import('./Large'))
},
{
w: 2,
h: 2,
label: '中',
component: asyncLoader(() => import('./Middle'))
},
{
w: 2,
h: 1,
label: '小',
component: asyncLoader(() => import('./Small'))
}
]
} as Widget

View File

@ -114,6 +114,7 @@ export default defineComponent(() => {
trriger.value &&
<MilkdownProvider >
<Editor
class={"w-full h-full "}
modelValue={store.state.list[currentIndex.value]?.content || ''} onUpdate:modelValue={(e) => {
if (store.state.list[currentIndex.value]) {

View File

@ -1,72 +1,63 @@
import { Milkdown, useEditor } from '@milkdown/vue';
import { defaultValueCtx, Editor, rootCtx } from '@milkdown/kit/core';
import { Milkdown, useEditor } from '@milkdown/vue'
import { defaultValueCtx, Editor, rootCtx } from '@milkdown/kit/core'
import { nord } from '@milkdown/theme-nord'
import { commonmark } from '@milkdown/kit/preset/commonmark'
import { gfm } from '@milkdown/kit/preset/gfm';
import { defineComponent, watch} from 'vue';
import { gfm } from '@milkdown/kit/preset/gfm'
import { defineComponent } from 'vue'
import { menu, menuConfigCtx, type MenuConfigItem } from '@milkdown-lab/plugin-menu'
import { listener, listenerCtx } from '@milkdown/kit/plugin/listener';
import { listener, listenerCtx } from '@milkdown/kit/plugin/listener'
const menuItems: MenuConfigItem[][] = [
[
{
type: 'button',
content: 'B',
// commandKey
key: 'ToggleStrong',
},
{
type: 'button',
content: 'I',
key: 'ToggleEmphasis',
},
{
type: 'button',
content: 'S',
key: 'ToggleStrikeThrough',
},
],
[
{
type: 'button',
content: 'B',
// commandKey
key: 'ToggleStrong'
},
{
type: 'button',
content: 'I',
key: 'ToggleEmphasis'
},
{
type: 'button',
content: 'S',
key: 'ToggleStrikeThrough'
}
]
]
export default defineComponent({
props: {
modelValue: {
type: String,
default: '',
}
},
emits: ['update:modelValue'],
setup: (props, ct) => {
useEditor((root) => {
return Editor.make()
.config(nord)
.config((ctx) => {
const listener = ctx.get(listenerCtx);
ctx.set(menuConfigCtx.key, {
attributes: { class: 'milkdown-menu', 'data-menu': 'true' },
items: menuItems,
})
ctx.set(rootCtx, document.querySelector('#app'))
listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
if (markdown !== prevMarkdown) {
ct.emit('update:modelValue', markdown)
}
})
ctx.set(rootCtx, root)
ctx.set(defaultValueCtx, props.modelValue || ' ')
})
.use(listener)
.use(commonmark)
.use(gfm)
.use(menu)
})
return () => (
<Milkdown />
)
props: {
modelValue: {
type: String,
default: ''
}
},
emits: ['update:modelValue'],
setup: (props, ct) => {
useEditor((root) => {
return Editor.make()
.config(nord)
.config((ctx) => {
const listener = ctx.get(listenerCtx)
ctx.set(menuConfigCtx.key, {
attributes: { class: 'milkdown-menu', 'data-menu': 'true' },
items: menuItems
})
ctx.set(rootCtx, document.querySelector('#app'))
listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
if (markdown !== prevMarkdown) {
ct.emit('update:modelValue', markdown)
}
})
ctx.set(rootCtx, root)
ctx.set(defaultValueCtx, props.modelValue || ' ')
})
.use(listener)
.use(commonmark)
.use(gfm)
.use(menu)
})
return () => <Milkdown />
}
})

View File

@ -1,9 +1,9 @@
import { defineStore } from "pinia";
import { reactive } from "vue";
import { v4 as uuid } from "uuid"
import dayjs from "dayjs";
import { defineStore } from 'pinia'
import { reactive } from 'vue'
import { v4 as uuid } from 'uuid'
import dayjs from 'dayjs'
export const DEFALUT_DATA = {
content: `
content: `
FatfoxTab新标签页平台!
@ -104,34 +104,43 @@ FatfoxTab新标签页共分为四个模式
...
`,
title: "FatFoxTab指南",
date: 1730877843004,
id: "defautId"
};
export type NotepadItem = {
title: string
content: string
id: string
date: number
pin: boolean
title: 'FatFoxTab指南',
date: 1730877843004,
id: 'defautId',
pin: false
}
export default defineStore("notepad", () => {
export type NotepadItem = {
title: string
content: string
id: string
date: number
pin: boolean
}
export default defineStore(
'notepad',
() => {
const state = reactive({
list: [DEFALUT_DATA] as NotepadItem[]
list: [DEFALUT_DATA] as NotepadItem[]
})
const addNewNote = () => {
state.list.unshift({
id: uuid(),
title: '',
date: dayjs().valueOf(),
content: '',
pin: false
})
state.list.unshift({
id: uuid(),
title: '',
date: dayjs().valueOf(),
content: '',
pin: false
})
}
const reset = () => {
state.list = [DEFALUT_DATA]
}
return {
state,
addNewNote
state,
addNewNote,
reset
}
}, {
},
{
persist: true
})
}
)

View File

@ -83,13 +83,13 @@ export default defineComponent(() => {
>
<img src={item.icon} alt="game icon" class={'w-[37px] h-[37px] rounded'}></img>
<div class={'flex-1 flex flex-col overflow-hidden'}>
<span class={'text-white text-[14px]'}>{item.name}</span>
<span class={'text-white text-[14px]'}>{item?.name}</span>
<span
class={
'text-[#fff9] text-[12px] whitespace-nowrap text-ellipsis overflow-hidden'
}
>
{item.des}
{item?.des}
</span>
</div>
</div>

View File

@ -26,7 +26,7 @@ export default defineComponent(() => {
'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 : '无目标'}
{store.state.list ? store.state.list.filter((val) => !val.isCompleted).pop()?.title : '无目标'}
</div>
<span class={'text-[42px] mb-1'}>
{store.state.beginTime < 0 ? '15:00' : formatSeconds(store.remainingTime)}

View File

@ -31,7 +31,7 @@ export default defineComponent(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
store.state.list.filter(val => val.title.includes(searchText.value)).sort((a, _) => {
return !a.isCompleted ? -1 : 1
}).map((item, idx) => (
}).map((item, _) => (
<div
key={item.id}
onClick={() => {
@ -47,7 +47,11 @@ export default defineComponent(() => {
)}
onClick={(e) => {
e.stopPropagation()
store.state.list[idx].isCompleted = !item.isCompleted
const idx = store.state.list.findIndex(val => val.id === item.id)
if (idx !== -1) {
store.state.list[idx].isCompleted = !item.isCompleted
}
}}>
{
!item.isCompleted &&

View File

@ -39,20 +39,21 @@ export const musicList = [
music: 'https://newfatfox.oss-cn-beijing.aliyuncs.com/admin/music/AWonderfulStore.mp3'
}
]
const initData = {
list: [] as TomatoTarget[],
timeList: [] as number[],
isPlaying: false as boolean,
selectMusic: 0,
isStart: false as boolean,
beginTime: -1 as number,
}
export default defineStore("work", () => {
const state = reactive({
list: [] as TomatoTarget[],
timeList: [] as number[],
isPlaying: false as boolean,
selectMusic: 0,
isStart: false as boolean,
beginTime: -1 as number,
})
const state = reactive({ ...initData })
const audio = new Audio()
const time = useTimeStore()
const remainingTime = computed(() => {
return dayjs(state.beginTime).add(1, 'minute').diff(dayjs(time.date), 'second')
return dayjs(state.beginTime).add(15, 'minute').diff(dayjs(time.date), 'second')
})
const stopTomatoTime = () => {
@ -91,7 +92,7 @@ export default defineStore("work", () => {
}
watch(remainingTime, (val) => {
if (val < 0) {
stopTomatoTime()
@ -117,7 +118,9 @@ export default defineStore("work", () => {
return state.list.filter(val => dayjs(val.finishTime).isSame(dayjs().subtract(1, 'day'), 'day') && val.isCompleted).length
})
const reset = () => {
Object.assign(state, { ...initData })
}
return {
state,
openShowModel,
@ -131,7 +134,8 @@ export default defineStore("work", () => {
yestodayHour,
todayFinishTarget,
yesFinishTarget,
setTrack
setTrack,
reset
}
}, {
persist: true