Merge remote-tracking branch 'origin/tomato'
10
package.json
|
@ -15,12 +15,22 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
||||||
|
"@milkdown-lab/plugin-menu": "^1.3.1",
|
||||||
|
"@milkdown/kit": "7.5.0",
|
||||||
|
"@milkdown/plugin-menu": "^6.5.4",
|
||||||
|
"@milkdown/plugin-prism": "^7.5.0",
|
||||||
|
"@milkdown/plugin-tooltip": "^7.5.3",
|
||||||
|
"@milkdown/theme-nord": "7.5.0",
|
||||||
|
"@milkdown/vue": "7.5.0",
|
||||||
"@pixi/spine-pixi": "^2.1.0",
|
"@pixi/spine-pixi": "^2.1.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
|
"@type-config/strict": "^1.2.1",
|
||||||
"ali-oss": "^6.21.0",
|
"ali-oss": "^6.21.0",
|
||||||
"ant-design-vue": "4.x",
|
"ant-design-vue": "4.x",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lunar-typescript": "^1.7.5",
|
"lunar-typescript": "^1.7.5",
|
||||||
|
|
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 440 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 634 B |
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 321 B |
|
@ -34,7 +34,7 @@ const layout = useLayoutStore()
|
||||||
<SettingsOverlay />
|
<SettingsOverlay />
|
||||||
<SettingsButton />
|
<SettingsButton />
|
||||||
<Sider />
|
<Sider />
|
||||||
<LoginModal v-if="router.path !== 'global-login'"/>
|
<LoginModal v-if="router.path === 'global-login'"/>
|
||||||
<Grid v-if="layout.ready" />
|
<Grid v-if="layout.ready" />
|
||||||
<Dock />
|
<Dock />
|
||||||
<div class="fixed z-40 right-[14%] top-8">
|
<div class="fixed z-40 right-[14%] top-8">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { computed, defineComponent, provide, ref, type InjectionKey, type Ref } from 'vue'
|
import { computed, defineComponent, inject, onMounted, onUnmounted, provide, ref, type InjectionKey, type Ref } from 'vue'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
||||||
import {
|
import {
|
||||||
|
@ -14,7 +14,8 @@ import WidgetAdder from './WidgetAdder'
|
||||||
import { Form, Input, Select } from 'ant-design-vue'
|
import { Form, Input, Select } from 'ant-design-vue'
|
||||||
import HotAdder from './HotAdder'
|
import HotAdder from './HotAdder'
|
||||||
import GameAdder from './GameAdder'
|
import GameAdder from './GameAdder'
|
||||||
import work_add_main_checked from '/tab/icons/work_add_main_checked.png'
|
import work_add_main_checked from '/icons/work_add_main_checked.png'
|
||||||
|
import useAdderPageStore from './useAdderPageStore'
|
||||||
addIcons(MdKeyboardcommandkey, FaCompass, FaPencilRuler, IoGameController)
|
addIcons(MdKeyboardcommandkey, FaCompass, FaPencilRuler, IoGameController)
|
||||||
|
|
||||||
const ItemButton = defineComponent({
|
const ItemButton = defineComponent({
|
||||||
|
@ -67,23 +68,21 @@ const ItemButton = defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const AddToToken = Symbol('addTo') as InjectionKey<Ref<number>>
|
export const AddToToken = Symbol('addTo') as InjectionKey<Ref<number>>
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const layout = useLayoutStore()
|
const layout = useLayoutStore()
|
||||||
const isGame = computed(() => layout.state.current === 0)
|
const isGame = computed(() => layout.state.current === 0)
|
||||||
const type = ref(1)
|
const store = useAdderPageStore()
|
||||||
const addTo = ref(layout.state.currentPage)
|
const addTo = ref(layout.state.currentPage)
|
||||||
provide(AddToToken, addTo)
|
provide(AddToToken, addTo)
|
||||||
|
onUnmounted(()=> {
|
||||||
|
store.type = 1
|
||||||
|
})
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class={clsx(
|
class={clsx(
|
||||||
'w-full h-full relative flex ',
|
'w-full h-full relative flex ',
|
||||||
isGame.value ? 'bg-[#2C2E3D]' : 'bg-[#fffc] backdrop-blur'
|
isGame.value ? 'bg-[#2C2E3D]' : 'bg-[#fffc] backdrop-blur'
|
||||||
)}
|
)}
|
||||||
// style={{
|
|
||||||
// backgroundImage: `url('/tab/bg/gameModel.png')`,
|
|
||||||
// backgroundSize: '100% 100%'
|
|
||||||
// }}
|
|
||||||
>
|
>
|
||||||
<ThemeProvider dark={isGame.value}>
|
<ThemeProvider dark={isGame.value}>
|
||||||
<div
|
<div
|
||||||
|
@ -95,33 +94,33 @@ export default defineComponent(() => {
|
||||||
<ItemButton
|
<ItemButton
|
||||||
name="md-keyboardcommandkey"
|
name="md-keyboardcommandkey"
|
||||||
label="功能组件"
|
label="功能组件"
|
||||||
active={type.value === 0}
|
active={store.type === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
type.value = 0
|
store.type = 0
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ItemButton
|
<ItemButton
|
||||||
name="fa-compass"
|
name="fa-compass"
|
||||||
label="网站图标"
|
label="网站图标"
|
||||||
active={type.value === 1}
|
active={store.type === 1}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
type.value = 1
|
store.type = 1
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ItemButton
|
<ItemButton
|
||||||
name="fa-pencil-ruler"
|
name="fa-pencil-ruler"
|
||||||
label="自定义网址"
|
label="自定义网址"
|
||||||
active={type.value === 2}
|
active={store.type === 2}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
type.value = 2
|
store.type = 2
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ItemButton
|
<ItemButton
|
||||||
name="io-game-controller"
|
name="io-game-controller"
|
||||||
label="快游戏"
|
label="快游戏"
|
||||||
active={type.value === 3}
|
active={store.type === 3}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
type.value = 3
|
store.type = 3
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -146,11 +145,11 @@ export default defineComponent(() => {
|
||||||
</Form>
|
</Form>
|
||||||
<div class="w-full h-0 flex-grow p-6">
|
<div class="w-full h-0 flex-grow p-6">
|
||||||
<div class="w-full h-full relative">
|
<div class="w-full h-full relative">
|
||||||
{type.value === 0 ? (
|
{store.type === 0 ? (
|
||||||
<WidgetAdder />
|
<WidgetAdder />
|
||||||
) : type.value === 1 ? (
|
) : store.type=== 1 ? (
|
||||||
<HotAdder />
|
<HotAdder />
|
||||||
) : type.value === 2 ? (
|
) : store.type === 2 ? (
|
||||||
<CustomAdder />
|
<CustomAdder />
|
||||||
) : (
|
) : (
|
||||||
<GameAdder />
|
<GameAdder />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import CategoryTab from '@/utils/CategoryTab'
|
import CategoryTab from '@/utils/CategoryTab'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import { computed, defineComponent, inject, ref, watch } from 'vue'
|
import { computed, defineComponent, inject, ref, watch } from 'vue'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import { AddToToken } from './AdderPage'
|
import { AddToToken } from './AdderPage'
|
||||||
|
@ -9,9 +9,9 @@ import { frontAddress, ossBase } from '@/config'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { generateRandomString } from '@/utils/tool'
|
import { generateRandomString } from '@/utils/tool'
|
||||||
import MD5 from 'crypto-js/md5'
|
import MD5 from 'crypto-js/md5'
|
||||||
const SECRET = 'A1Cv12olxT12dOE3xA1vPA=='
|
export const SECRET = 'A1Cv12olxT12dOE3xA1vPA=='
|
||||||
const URL_ADDRESS = 'http://newfatfox.oss-cn-beijing.aliyuncs.com'
|
export const URL_ADDRESS = 'http://newfatfox.oss-cn-beijing.aliyuncs.com'
|
||||||
interface GameType {
|
export interface GameType {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
rom: string
|
rom: string
|
||||||
|
@ -21,7 +21,7 @@ interface GameType {
|
||||||
despt: string
|
despt: string
|
||||||
icon: string
|
icon: string
|
||||||
}
|
}
|
||||||
interface OtherGame {
|
export interface OtherGame {
|
||||||
id: number // 游戏ID
|
id: number // 游戏ID
|
||||||
category_ids: number[] // 分类ID数组
|
category_ids: number[] // 分类ID数组
|
||||||
rank: number // 排名
|
rank: number // 排名
|
||||||
|
@ -33,6 +33,32 @@ interface OtherGame {
|
||||||
cover_url: string // 封面URL
|
cover_url: string // 封面URL
|
||||||
corner_mark: number // 角标标识
|
corner_mark: number // 角标标识
|
||||||
}
|
}
|
||||||
|
export const DefautGameTypeList = [
|
||||||
|
{
|
||||||
|
id: 'fc',
|
||||||
|
type: '经典红白机',
|
||||||
|
attr: 0,
|
||||||
|
oridinal: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'md',
|
||||||
|
type: '经典世嘉',
|
||||||
|
attr: 1,
|
||||||
|
oridinal: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'yiqiyoo',
|
||||||
|
type: '休闲游戏',
|
||||||
|
attr: 1,
|
||||||
|
oridinal: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gba',
|
||||||
|
type: '经典GBA',
|
||||||
|
attr: 3,
|
||||||
|
oridinal: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
export const GameItem = defineComponent({
|
export const GameItem = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
content: {
|
content: {
|
||||||
|
@ -100,9 +126,9 @@ export const GameItem = defineComponent({
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
link: !props.content.rom.startsWith('http')
|
link: !props.content.rom.startsWith('http')
|
||||||
? `${frontAddress}/emu/#/home?params=${JSON.stringify({
|
? `${frontAddress}/emu/#/home?params=${JSON.stringify({
|
||||||
...props.content,
|
...props.content,
|
||||||
rom: ossBase + '/' + props.content.rom
|
rom: ossBase + '/' + props.content.rom
|
||||||
})}`
|
})}`
|
||||||
: props.content.rom,
|
: props.content.rom,
|
||||||
name: '',
|
name: '',
|
||||||
label: props.content.name,
|
label: props.content.name,
|
||||||
|
@ -188,32 +214,7 @@ export default defineComponent(() => {
|
||||||
<div class={'w-full h-full flex flex-col gap-y-4'}>
|
<div class={'w-full h-full flex flex-col gap-y-4'}>
|
||||||
<div class={'w-full '}>
|
<div class={'w-full '}>
|
||||||
<CategoryTab
|
<CategoryTab
|
||||||
list={[
|
list={DefautGameTypeList}
|
||||||
{
|
|
||||||
id: 'fc',
|
|
||||||
type: '经典红白机',
|
|
||||||
attr: 0,
|
|
||||||
oridinal: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'md',
|
|
||||||
type: '经典世嘉',
|
|
||||||
attr: 1,
|
|
||||||
oridinal: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'yiqiyoo',
|
|
||||||
type: '休闲游戏',
|
|
||||||
attr: 1,
|
|
||||||
oridinal: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gba',
|
|
||||||
type: '经典GBA',
|
|
||||||
attr: 3,
|
|
||||||
oridinal: 3
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
selectType={selectType.value}
|
selectType={selectType.value}
|
||||||
onUpdate:type={(e) => {
|
onUpdate:type={(e) => {
|
||||||
selectType.value = e
|
selectType.value = e
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const WidgetItem = defineComponent({
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<img
|
<img
|
||||||
src={ props.content.icon}
|
src={props.content.icon}
|
||||||
class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg"
|
class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg"
|
||||||
/>
|
/>
|
||||||
<div class="px-2 w-0 flex-grow ">
|
<div class="px-2 w-0 flex-grow ">
|
||||||
|
@ -119,12 +119,19 @@ export const WidgetItem = defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
|
const layout = useLayoutStore()
|
||||||
return () => (
|
return () => (
|
||||||
<div class="absolute left-0 top-0 w-full h-full overflow-y-auto scrollbar-hide gap-4">
|
<div class="absolute left-0 top-0 w-full h-full overflow-y-auto scrollbar-hide gap-4">
|
||||||
<div class="w-full grid grid-cols-3 grid-flow-row-dense gap-4" style="grid-auto-rows: 120px">
|
<div class="w-full grid grid-cols-3 grid-flow-row-dense gap-4" style="grid-auto-rows: 120px">
|
||||||
{widgetList.map((el) => (
|
{
|
||||||
<WidgetItem content={el} key={el.name} />
|
layout.state.current !== 1 ?
|
||||||
))}
|
widgetList.filter(val => val.name !== 'tomato_work').map((el) => (
|
||||||
|
<WidgetItem content={el} key={el.name} />
|
||||||
|
)) :
|
||||||
|
widgetList.map((el) => (
|
||||||
|
<WidgetItem content={el} key={el.name} />
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export default defineStore("adderPage", () => {
|
||||||
|
const type = ref(1)
|
||||||
|
return {
|
||||||
|
type
|
||||||
|
}
|
||||||
|
})
|
|
@ -22,7 +22,7 @@ export default defineComponent({
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
const layout = useLayoutStore()
|
const layout = useLayoutStore()
|
||||||
let it = 0
|
let it: any = 0
|
||||||
const hover = ref(false)
|
const hover = ref(false)
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { computed, defineComponent, onMounted, ref, Transition } from 'vue'
|
import { computed, defineComponent, onMounted, ref, Transition, watch } from 'vue'
|
||||||
import returnImg from "~/public/icons/work/return.png"
|
import returnImg from "~/icons/work/return.png"
|
||||||
import endImg from "~/public/icons/work/tomotoIconEnd.png"
|
import endImg from "~/icons/work/tomotoIconEnd.png"
|
||||||
import playWaveGif from "~/public/icons/work/playMusicIcon.gif"
|
import playWaveGif from "~/icons/work/playMusicIcon.gif"
|
||||||
import PlayStartImg from "~/public/icons/work/start.png"
|
import PlayStartImg from "~/icons/work/start.png"
|
||||||
import musicIcon from "~/public/icons/work/musicIcon.png"
|
import musicIcon from "~/icons/work/musicIcon.png"
|
||||||
import useBackgroundStore from '../background/useBackgroundStore'
|
import useBackgroundStore from '../background/useBackgroundStore'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import useTomatoStore, { musicList } from '@/widgets/work/useTomatoStore'
|
import useTomatoStore, { musicList } from '@/widgets/work/useTomatoStore'
|
||||||
import Search from '../header/search'
|
import Search from '../header/search'
|
||||||
import { Tooltip } from 'ant-design-vue'
|
import { Modal, Tooltip } from 'ant-design-vue'
|
||||||
import { formatSeconds } from '@/utils/tool'
|
import { formatSeconds } from '@/utils/tool'
|
||||||
|
import clsx from 'clsx'
|
||||||
export const DefaultPageSetting = [
|
export const DefaultPageSetting = [
|
||||||
{
|
{
|
||||||
name: '游戏',
|
name: '游戏',
|
||||||
|
@ -37,13 +38,11 @@ export const DefaultPageSetting = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const show = computed(() => true)
|
|
||||||
const selectMode = ref(0)
|
|
||||||
const background = useBackgroundStore()
|
const background = useBackgroundStore()
|
||||||
const layout = useLayoutStore()
|
const layout = useLayoutStore()
|
||||||
|
|
||||||
const store = useTomatoStore()
|
const store = useTomatoStore()
|
||||||
const isFirst = ref(false)
|
const isFirst = ref(false)
|
||||||
|
const showSelectModal = ref(false)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 检查 localStorage 是否已经有访问记录
|
// 检查 localStorage 是否已经有访问记录
|
||||||
const visited = localStorage.getItem('hasVisited')
|
const visited = localStorage.getItem('hasVisited')
|
||||||
|
@ -54,9 +53,11 @@ export default defineComponent(() => {
|
||||||
// 设置标记,后续访问不会再次显示
|
// 设置标记,后续访问不会再次显示
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
store.openFullscreen && (
|
store.openFullscreen && (
|
||||||
<div class="fixed left-0 top-0 z-50 w-full ">
|
<div class="fixed left-0 top-0 z-50 w-full ">
|
||||||
|
|
||||||
<Transition name="background">
|
<Transition name="background">
|
||||||
{background.bgTrriger && (
|
{background.bgTrriger && (
|
||||||
<>
|
<>
|
||||||
|
@ -76,28 +77,72 @@ export default defineComponent(() => {
|
||||||
<Transition name="modal">
|
<Transition name="modal">
|
||||||
|
|
||||||
<div class={"w-[500px] h-[500px] absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 "}>
|
<div class={"w-[500px] h-[500px] absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 "}>
|
||||||
{
|
|
||||||
Array.from({ length: 60 }).map((_, idx) => (
|
|
||||||
<div class={"bg-white w-[30px] h-[5px] absolute mt-[-2.5px] top-1/2"} style={{
|
|
||||||
transformOrigin: '250px',
|
|
||||||
borderRadius: '3px',
|
|
||||||
transform: `rotateZ(${idx * 6}deg)`
|
|
||||||
|
|
||||||
}}>
|
<Modal open={showSelectModal.value} footer={null} class={'bg-white p-0 rounded-lg'}
|
||||||
|
destroyOnClose
|
||||||
|
centered
|
||||||
|
onCancel={() => {
|
||||||
|
showSelectModal.value = false
|
||||||
|
}}>
|
||||||
|
<div class={"flex flex-col items-center px-4"}>
|
||||||
|
<span class={"text-[#333] font-bold text-[16px] mb-5"}>音乐选择</span>
|
||||||
|
<div class={"flex flex-col w-full gap-y-1"}>
|
||||||
|
{
|
||||||
|
musicList.map((item, idx) => (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
store.state.selectMusic = idx
|
||||||
|
store.setTrack(idx)
|
||||||
|
}}
|
||||||
|
class={"bg-black/[0.05] cursor-pointer rounded-lg text-[#333] text-[14px] py-2 pl-10 border-[1px] relative border-transparent hover:border-[#5955FB]"}>
|
||||||
|
{
|
||||||
|
store.state.selectMusic === idx &&
|
||||||
|
<img src={playWaveGif} class={" absolute left-2 top-1/2 -translate-y-1/2"}></img>
|
||||||
|
|
||||||
|
}
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
))
|
<button
|
||||||
}
|
onClick={() => {
|
||||||
|
showSelectModal.value = false
|
||||||
|
|
||||||
|
}}
|
||||||
|
class={"px-10 py-2 mt-7 font-bold text-[16px] text-white rounded-lg flex items-center justify-center cursor-pointer hover:opacity-90 duration-150"} style={{
|
||||||
|
background: 'linear-gradient(225deg,#642FFF 0%,#5162FF 100%)',
|
||||||
|
boxShadow: '0 2px 6px #00000026'
|
||||||
|
}}>确定</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<div class={"w-full h-full absolute z-0 rotate-90"}>
|
||||||
|
{
|
||||||
|
Array.from({ length: 60 }).map((_, idx) => (
|
||||||
|
<div class={clsx(" w-[30px] h-[5px] absolute mt-[-2.5px] top-1/2",
|
||||||
|
(((60 * 15 - store.remainingTime) / 15) >= idx) ? "bg-white" : "bg-white/50"
|
||||||
|
)} style={{
|
||||||
|
transformOrigin: '250px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
transform: `rotateZ(${idx * 6}deg)`
|
||||||
|
|
||||||
|
}}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class={"w-[500px] flex justify-center flex-col items-center gap-y-3 h-full text-white "}>
|
<div class={"w-[500px] flex justify-center flex-col items-center gap-y-3 h-full text-white "}>
|
||||||
<span class={"text-[24px] leading-[36px]"}>专注中</span>
|
<span class={"text-[24px] leading-[36px]"}>专注中</span>
|
||||||
<span class={"font-din text-[82px] font-bold leading-[115px]"}>{!store.state.isStart ? '15:00' : formatSeconds(store.remainingTime)}</span>
|
<span class={"font-din text-[82px] font-bold leading-[115px]"}>{!store.state.isStart ? '15:00' : formatSeconds(store.remainingTime)}</span>
|
||||||
<div class={"relative"}>
|
<div class={"relative"}>
|
||||||
<div class={"aboslute w-[370px]"}>
|
<div class={"aboslute w-[370px] z-10"}>
|
||||||
<Search isMini></Search>
|
<Search isMini></Search>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class={"w-full flex gap-x-4 justify-center mt-5"}>
|
<div class={"w-full flex gap-x-4 justify-center z-[1] mt-5"}>
|
||||||
<Tooltip title={"返回工作模式"}>
|
<Tooltip title={"返回工作模式"}>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -111,21 +156,38 @@ export default defineComponent(() => {
|
||||||
<img src={returnImg} alt='return' class={"w-[18px] h-[18px]"}></img>
|
<img src={returnImg} alt='return' class={"w-[18px] h-[18px]"}></img>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={"停止"}>
|
<Tooltip title={store.state.isStart ? "停止" : '开始'}>
|
||||||
<div
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
store.state.isStart ?
|
||||||
|
store.stopTomatoTime() :
|
||||||
|
store.beginTomatoTime()
|
||||||
|
|
||||||
|
}}
|
||||||
class={"w-[44px] h-[44px] flex items-center justify-center rounded-lg cursor-pointer hover:opacity-90"}
|
class={"w-[44px] h-[44px] flex items-center justify-center rounded-lg cursor-pointer hover:opacity-90"}
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(225deg,#707eff 0%,#6b97ff 100%)',
|
background: 'linear-gradient(225deg,#707eff 0%,#6b97ff 100%)',
|
||||||
boxShadow: '0 2px 4px #0003'
|
boxShadow: '0 2px 4px #0003'
|
||||||
}}>
|
}}>
|
||||||
<img src={endImg} alt='return' class={"w-[18px] h-[18px]"}></img>
|
{
|
||||||
|
store.state.isStart ?
|
||||||
|
<img src={endImg} alt='return' class={"w-[18px] h-[18px]"}></img>
|
||||||
|
:
|
||||||
|
<img src={PlayStartImg} alt="start" class={"w-[18px] h-[18px]"} />
|
||||||
|
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div class={"w-[140px] h-[44px] relative bg-[#f0f0f0] rounded-lg flex px-3 items-center justify-between cursor-pointer hover:opacity-90"}
|
<div class={"w-[140px] h-[44px] relative bg-[#f0f0f0] rounded-lg flex px-3 gap-x-2 items-center justify-between cursor-pointer hover:opacity-90"}
|
||||||
style={{
|
style={{
|
||||||
boxShadow: '0 2px 4px #0003'
|
boxShadow: '0 2px 4px #0003'
|
||||||
}}>
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
showSelectModal.value = true
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
{
|
{
|
||||||
store.state.isPlaying ?
|
store.state.isPlaying ?
|
||||||
<img src={playWaveGif} alt='return' class={"w-[18px] h-[18px] "}></img>
|
<img src={playWaveGif} alt='return' class={"w-[18px] h-[18px] "}></img>
|
||||||
|
@ -133,8 +195,19 @@ export default defineComponent(() => {
|
||||||
<img src={musicIcon} alt='return' class={"w-[18px] h-[18px] "}></img>
|
<img src={musicIcon} alt='return' class={"w-[18px] h-[18px] "}></img>
|
||||||
}
|
}
|
||||||
<span class={"whitespace-nowrap text-ellipsis overflow-hidden text-[#333]"}>{musicList[store.state.selectMusic].name}</span>
|
<span class={"whitespace-nowrap text-ellipsis overflow-hidden text-[#333]"}>{musicList[store.state.selectMusic].name}</span>
|
||||||
<div class={"w-[26px] h-[26px] right-[-14px] rounded-full bg-[#c0c0c0] absolute flex items-center justify-center"}>
|
<div
|
||||||
<img src={PlayStartImg} alt='start ' class={"w-[12px] h-[12px]"}></img>
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
store.togglePlay()
|
||||||
|
}}
|
||||||
|
class={"w-[26px] h-[26px] right-[-14px] rounded-full bg-[#c0c0c0] absolute flex items-center justify-center"}>
|
||||||
|
{
|
||||||
|
store.state.isPlaying ?
|
||||||
|
<img src={endImg} alt='start ' class={"w-[12px] h-[12px]"}></img>
|
||||||
|
:
|
||||||
|
<img src={PlayStartImg} alt='start ' class={"w-[12px] h-[12px]"}></img>
|
||||||
|
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { computed, defineComponent, onMounted, ref, Transition } from 'vue'
|
import { computed, defineComponent, onMounted, ref, Transition } from 'vue'
|
||||||
import WelcomeImg from '~/public/icons/welcome/welcomeTitle.png'
|
import WelcomeImg from '~/icons/welcome/welcomeTitle.png'
|
||||||
import DivBgImg from '~/public/icons/welcome/back.png'
|
import DivBgImg from '~/icons/welcome/back.png'
|
||||||
import startUseImg from '~/public/icons/welcome/startUse.png'
|
import startUseImg from '~/icons/welcome/startUse.png'
|
||||||
import useBackgroundStore from '../background/useBackgroundStore'
|
import useBackgroundStore from '../background/useBackgroundStore'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
export const DefaultPageSetting = [
|
export const DefaultPageSetting = [
|
||||||
|
|
|
@ -16,7 +16,10 @@ export default defineComponent({
|
||||||
const router = useRouterStore()
|
const router = useRouterStore()
|
||||||
return () => {
|
return () => {
|
||||||
const placeholder = (
|
const placeholder = (
|
||||||
<div class="w-full h-full flex justify-center items-center text-black/60">组件维护中</div>
|
<div class="w-full h-full flex justify-center items-center text-black/60" onContextmenu={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
menu.open(props.block)
|
||||||
|
}}>组件维护中</div>
|
||||||
)
|
)
|
||||||
const selected = widgetList.find((el) => el.name === props.block.name)
|
const selected = widgetList.find((el) => el.name === props.block.name)
|
||||||
if (!selected) return placeholder
|
if (!selected) return placeholder
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default defineComponent({
|
||||||
const router = useRouterStore()
|
const router = useRouterStore()
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class="absolute left-0 -bottom-2 translate-y-full w-full rounded-lg bg-white/60 backdrop-blur shadow-lg p-4 flex flex-wrap gap-4"
|
class="absolute left-0 -bottom-2 z-10 translate-y-full w-full rounded-lg bg-white/60 backdrop-blur shadow-lg p-4 flex flex-wrap gap-4"
|
||||||
v-outside-click={() => {
|
v-outside-click={() => {
|
||||||
search.showSearchConfig = false
|
search.showSearchConfig = false
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default defineComponent(() => {
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
return () =>
|
return () =>
|
||||||
settings.state.showHistory && (
|
settings.state.showHistory && (
|
||||||
<div class="absolute left-0 -bottom-2 translate-y-full w-full rounded-lg bg-white/60 backdrop-blur shadow-lg p-4">
|
<div class="absolute left-0 -bottom-2 translate-y-full w-full rounded-lg z-10 bg-white/60 backdrop-blur shadow-lg p-4">
|
||||||
{searchConfig.history.map((item, idx) => (
|
{searchConfig.history.map((item, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
|
|
42
src/main.css
|
@ -2,6 +2,48 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.milkdown {
|
||||||
|
@apply bg-slate-50 p-3 border rounded;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 18px);
|
||||||
|
border-radius: 12px 12px 20px 20px;
|
||||||
|
background: rgba(189, 171, 155, 0.1) !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: 0 0 #fff !important;
|
||||||
|
color: #333 !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 80px;
|
||||||
|
}
|
||||||
|
.milkdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px 12px 0 0 !important;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgba(215, 203, 185, 0.4) !important;
|
||||||
|
border: none !important;
|
||||||
|
outline: none;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.milkdown ul {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.milkdown-menu ul li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.milkdown-menu button {
|
||||||
|
color: #bca694 !important;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-top: 5px !important;
|
||||||
|
padding-bottom: 5px !important;
|
||||||
|
}
|
||||||
:root {
|
:root {
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import App from './App.vue'
|
||||||
import getFp from './utils/getFp'
|
import getFp from './utils/getFp'
|
||||||
import vOutsideClick from './utils/vOutsideClick'
|
import vOutsideClick from './utils/vOutsideClick'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import Toast, { useToast, type PluginOptions } from 'vue-toastification'
|
import Toast, { useToast, type PluginOptions } from 'vue-toastification'
|
||||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||||
import 'vue-toastification/dist/index.css'
|
import 'vue-toastification/dist/index.css'
|
||||||
|
@ -16,7 +17,6 @@ dayjs.locale('zh-cn')
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
export const globalToast = useToast()
|
export const globalToast = useToast()
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
// ! persist 利用 localstorage,请不要在大量数据时使用
|
// ! persist 利用 localstorage,请不要在大量数据时使用
|
||||||
// 大量数据(扩张内容,文件),清直接使用 ./db.ts
|
// 大量数据(扩张内容,文件),清直接使用 ./db.ts
|
||||||
app.use(createPinia().use(persist))
|
app.use(createPinia().use(persist))
|
||||||
|
|
|
@ -6,7 +6,7 @@ import asyncLoader from '@/utils/asyncLoader'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { OhVueIcon } from 'oh-vue-icons'
|
import { OhVueIcon } from 'oh-vue-icons'
|
||||||
import { computed, defineComponent, Transition } from 'vue'
|
import { computed, defineComponent, Transition } from 'vue'
|
||||||
import SettingLineImg from '~/public/icons/settingLine.png'
|
import SettingLineImg from '~/icons/settingLine.png'
|
||||||
const Content = asyncLoader(() => import('./SettingsOverlayContent'))
|
const Content = asyncLoader(() => import('./SettingsOverlayContent'))
|
||||||
|
|
||||||
const SettingsTab = defineComponent({
|
const SettingsTab = defineComponent({
|
||||||
|
|
|
@ -2,20 +2,26 @@ import { defineComponent } from 'vue'
|
||||||
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
||||||
import { FaUserAlt } from 'oh-vue-icons/icons'
|
import { FaUserAlt } from 'oh-vue-icons/icons'
|
||||||
import useUserStore from './useUserStore'
|
import useUserStore from './useUserStore'
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
addIcons(FaUserAlt)
|
addIcons(FaUserAlt)
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const { profile } = useUserStore()
|
const store = useUserStore()
|
||||||
|
watch(() => store.profile.avatar, (e) => {
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
|
console.log('avatar change')
|
||||||
|
})
|
||||||
return () => {
|
return () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="w-full h-full rounded-full bg-black/20 flex justify-center items-center bg-center bg-no-repeat bg-cover"
|
class="w-full h-full rounded-full bg-black/20 flex justify-center items-center bg-center bg-no-repeat bg-cover"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url('${profile?.avatar}')`
|
backgroundImage: `url('${store.profile?.avatar}')`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!profile.avatar && <OhVueIcon name="fa-user-alt" fill="white" scale={1.2} />}
|
{!store.profile.avatar && <OhVueIcon name="fa-user-alt" fill="white" scale={1.2} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { defineComponent, onMounted, reactive, ref, Transition, watch } from 'vu
|
||||||
import useRouterStore from '@/useRouterStore'
|
import useRouterStore from '@/useRouterStore'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import useUserStore from './useUserStore'
|
import useUserStore from './useUserStore'
|
||||||
import Logo from '~/public/logo.png'
|
import Logo from '~//logo.png'
|
||||||
import LogoIcon from '~/public/loginIcon.png'
|
import LogoIcon from '~//loginIcon.png'
|
||||||
import LogoClose from '~/public/icons/loginClose.png'
|
import LogoClose from '~/icons/loginClose.png'
|
||||||
import { message, Spin } from 'ant-design-vue'
|
import { message, Spin } from 'ant-design-vue'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import QrCode from 'qrcode'
|
import QrCode from 'qrcode'
|
||||||
|
@ -27,11 +27,14 @@ export default defineComponent(() => {
|
||||||
watch(wxCode, (url, _, onCleanUp) => {
|
watch(wxCode, (url, _, onCleanUp) => {
|
||||||
if (!url) return
|
if (!url) return
|
||||||
const it = setInterval(() => {
|
const it = setInterval(() => {
|
||||||
request('GET', `/api/wxlogin2/access?uuid=${_id.value}`).then((res) => {
|
request<string>('GET', `/api/wxlogin2/access?uuid=${_id.value}`, {
|
||||||
|
returnType: 'text'
|
||||||
|
}).then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
clearInterval(it)
|
clearInterval(it)
|
||||||
console.log(res)
|
console.log(res)
|
||||||
|
user.token = res
|
||||||
|
router.back()
|
||||||
message.success('登录成功')
|
message.success('登录成功')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,13 +23,14 @@ export default defineStore('user', () => {
|
||||||
watch(token, (val) => {
|
watch(token, (val) => {
|
||||||
localStorage.setItem('token', val)
|
localStorage.setItem('token', val)
|
||||||
})
|
})
|
||||||
const profile = reactive(defaultUserInfo)
|
const profile = reactive({ ...defaultUserInfo })
|
||||||
watch(
|
watch(
|
||||||
token,
|
token,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return
|
if (!val) return
|
||||||
request<UserInfo>('GET', '/api/profile').then((res) => {
|
request<UserInfo>('GET', '/api/profile').then((res) => {
|
||||||
Object.assign(profile, res)
|
Object.assign(profile, res)
|
||||||
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
|
@ -37,7 +38,8 @@ export default defineStore('user', () => {
|
||||||
const isLogin = computed(() => !!token.value && !!profile.id)
|
const isLogin = computed(() => !!token.value && !!profile.id)
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
token.value = ''
|
token.value = ''
|
||||||
Object.assign(profile, defaultUserInfo)
|
Object.assign(profile, { ...defaultUserInfo })
|
||||||
|
// profile.avatar = ''
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
|
|
|
@ -35,7 +35,6 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(number, (n, old) => {
|
watch(number, (n, old) => {
|
||||||
console.log(Math.abs(n - old) / 400)
|
|
||||||
|
|
||||||
gsap.to(tweened, {
|
gsap.to(tweened, {
|
||||||
number: n,
|
number: n,
|
||||||
|
|
|
@ -4,9 +4,10 @@ import { RiTimeLine } from 'oh-vue-icons/icons'
|
||||||
import { addIcons, OhVueIcon } from 'oh-vue-icons'
|
import { addIcons, OhVueIcon } from 'oh-vue-icons'
|
||||||
import { IoCloseCircleOutline, RiCloseCircleLine } from 'oh-vue-icons/icons'
|
import { IoCloseCircleOutline, RiCloseCircleLine } from 'oh-vue-icons/icons'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import useDiscountStore from './useDiscountStore'
|
||||||
addIcons(RiTimeLine, IoCloseCircleOutline, RiCloseCircleLine)
|
addIcons(RiTimeLine, IoCloseCircleOutline, RiCloseCircleLine)
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const store = useGameNews()
|
const store = useDiscountStore()
|
||||||
const containerRef = ref<VNodeRef | null>(null as VNodeRef | null)
|
const containerRef = ref<VNodeRef | null>(null as VNodeRef | null)
|
||||||
const searchText = ref('')
|
const searchText = ref('')
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
|
@ -58,7 +59,7 @@ export default defineComponent(() => {
|
||||||
class={
|
class={
|
||||||
'flex cursor-pointer h-[215px] overflow-hidden bg-[#17212d] items-center flex-col rounded-lg relative '
|
'flex cursor-pointer h-[215px] overflow-hidden bg-[#17212d] items-center flex-col rounded-lg relative '
|
||||||
}
|
}
|
||||||
onClick={() => {}}
|
onClick={() => { }}
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<img class={'h-[142px] w-full object-cover'} src={item.commdity[0]?.img}></img>
|
<img class={'h-[142px] w-full object-cover'} src={item.commdity[0]?.img}></img>
|
||||||
|
@ -83,13 +84,13 @@ export default defineComponent(() => {
|
||||||
-13%
|
-13%
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[#fffbc2] text-[16px] ml-2">
|
<span class="text-[#fffbc2] text-[16px] ml-2">
|
||||||
¥{item.commdity[0].price}
|
¥{item.commdity[0]?.price}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-[12px] text-[#bdbdbd] line-through decoration-current">
|
<span class="text-[12px] text-[#bdbdbd] line-through decoration-current">
|
||||||
¥{item.commdity[0].oldprice}
|
¥{item.commdity[0]?.oldprice}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-[12px] text-[#ebebeb] line-through decoration-current">
|
<span class="text-[12px] text-[#ebebeb] line-through decoration-current">
|
||||||
剩余{item.commdity[0].oldprice}天
|
剩余{item.commdity[0]?.oldprice}天
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,31 +23,27 @@ export default defineStore("gameDiscount", () => {
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const noMoreData = ref(false)
|
const noMoreData = ref(false)
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
pageId: 1,
|
pageSize: 1,
|
||||||
name: '',
|
pageIndex: 1,
|
||||||
|
find: ''
|
||||||
})
|
})
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
const res = await request<{
|
const res = await request<
|
||||||
data: GameDiscount[];
|
GameDiscount[]
|
||||||
}>('POST', '/api/gameDiscount', {
|
>('GET', `/api/gameDiscount?pageSize=${state.pageSize}&pageIndex=${state.pageIndex}&find=${state.find}&type=all`)
|
||||||
data: {
|
return res
|
||||||
pageId: state.pageId,
|
|
||||||
name: state.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return res.data
|
|
||||||
}
|
}
|
||||||
const getNews = async () => {
|
const getNews = async () => {
|
||||||
|
|
||||||
if (loading.value || noMoreData.value) return;
|
if (loading.value || noMoreData.value) return;
|
||||||
loading.value = true;
|
|
||||||
try {
|
try {
|
||||||
|
loading.value = true;
|
||||||
const data = await getList()
|
const data = await getList()
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
noMoreData.value = true;
|
noMoreData.value = true;
|
||||||
} else {
|
} else {
|
||||||
list.value.push(...data);
|
list.value.push(...data);
|
||||||
state.pageId += 1;
|
state.pageIndex += 1;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
|
@ -1,9 +1,174 @@
|
||||||
|
import { frontAddress, ossBase } from '@/config'
|
||||||
|
import { DefautGameTypeList, SECRET, type GameType, type OtherGame } from '@/layout/adder/GameAdder'
|
||||||
|
import useAdderPageStore from '@/layout/adder/useAdderPageStore'
|
||||||
|
import useRouterStore from '@/useRouterStore'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import { generateRandomString } from '@/utils/tool'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { MD5 } from 'crypto-js'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { provide, ref, toRef, watch, type InjectionKey, type Ref } from 'vue'
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
|
const selectType = ref('fc')
|
||||||
|
const appList = ref<GameType[]>([])
|
||||||
|
const hoverId = ref(0)
|
||||||
|
const router = useRouterStore()
|
||||||
|
const loading = ref(false)
|
||||||
|
const fetchGame = async (page: number) => {
|
||||||
|
const parems = `nonce=${generateRandomString(8)}&pid=PIDc8uT24mpo×tamp=${dayjs().unix()}`
|
||||||
|
const sign = MD5(parems + SECRET).toString()
|
||||||
|
const response = await fetch(
|
||||||
|
`https://ge.yiqiyoo.com/game/v2/third-part/games?${parems}&sign=${sign}&paginate.limit=99&paginate.page=${page}`
|
||||||
|
)
|
||||||
|
const res = await response.json()
|
||||||
|
return res.data.items
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
selectType,
|
||||||
|
(val) => {
|
||||||
|
console.log(val);
|
||||||
|
appList.value = []
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
if (val !== 'yiqiyoo') {
|
||||||
|
request<GameType[]>('GET', `/api/games?type=${val}`)
|
||||||
|
.then((res) => {
|
||||||
|
appList.value = res
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Promise.all([fetchGame(1)]).then((res) => {
|
||||||
|
const resData = res.flat() as OtherGame[]
|
||||||
|
appList.value = resData.map((el) => ({
|
||||||
|
id: el.id.toString(),
|
||||||
|
name: el.name,
|
||||||
|
despt: el.short_description,
|
||||||
|
icon: el.icon,
|
||||||
|
rom: el.url,
|
||||||
|
playstation: el.url,
|
||||||
|
hot: el.rank,
|
||||||
|
category: el.category_ids[0].toString()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
return () => (
|
return () => (
|
||||||
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
|
<div class="w-full h-full bg-[#ecfbff] flex flex-col backdrop-blur-sm p-[8px]" style={{
|
||||||
large
|
background: 'rgba(23,33,45,.6)'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={"w-full h-full rounded-2xl backdrop-blur-lg px-4 flex flex-col"} style={{
|
||||||
|
background: 'rgba(23,33,46,.8)'
|
||||||
|
}}>
|
||||||
|
<div class={"w-full flex justify-between items-center"}>
|
||||||
|
<div class={"flex gap-x-2 h-[36px]"}>
|
||||||
|
{
|
||||||
|
DefautGameTypeList.map(item => (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
selectType.value = item.id
|
||||||
|
}}
|
||||||
|
class={clsx("flex items-center jusitfy-center relative py-4 text-[13px]",
|
||||||
|
selectType.value === item.id ? "text-[#589fff]" : " text-[#589fffcc]"
|
||||||
|
)} >
|
||||||
|
{item.type}
|
||||||
|
{
|
||||||
|
selectType.value === item.id &&
|
||||||
|
<div class={"bg-[#589fff] w-[22px] h-[4px] absolute top-0 left-1/2 -translate-x-1/2"} style={{
|
||||||
|
borderRadius: '0 0 12px 12px',
|
||||||
|
|
||||||
|
}}></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<span class={"text-[#ddd]/70 text-[14px]"} onClick={() => {
|
||||||
|
router.go('global-adder')
|
||||||
|
useAdderPageStore().type = 3
|
||||||
|
}}>查看更多</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!loading.value &&
|
||||||
|
|
||||||
|
<div class={"flex-1 h-0 w-full overflow-y-auto scrollbar-hide"}>
|
||||||
|
<div class={"w-full flex flex-col"}>
|
||||||
|
{
|
||||||
|
appList.value.filter((_, index) => index < 10).map((item, idx) => (
|
||||||
|
<div class={"flex items-center gap-x-2 pl-[8px] py-[4px] rounded-lg"}
|
||||||
|
onMouseenter={() => {
|
||||||
|
hoverId.value = idx
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
background: hoverId.value === idx ? "rgba(61,80,105,.8)" : "transparent"
|
||||||
|
}}>
|
||||||
|
<div class={clsx("w-[22px] h-[22px] bg-white/10 flex items-center justify-center rounded",
|
||||||
|
idx === 0 ? "text-red-500" : idx === 1 ? "text-orange-500" : idx === 2 ? "text-yellow-400" : "text-white"
|
||||||
|
)}>{idx + 1}</div>
|
||||||
|
<div class={"flex flex-1"}>
|
||||||
|
{
|
||||||
|
hoverId.value === idx ?
|
||||||
|
<div class={"w-full h-full flex justify-between pr-4 items-center"}>
|
||||||
|
<div class={"flex gap-x-3"}>
|
||||||
|
<img
|
||||||
|
class={"w-[36px] h-[36px] rounded "}
|
||||||
|
src={item.icon.startsWith('http')
|
||||||
|
? item.icon
|
||||||
|
: ossBase + '/' + item.icon}></img>
|
||||||
|
<div class={"flex flex-col"}>
|
||||||
|
<span class={"w-[150px] text-[13px] text-white text-ellipsis whitespace-nowrap overflow-hidden"}>{item.name}</span>
|
||||||
|
<span class={"w-[150px] text-[13px] text-white/50 text-ellipsis whitespace-nowrap overflow-hidden"}>{item.despt}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
!item.rom.startsWith('http')
|
||||||
|
? `${frontAddress}/emu/#/home?params=${JSON.stringify({
|
||||||
|
...item,
|
||||||
|
rom: ossBase + '/' + item.rom
|
||||||
|
})}`
|
||||||
|
: item.rom,
|
||||||
|
)
|
||||||
|
|
||||||
|
}}
|
||||||
|
class={"bg-[#317aff] w-[76px] h-[26px] rounded text-white text-[12px]"}>立即游玩</button>
|
||||||
|
</div> :
|
||||||
|
<div class={"w-full h-full flex gap-x-4"}>
|
||||||
|
<span class={"w-[80px] text-[13px] text-white text-ellipsis whitespace-nowrap overflow-hidden"}>{item.name}</span>
|
||||||
|
<span class={"w-[180px] text-[13px] text-white/50 text-ellipsis whitespace-nowrap overflow-hidden"}>{item.despt}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
|
|
||||||
return () => (
|
|
||||||
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
|
|
||||||
middle
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => (
|
|
||||||
<div
|
|
||||||
class="w-full h-full bg-red-50 flex "
|
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(180deg,#dcefff 0%,#e7ecff 100%)'
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => (
|
|
||||||
<div
|
|
||||||
class="w-full h-full items-center pl-3 gap-x-2 flex py-3 text-white"
|
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(135deg,#5996ff 0%,#4862ff 100%)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -3,23 +3,11 @@ import type { Widget } from '..'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'gameNews',
|
name: 'gameNews',
|
||||||
label: '游戏资讯',
|
label: '经典即玩游戏',
|
||||||
description: '游戏资讯',
|
description: '经典即玩游戏',
|
||||||
icon: '/tab/icons/game_news_icon.png',
|
icon: '/tab/icons/classicPlay.png',
|
||||||
modal: asyncLoader(() => import('./Modal')),
|
modal: null,
|
||||||
list: [
|
list: [
|
||||||
{
|
|
||||||
w: 2,
|
|
||||||
h: 1,
|
|
||||||
label: '小',
|
|
||||||
component: asyncLoader(() => import('./Small'))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
w: 2,
|
|
||||||
h: 2,
|
|
||||||
label: '中',
|
|
||||||
component: asyncLoader(() => import('./Middle'))
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
w: 4,
|
w: 4,
|
||||||
h: 2,
|
h: 2,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import request from '@/utils/request'
|
||||||
import { addIcons, OhVueIcon } from 'oh-vue-icons'
|
import { addIcons, OhVueIcon } from 'oh-vue-icons'
|
||||||
import { computed, defineComponent, onMounted, ref, watch, type CSSProperties } from 'vue'
|
import { computed, defineComponent, onMounted, ref, watch, type CSSProperties } from 'vue'
|
||||||
import { FaChevronLeft } from 'oh-vue-icons/icons'
|
import { FaChevronLeft } from 'oh-vue-icons/icons'
|
||||||
|
import PlayImg from "~/icons/game_video_bg_play.png"
|
||||||
import type { CarouselRef } from 'ant-design-vue/es/carousel'
|
import type { CarouselRef } from 'ant-design-vue/es/carousel'
|
||||||
import { randomNum } from '@/utils/tool'
|
import { randomNum } from '@/utils/tool'
|
||||||
addIcons(FaChevronLeft)
|
addIcons(FaChevronLeft)
|
||||||
|
@ -54,7 +55,9 @@ export default defineComponent(() => {
|
||||||
}, 7000)
|
}, 7000)
|
||||||
})
|
})
|
||||||
return () => (
|
return () => (
|
||||||
<div class="w-full h-full p-2 bg-[#17212d] ">
|
<div class="w-full h-full p-2 bg-[#17212d] relative ">
|
||||||
|
<img src={PlayImg} class={"absolute z-10 w-[40px] bg-[#ffffff24] rounded-lg backdrop-blur-sm left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"}></img>
|
||||||
|
|
||||||
{
|
{
|
||||||
<div
|
<div
|
||||||
class={'w-full h-full rounded-xl relative group'}
|
class={'w-full h-full rounded-xl relative group'}
|
||||||
|
@ -65,6 +68,7 @@ export default defineComponent(() => {
|
||||||
backgroundRepeat: 'no-repeat'
|
backgroundRepeat: 'no-repeat'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
'absolute bottom-0 left-1/2 -translate-x-1/2 pb-2 w-[300px] flex flex-col text-white '
|
'absolute bottom-0 left-1/2 -translate-x-1/2 pb-2 w-[300px] flex flex-col text-white '
|
||||||
|
|
|
@ -9,6 +9,8 @@ import hotspot from './hotspot'
|
||||||
import constellation from './constellation'
|
import constellation from './constellation'
|
||||||
import gameVideo from './gameVideo'
|
import gameVideo from './gameVideo'
|
||||||
import work from './work'
|
import work from './work'
|
||||||
|
import game from './game'
|
||||||
|
import notepad from './notepad'
|
||||||
export interface Widget {
|
export interface Widget {
|
||||||
name: string // 小组件类型唯一标识
|
name: string // 小组件类型唯一标识
|
||||||
label: string // 小组件名称
|
label: string // 小组件名称
|
||||||
|
@ -23,4 +25,4 @@ export interface Widget {
|
||||||
}[] // 不同尺寸小组件块
|
}[] // 不同尺寸小组件块
|
||||||
}
|
}
|
||||||
|
|
||||||
export default [calendar, weather, weApply, gameNews, eat, discount, hotspot, constellation, gameVideo, work] as Widget[]
|
export default [game, calendar, weather, weApply, gameNews, eat, discount, hotspot, constellation, gameVideo, work, notepad] as Widget[]
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import useNotepadStore from './useNotepadStore'
|
||||||
|
|
||||||
|
export default defineComponent(() => {
|
||||||
|
const store = useNotepadStore()
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class="w-full h-full bg-[#fff] flex ">
|
||||||
|
<div class={"w-1/2 h-full flex items-center justify-center flex-col gap-y-1 relative"}>
|
||||||
|
<img class={'w-[52px] h-[52px]'} src={'/tab/icons/note_pad_icon.png'}></img>
|
||||||
|
<span>记事本</span>
|
||||||
|
<img src="/tab/icons/notepad/note_pad_line.png" class={"absolute w-[25px] right-2 top-1/2 -translate-y-1/2"} alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="w-0 flex-1 h-full bg-[#fff] flex flex-col px-4 pb-0 pt-5 gap-y-2">
|
||||||
|
<div>
|
||||||
|
<span class={"text-[14px] text-[#333] pb-1 border-b-[1px] border-b-[#e5daca]"}>欢迎使用记事本</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
store.state.list.filter((_, idx) => idx < 4).map(item => (
|
||||||
|
<div class={"w-full pb-[1px] border-b-[1px] border-b-[#e5daca]"}>
|
||||||
|
<div class={"w-[130px] text-[12px] text-[#656565] text-ellipsis overflow-hidden whitespace-nowrap"}>{item.title || '新建笔记'}</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import useNotepadStore from './useNotepadStore'
|
||||||
|
|
||||||
|
export default defineComponent(() => {
|
||||||
|
const store = useNotepadStore()
|
||||||
|
return () => (
|
||||||
|
<div class="w-full h-full bg-[#fff] flex flex-col px-4 pb-0 pt-5 gap-y-2">
|
||||||
|
<div>
|
||||||
|
<span class={"text-[14px] text-[#333] pb-1 border-b-[1px] border-b-[#e5daca]"}>欢迎使用记事本</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
store.state.list.filter((_, idx) => idx < 4).map(item => (
|
||||||
|
<div class={"w-full pb-[1px] border-b-[1px] border-b-[#e5daca]"}>
|
||||||
|
<div class={"w-[130px] text-[12px] text-[#656565] text-ellipsis overflow-hidden whitespace-nowrap"}>{item.title || '新建笔记'}</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { addIcons, OhVueIcon } from 'oh-vue-icons'
|
||||||
|
import { computed, defineComponent, onMounted, ref } from 'vue'
|
||||||
|
import decorativeImg from "~/icons/notepad/decorative.png"
|
||||||
|
import { HiSearch } from "oh-vue-icons/icons";
|
||||||
|
import useNotepadStore from './useNotepadStore';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import Editor from './editor';
|
||||||
|
import "@milkdown/theme-nord/style.css"
|
||||||
|
import "@milkdown-lab/plugin-menu/style.css"
|
||||||
|
import { MilkdownProvider } from '@milkdown/vue';
|
||||||
|
addIcons(HiSearch)
|
||||||
|
export default defineComponent(() => {
|
||||||
|
const store = useNotepadStore()
|
||||||
|
const selectId = ref(store.state.list[0]?.id || '')
|
||||||
|
const trriger = ref(true)
|
||||||
|
const searchWorks = ref('')
|
||||||
|
const currentIndex = computed(() => {
|
||||||
|
trriger.value = false
|
||||||
|
setTimeout(() => {
|
||||||
|
trriger.value = true
|
||||||
|
}, 0)
|
||||||
|
return store.state.list.findIndex(val => val.id === selectId.value)
|
||||||
|
})
|
||||||
|
const showList = computed(() => {
|
||||||
|
return store.state.list.map(val => val).sort(a => a.pin ? -1 : 1)
|
||||||
|
})
|
||||||
|
return () => (
|
||||||
|
<div
|
||||||
|
class="w-full h-full bg-white flex p-4 "
|
||||||
|
|
||||||
|
>
|
||||||
|
<div class={"w-[227px] h-full flex flex-col gap-y-3"}>
|
||||||
|
<div class={"flex gap-x-2"}>
|
||||||
|
<div class={"flex-1 w-0 relative "}>
|
||||||
|
<input type="text"
|
||||||
|
value={searchWorks.value}
|
||||||
|
onInput={(e: any) => {
|
||||||
|
searchWorks.value = e.target.value
|
||||||
|
}}
|
||||||
|
placeholder='输入内容'
|
||||||
|
class={"w-full outline-none h-full pl-2 pr-8 border-[1px] border-[#ddd] rounded-lg text-[#333] placeholder:text-[#C5C5C5]"} />
|
||||||
|
<OhVueIcon name={HiSearch.name} class={" absolute right-2 top-1/2 -translate-y-1/2"} fill='#C5C5C5'></OhVueIcon>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
store.addNewNote()
|
||||||
|
selectId.value = store.state.list[0]?.id
|
||||||
|
}}
|
||||||
|
class={"w-[50px] hover:opacity-90 h-[30px] flex items-center justify-center bg-[#b59e86] rounded text-white"}>新建</button>
|
||||||
|
</div>
|
||||||
|
<div class={"flex-1 h-0 w-full pt-2 "}>
|
||||||
|
<div class={"w-full flex flex-col gap-y-2 "}>
|
||||||
|
{
|
||||||
|
showList.value.filter(val => val.content.includes(searchWorks.value)).map((item) => (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
selectId.value = item.id
|
||||||
|
}}
|
||||||
|
class={clsx("flex justify-between w-full cursor-pointer h-[66px] p-2 pl-3 rounded-lg group ",
|
||||||
|
selectId.value === item.id ? "bg-[#c7ad9666]" : "bg-[#bdab9b33]"
|
||||||
|
)}>
|
||||||
|
<div class={"flex flex-col justify-between text-[14px]"}>
|
||||||
|
<span class={"text-[#333]"}>{item.title || '新建笔记'}</span>
|
||||||
|
<span class={"text-[#656565]"}>{dayjs(item.date).format('YYYY-MM-DD')}</span>
|
||||||
|
</div>
|
||||||
|
<div class={"flex flex-col justify-between"}>
|
||||||
|
<img
|
||||||
|
onClick={() => {
|
||||||
|
item.pin = !item.pin
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
src='/tab/icons/notepad/top.png' alt='top' class={clsx("w-[22px]", item.pin ? "" : "hidden opacity-70 group-hover:block")}></img>
|
||||||
|
<img
|
||||||
|
onClick={() => {
|
||||||
|
const idx = store.state.list.findIndex(val => val.id === item.id)
|
||||||
|
if (idx !== -1) {
|
||||||
|
store.state.list.splice(idx, 1)
|
||||||
|
// if (index === showList.value.length - 1) {
|
||||||
|
// selectId.value = showList.value[index - 2]?.id || ''
|
||||||
|
// } else {
|
||||||
|
// selectId.value = showList.value[index + 1]?.id || ''
|
||||||
|
// }
|
||||||
|
// selectId.value = showList.value[index - 1 > 0 ? index - 1 : (showList.value.length - 1)]?.id || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
||||||
|
src='/tab/icons/notepad/delete.png' alt='delete' class={"hidden group-hover:block w-[22px] opacity-70"}></img>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={"w-0 flex-1 h-full pt-2"}>
|
||||||
|
<div class={"w-full h-full flex justify-between pl-5 gap-x-5"}>
|
||||||
|
<span class={"absolute opacity-0 prose"}></span>
|
||||||
|
<img src={decorativeImg} alt='decorativeImg' class={"h-full"}></img>
|
||||||
|
<div class={"flex-1 w-0 h-full flex flex-col"}>
|
||||||
|
<input
|
||||||
|
maxlength={20}
|
||||||
|
onInput={(e: any) => {
|
||||||
|
if (store.state.list[currentIndex.value]) {
|
||||||
|
store.state.list[currentIndex.value].title = e.target.value
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="text" class={"w-full border-b-[1px] pb-[3px] border-b-[#bdab9b] outline-none "}
|
||||||
|
value={store.state.list[currentIndex.value]?.title || ''} />
|
||||||
|
<div class={"flex flex-1 h-0 rounded-lg mt-3 w-full"}>
|
||||||
|
{
|
||||||
|
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]) {
|
||||||
|
store.state.list[currentIndex.value].content = e
|
||||||
|
}
|
||||||
|
}}></Editor>
|
||||||
|
</MilkdownProvider>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent(() => {
|
||||||
|
return () => (
|
||||||
|
<div
|
||||||
|
class="w-full h-full flex items-center px-3 bg-white"
|
||||||
|
>
|
||||||
|
<img class={'w-[52px] h-[52px]'} src={'/tab/icons/note_pad_icon.png'}></img>
|
||||||
|
<div class={'flex-1 flex justify-center'}>
|
||||||
|
<div class="flex-col flex">
|
||||||
|
<span class={'text-[16px] text-[#AE9680]'}>记事本</span>
|
||||||
|
<div class={'flex items-center text-[#AE9680] text-[12px] '}>
|
||||||
|
立即创建
|
||||||
|
<div class={"opacity-50 ml-[2px] scale-80"}>
|
||||||
|
》
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,72 @@
|
||||||
|
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 { menu, menuConfigCtx, type MenuConfigItem } from '@milkdown-lab/plugin-menu'
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
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 />
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import asyncLoader from '@/utils/asyncLoader'
|
||||||
|
import type { Widget } from '..'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'notepad',
|
||||||
|
label: '记事本',
|
||||||
|
description: '记事本',
|
||||||
|
icon: '/tab/icons/note_pad_icon.png',
|
||||||
|
modal: asyncLoader(() => import('./Modal')),
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
w: 2,
|
||||||
|
h: 1,
|
||||||
|
label: '小',
|
||||||
|
component: asyncLoader(() => import('./Small'))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
w: 2,
|
||||||
|
h: 2,
|
||||||
|
label: '中',
|
||||||
|
component: asyncLoader(() => import('./Middle'))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
w: 4,
|
||||||
|
h: 2,
|
||||||
|
label: '大',
|
||||||
|
component: asyncLoader(() => import('./Large'))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Widget
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
export const DEFALUT_DATA = {
|
||||||
|
content: `
|
||||||
|
欢迎来到FatfoxTab新标签页平台!在这里您将发现海量有趣的内容和实用的工具。
|
||||||
|
|
||||||
|
|
||||||
|
为了让您有更好的体验,我们为您提供了这个新手引导。接下来,让我们一起了解如何更好的使用我们的平台。
|
||||||
|
|
||||||
|
## **1. 功能模块**
|
||||||
|
|
||||||
|
我们的产品有多个功能模块,每个模块都有不同的功能。以下是一些常用的功能模块:
|
||||||
|
|
||||||
|
|
||||||
|
编辑主页:右键点击桌面空白处可快速编辑主页 添加网址导航及功能组件,更换壁纸等。
|
||||||
|
|
||||||
|
左侧栏添加:左侧栏下方点击【添加】即可编辑自己常用分类工具栏。
|
||||||
|
|
||||||
|
消息反馈:可以在反馈模块中查看您的收件箱和发件箱,及对产品提出的改进建议及BUG反馈。
|
||||||
|
|
||||||
|
设置:在左侧栏下方设置内均可以对页面展示样式进行个性化调整。
|
||||||
|
|
||||||
|
快捷键设置:鼠标拉动网址导航-放置对应快捷键-点击对应快捷键既可跳转网址。
|
||||||
|
|
||||||
|
页面底部快捷介绍:为了符合用户的体验操作,产品设置了快捷键点击跳转网页。
|
||||||
|
|
||||||
|
|
||||||
|
## **2. 模式切换**
|
||||||
|
|
||||||
|
FatfoxTab新标签页共分为四个模式;
|
||||||
|
|
||||||
|
工作/游戏/轻娱/极简
|
||||||
|
|
||||||
|
鼠标移入左边栏左上角模式切换,可自行切换自己所需要的模式。
|
||||||
|
|
||||||
|
|
||||||
|
## **3. 工作模式**
|
||||||
|
|
||||||
|
【番茄工作法】
|
||||||
|
|
||||||
|
新建目标:点击新建目标即添加今日待完成事项,开始计时后每15分钟为一个目标周期进行倒计时展示。
|
||||||
|
|
||||||
|
目标列表:目标列表展示您今日所创建的待完成事项,完成后点击前方按钮即可进行划线标注完成。
|
||||||
|
|
||||||
|
查看日程:以日历的形式记录您所创建的所有任务。
|
||||||
|
|
||||||
|
数据概况:以折线图的的形式记录近段时间的专注时长。
|
||||||
|
|
||||||
|
音乐播放:开始计时后点击音乐模块的播放键即可播放。
|
||||||
|
|
||||||
|
|
||||||
|
## **4. 游戏模式**
|
||||||
|
|
||||||
|
专为游戏爱好者提供了PC游戏、主机游戏、手游等相关内容-最新的游戏资讯、最全的游戏折扣、最热门的游戏视频,给您最好的游戏体验。
|
||||||
|
|
||||||
|
|
||||||
|
## **5. AI助手**
|
||||||
|
|
||||||
|
免费使用:每天30次免费聊天,让您免费接触前沿生产力。
|
||||||
|
|
||||||
|
结果增强:通过小助手,增强您的搜索结果,让您获得更多精准搜索结果。
|
||||||
|
|
||||||
|
指令助手:预设超多的AI对话指令模板,对话交流,适用不同需求。
|
||||||
|
|
||||||
|
一键访问最佳大语言模型。提出任何问题,实时获得准确、简洁的回答。感受终极人工智能体验。希望打开FatfoxTab新标签页后 每天都有好心情哦~
|
||||||
|
|
||||||
|
|
||||||
|
## **6. 注册/登录**
|
||||||
|
|
||||||
|
鼠标点击左侧栏左下角,点击人物头像,微信扫一扫或邮箱登录。
|
||||||
|
|
||||||
|
|
||||||
|
## **7. 自定义功能**
|
||||||
|
|
||||||
|
1.自定义新标签页中的小组件和图标,支持随意拖拽布置,任意搭配。
|
||||||
|
|
||||||
|
2.快速添加常用网站或小组件到主页。
|
||||||
|
|
||||||
|
3.自定义搜索引擎,快捷切换百度/谷歌/必应等搜索引擎。
|
||||||
|
|
||||||
|
4.自定义壁纸,海量高清壁纸任你选择,包括自然、海洋、建筑、动物等类别。
|
||||||
|
|
||||||
|
5.自定义左侧栏,支持图标分类。办公娱乐更加得心应手,可设置左侧栏隐藏或显示。
|
||||||
|
|
||||||
|
6.自定义底部栏,收纳常用网站,一键进入更方便,可设置底部栏隐藏或显示。
|
||||||
|
|
||||||
|
7.登录/注册FatfoxTab账号,可在不同设备备份、同步你的数据。
|
||||||
|
|
||||||
|
8.你可以接收到我们向你发送的消息通知。
|
||||||
|
|
||||||
|
9.工作模式-番茄工作法专属轻音乐。
|
||||||
|
|
||||||
|
10.支持组件尺寸调整,可调大、中、小 三种模式。
|
||||||
|
|
||||||
|
11.系统状态小组件,实时显示CPU、内存的使用情况。
|
||||||
|
|
||||||
|
12.游戏折扣、热门游戏、星座运势等小组件。
|
||||||
|
|
||||||
|
13.自定义静态动态壁纸,可下载可设置毛玻璃效果。
|
||||||
|
|
||||||
|
|
||||||
|
更多新功能正在加速开发中,敬请期待...
|
||||||
|
|
||||||
|
`,
|
||||||
|
title: "FatFoxTab指南",
|
||||||
|
date: 1730877843004,
|
||||||
|
id: "defautId"
|
||||||
|
};
|
||||||
|
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[]
|
||||||
|
})
|
||||||
|
const addNewNote = () => {
|
||||||
|
state.list.unshift({
|
||||||
|
id: uuid(),
|
||||||
|
title: '',
|
||||||
|
date: dayjs().valueOf(),
|
||||||
|
content: '',
|
||||||
|
pin: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
addNewNote
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: true
|
||||||
|
})
|
|
@ -1,9 +1,84 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
import { formatSeconds } from '@/utils/tool'
|
||||||
|
import PlayStartImg from "~/icons/work/start.png"
|
||||||
|
import returnImg from "~/icons/work/return.png"
|
||||||
|
import endImg from "~/icons/work/tomotoIconEnd.png"
|
||||||
|
import PlusImg from "~/icons/work/tomatoIconAdd.png"
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { Tooltip } from 'ant-design-vue'
|
||||||
|
import useTomatoStore from './useTomatoStore'
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
|
const store = useTomatoStore()
|
||||||
return () => (
|
return () => (
|
||||||
<div class="w-full h-full bg-[#ecfbff] flex flex-col">
|
<div class="w-full h-full flex relative p-6 justify-between" style={{
|
||||||
|
background: `url('https://newfatfox.oss-cn-beijing.aliyuncs.com/admin/tomotoback.png')`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundRepeat: 'no-repeat'
|
||||||
|
}}>
|
||||||
|
<div class={"bg-[#0000004d] absolute top-0 left-0 right-0 bottom-0 "}></div>
|
||||||
|
<div class={"w-[115px] h-full flex flex-col items-center z-10 text-white"}>
|
||||||
|
<div class={"w-full bg-white/20 text-center rounded text-[14px]"}>无目标</div>
|
||||||
|
<span class={"text-[42px] mb-1"}>
|
||||||
|
{
|
||||||
|
store.state.beginTime < 0 ? '15:00' : formatSeconds(store.remainingTime)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class={"text-[14px]"}>你今日已完成
|
||||||
|
<span class={"text-[#76e6ff] mx-1"}>
|
||||||
|
|
||||||
|
|
||||||
|
{store.state.timeList.filter(val => dayjs(val).isSame(dayjs(), 'day')).length}</span>
|
||||||
|
</span>
|
||||||
|
<span class={"text-[14px]"}>个番茄时</span>
|
||||||
|
</div>
|
||||||
|
<div class={"flex flex-col justify-end"}>
|
||||||
|
<div class={"flex gap-x-3 "}>
|
||||||
|
<Tooltip title={"沉浸模式"}>
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
store.openFullscreen = true
|
||||||
|
}}
|
||||||
|
class={"w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl"}>
|
||||||
|
<img src={returnImg} alt="start" class={"w-[18px]"}></img>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={"开始"}>
|
||||||
|
<div class={"w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl"}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (store.state.isStart) {
|
||||||
|
store.stopTomatoTime()
|
||||||
|
} else {
|
||||||
|
store.beginTomatoTime()
|
||||||
|
store.openFullscreen = true
|
||||||
|
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
store.state.isStart ?
|
||||||
|
<img src={endImg} alt="start" class={"w-[18px]"}></img>
|
||||||
|
:
|
||||||
|
<img src={PlayStartImg} alt="start" class={"w-[18px]"}></img>
|
||||||
|
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={"添加目标"}>
|
||||||
|
<div class={"w-[42px] h-[42px] bg-white/40 backdrop-blur-md flex items-center justify-center rounded-xl"}
|
||||||
|
onClick={() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
store.openShowModel = null
|
||||||
|
|
||||||
|
}, 300)
|
||||||
|
}}>
|
||||||
|
<img src={PlusImg} alt="start" class={"w-[18px]"}></img>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { defineComponent, ref } from 'vue'
|
import { defineComponent, ref } from 'vue'
|
||||||
import PlusIcon from '~/public/icons/work/tomato_work_add_icon.png'
|
import PlusIcon from '~/icons/work/tomato_work_add_icon.png'
|
||||||
import DataImg from '~/public/icons/work/dataImg.png'
|
import DataImg from '~/icons/work/dataImg.png'
|
||||||
import DateImg from '~/public/icons/work/dateImg.png'
|
import DateImg from '~/icons/work/dateImg.png'
|
||||||
import ListImg from '~/public/icons/work/listImg.png'
|
import ListImg from '~/icons/work/listImg.png'
|
||||||
import AddImg from '~/public/icons/work/addTarget.png'
|
import AddImg from '~/icons/work/addTarget.png'
|
||||||
import DownImg from "~/public/icons/work/selectDown1.0.3.png"
|
import DownImg from "~/icons/work/selectDown1.0.3.png"
|
||||||
import { v4 as uuid } from "uuid"
|
import { v4 as uuid } from "uuid"
|
||||||
import List from './modal_view/list'
|
import List from './modal_view/list'
|
||||||
import { DatePicker, Modal, TimePicker } from 'ant-design-vue'
|
import { DatePicker, Modal, TimePicker } from 'ant-design-vue'
|
||||||
|
@ -13,6 +13,7 @@ import type { TomatoTarget } from './useTomatoStore'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import useTomatoStore from './useTomatoStore'
|
import useTomatoStore from './useTomatoStore'
|
||||||
import Calendar from './modal_view/calendar'
|
import Calendar from './modal_view/calendar'
|
||||||
|
import DataDetail from './modal_view/DataDetail'
|
||||||
const workTab = [
|
const workTab = [
|
||||||
{
|
{
|
||||||
title: '目标列表',
|
title: '目标列表',
|
||||||
|
@ -235,7 +236,7 @@ export default defineComponent(() => {
|
||||||
) : select.value === 1 ? (
|
) : select.value === 1 ? (
|
||||||
<Calendar />
|
<Calendar />
|
||||||
) : select.value === 2 ? (
|
) : select.value === 2 ? (
|
||||||
<div>数据详情</div>
|
<DataDetail />
|
||||||
) : (
|
) : (
|
||||||
<>loading</>
|
<>loading</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,12 +3,21 @@ import { defineComponent } from 'vue'
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class="w-full h-full items-center pl-3 gap-x-2 flex py-3 text-white"
|
class="w-full h-full flex items-center px-3 bg-white"
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(135deg,#5996ff 0%,#4862ff 100%)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<img class={'w-[48px] h-[48px]'} src={'/tab/icons/work/tomato_work_icon.png'}></img>
|
||||||
|
<div class={'flex-1 flex justify-center'}>
|
||||||
|
<div class="flex-col flex">
|
||||||
|
<span class={'text-[16px] text-[#333]'}>番茄时</span>
|
||||||
|
<div class={'flex items-center text-[#999] text-[12px] '}>
|
||||||
|
立即使用
|
||||||
|
<div>
|
||||||
|
<img class={"w-[7px] h-[6px] ml-1"} src="/tab/icons/work/tomoto_gt.png"></img>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,23 +2,24 @@ import asyncLoader from '@/utils/asyncLoader'
|
||||||
import type { Widget } from '..'
|
import type { Widget } from '..'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'work',
|
name: 'tomato_work',
|
||||||
label: '番茄工作法',
|
label: '番茄工作法',
|
||||||
description: '番茄记事法',
|
description: '番茄记事法',
|
||||||
icon: '/tab/icons/work/tomato_work_icon.png',
|
icon: '/tab/icons/work/tomato_work_icon.png',
|
||||||
modal: asyncLoader(() => import('./Modal')),
|
modal: asyncLoader(() => import('./Modal')),
|
||||||
list: [
|
list: [
|
||||||
|
{
|
||||||
|
w: 4,
|
||||||
|
h: 2,
|
||||||
|
label: '大',
|
||||||
|
component: asyncLoader(() => import('./Large'))
|
||||||
|
},
|
||||||
{
|
{
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 1,
|
h: 1,
|
||||||
label: '小',
|
label: '小',
|
||||||
component: asyncLoader(() => import('./Small'))
|
component: asyncLoader(() => import('./Small'))
|
||||||
},
|
},
|
||||||
{
|
|
||||||
w: 4,
|
|
||||||
h: 2,
|
|
||||||
label: '大',
|
|
||||||
component: asyncLoader(() => import('./Large'))
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
} as Widget
|
} as Widget
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { computed, defineComponent, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||||
|
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import { LineChart, PieChart, } from 'echarts/charts';
|
||||||
|
// 引入柱状图图表,图表后缀都为 Chart
|
||||||
|
import { BarChart } from 'echarts/charts';
|
||||||
|
// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
DatasetComponent,
|
||||||
|
TransformComponent
|
||||||
|
} from 'echarts/components';
|
||||||
|
// 标签自动布局、全局过渡动画等特性
|
||||||
|
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||||
|
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import useTomatoStore from "../useTomatoStore";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import gsap from 'gsap'
|
||||||
|
import { Progress } from "ant-design-vue";
|
||||||
|
|
||||||
|
// 注册必须的组件
|
||||||
|
echarts.use([
|
||||||
|
PieChart,
|
||||||
|
TitleComponent,
|
||||||
|
LineChart,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
DatasetComponent,
|
||||||
|
TransformComponent,
|
||||||
|
BarChart,
|
||||||
|
LabelLayout,
|
||||||
|
UniversalTransition,
|
||||||
|
CanvasRenderer
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 接下来的使用就跟之前一样,初始化图表,设置配置项
|
||||||
|
|
||||||
|
export default defineComponent(() => {
|
||||||
|
const chart = ref(null);
|
||||||
|
const leftChat = ref(null)
|
||||||
|
const store = useTomatoStore()
|
||||||
|
const precent1 = reactive({
|
||||||
|
number: 0
|
||||||
|
})
|
||||||
|
const precent2 = reactive({
|
||||||
|
number: 0
|
||||||
|
})
|
||||||
|
let myChart: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
myChart = echarts.init(chart.value);
|
||||||
|
const option = {
|
||||||
|
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const dataPoint = params[0];
|
||||||
|
const timeInMinutes = dataPoint.value;
|
||||||
|
// 使用 HTML 标签格式化 tooltip 内容
|
||||||
|
return `${dataPoint.name} <br/> <strong class="text-blue-500">${timeInMinutes} </strong>分钟`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: Array.from({ length: 7 }).map((_, idx) => (dayjs().subtract(idx, 'day').format('MM月DD日'))).reverse(),
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
data: Array.from({ length: 7 }).map((_, idx) => (dayjs().subtract(idx, 'day').format('MM月DD日')))
|
||||||
|
.reverse()
|
||||||
|
.map(item => store.state.timeList.reduce((pre, cur) => {
|
||||||
|
if (dayjs(cur).isSame(dayjs(item, "MM月DD日"), 'day')) {
|
||||||
|
return pre + 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}, 0) * 15),
|
||||||
|
lineStyle: {
|
||||||
|
color: '#C876FB', // 线条的颜色
|
||||||
|
width: 2, // 线条宽度
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#FF8688', // 数据点的颜色
|
||||||
|
},
|
||||||
|
showSymbol: false, // 默认不显示数据点
|
||||||
|
// hover 时显示数据点
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
color: '#FF8688', // 悬停时数据点的颜色
|
||||||
|
|
||||||
|
},
|
||||||
|
showSymbol: true, // 悬停时显示数据点
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', myChart?.resize as any);
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
}, 1000)
|
||||||
|
gsap.to(precent1, { duration: 1, number: Number(100) || 0 })
|
||||||
|
gsap.to(precent2, { duration: 1, number: Number(100) || 0 })
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', myChart?.resize as any);
|
||||||
|
myChart?.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class={"w-full h-full flex flex-col px-4 pb-3 gap-y-1"}>
|
||||||
|
<div class={"h-[144px] mt-[44px] flex justify-between"}>
|
||||||
|
<div class={"w-[360px] h-full flex justify-end items-center pr-10 relative"} style={{
|
||||||
|
background: 'linear-gradient(180deg,#F4FAFF 0%,#FFFFFF 100%)',
|
||||||
|
boxShadow: '0 2px 12px #0000001a',
|
||||||
|
borderRadius: '46px 8px 8px'
|
||||||
|
}}>
|
||||||
|
<div class={" left-5 -top-10 absolute bg-white rounded-full"} style={{
|
||||||
|
width: '174px',
|
||||||
|
height: '174px'
|
||||||
|
}}>
|
||||||
|
|
||||||
|
<Progress type="circle"
|
||||||
|
percent={precent1.number}
|
||||||
|
showInfo={false}
|
||||||
|
size={174}
|
||||||
|
strokeWidth={10}
|
||||||
|
strokeColor={{
|
||||||
|
'25%': '#76E05F',
|
||||||
|
'75%': '#F8E14F',
|
||||||
|
}}></Progress>
|
||||||
|
<div class={"w-[65%] bg-[#F6F6F6] shadow rounded-full h-[65%] items-center justify-center absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"}>
|
||||||
|
<span class={"text-[#666]"}>相比昨天</span>
|
||||||
|
<span class={"font-bold text-[#333] text-[30px]"}>{(store.todayHour - store.yestodayHour) >= 0 ? "+" : ''}{(store.todayHour - store.yestodayHour) * 100}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={"flex flex-col items-center"}>
|
||||||
|
<span class={"text-[#333] text-[16px]"}>今日专注时长</span>
|
||||||
|
<span class={"text-[#5a6eff] font-extrabold text-[36px]"}> {(store.todayHour * 0.15).toFixed(2)}
|
||||||
|
<span class={"text-[20px]"}>h</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={"w-[360px] h-full relative flex items-center justify-end pr-5"} style={{
|
||||||
|
background: 'linear-gradient(180deg,#F4FAFF 0%,#FFFFFF 100%)',
|
||||||
|
boxShadow: '0 2px 12px #0000001a',
|
||||||
|
borderRadius: '46px 8px 8px'
|
||||||
|
}}>
|
||||||
|
<div class={" left-5 -top-10 absolute bg-white rounded-full"} style={{
|
||||||
|
width: '174px',
|
||||||
|
height: '174px'
|
||||||
|
}}>
|
||||||
|
|
||||||
|
<Progress type="circle"
|
||||||
|
percent={precent2.number}
|
||||||
|
showInfo={false}
|
||||||
|
size={174}
|
||||||
|
strokeWidth={10}
|
||||||
|
strokeColor={{
|
||||||
|
'25%': '#DB63FB',
|
||||||
|
'75%': '#977BFB',
|
||||||
|
}}></Progress>
|
||||||
|
<div class={"w-[65%] bg-[#F6F6F6] shadow rounded-full h-[65%] items-center justify-center absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"}>
|
||||||
|
<span class={"text-[#666]"}>相比昨天</span>
|
||||||
|
<span class={"font-bold text-[#333] text-[30px]"}>{(store.todayFinishTarget - store.yesFinishTarget) >= 0 ? "+" : ''}{(store.todayFinishTarget - store.yesFinishTarget) * 100}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={"flex flex-col items-center"}>
|
||||||
|
<span class={"text-[#333] text-[16px]"}>今日完成目标数</span>
|
||||||
|
<span class={"text-[#5a6eff] font-extrabold text-[36px]"}>{store.todayFinishTarget}
|
||||||
|
<span class={"text-[20px]"}>个</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={"flex-1 h-0 w-full p-1 flex flex-col rounded-lg items-start justify-between relative"} style={{
|
||||||
|
boxShadow: '0 2px 12px #0000001a'
|
||||||
|
}}>
|
||||||
|
<span class={" absolute top-2 left-5 text-[#666666]"}>专注时长历史趋势</span>
|
||||||
|
<div class={"h-[750px] relative w-full"}>
|
||||||
|
<div ref={chart} class={"w-full h-full"} ></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<span class={"absolute bottom-3 left-5 text-[#9d6dff]"}>您的趋势向上,为了美好的明天,继续努力吧!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCalendarStore } from "@/widgets/calendar/useCalendarStore";
|
import { useCalendarStore } from "@/widgets/calendar/useCalendarStore";
|
||||||
import { DatePicker } from "ant-design-vue";
|
import { DatePicker } from "ant-design-vue";
|
||||||
import DownImg from "~/public/icons/work/selectDown1.0.3.png"
|
import DownImg from "~/icons/work/selectDown1.0.3.png"
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { defineComponent, ref } from "vue";
|
import { defineComponent, ref } from "vue";
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => (
|
|
||||||
<div class={"w-full h-full"}>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, defineComponent, ref } from 'vue'
|
||||||
import useTomatoStore from '../useTomatoStore'
|
import useTomatoStore from '../useTomatoStore'
|
||||||
import NoDataImg from "~/public/icons/work/noData.png"
|
import NoDataImg from "~/icons/work/noData.png"
|
||||||
import StopImg from "~/public/icons/work/tomotoIconEnd.png"
|
import StopImg from "~/icons/work/tomotoIconEnd.png"
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import PlayImg from "~/public/icons/work/work_page-play.png"
|
import PlayImg from "~/icons/work/work_page-play.png"
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const store = useTomatoStore()
|
const store = useTomatoStore()
|
||||||
const searchText = ref('')
|
const searchText = ref('')
|
||||||
|
@ -86,7 +86,9 @@ export default defineComponent(() => {
|
||||||
<div class={"relative h-[120px] w-full py-1"}>
|
<div class={"relative h-[120px] w-full py-1"}>
|
||||||
<div class={"w-full h-full rounded-lg border-[1px] border-black/20 justify-between flex items-center px-4"}>
|
<div class={"w-full h-full rounded-lg border-[1px] border-black/20 justify-between flex items-center px-4"}>
|
||||||
<div class={"flex flex-col text-[16px] gap-y-2"}>
|
<div class={"flex flex-col text-[16px] gap-y-2"}>
|
||||||
<span class={" tracking-wide"}>你今日已完成 <span class={"text-[#5b47ff]"}>{store.state.list.filter(val => dayjs(val.finishTime).isSame(dayjs(), 'day') && val.isCompleted).length}</span>个番茄时</span>
|
<span class={" tracking-wide"}>你今日已完成<span class={"text-[#5b47ff] mx-1"}>
|
||||||
|
{store.state.timeList.filter(val => dayjs(val).isSame(dayjs(), 'day')).length}
|
||||||
|
</span>个番茄时</span>
|
||||||
<span class={"text-[#ff8686]"}>不积跬步无以至千里、千里之行始于足下。</span>
|
<span class={"text-[#ff8686]"}>不积跬步无以至千里、千里之行始于足下。</span>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
|
@ -99,7 +101,6 @@ export default defineComponent(() => {
|
||||||
store.stopTomatoTime()
|
store.stopTomatoTime()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<img src={StopImg} alt="play img " class={"w-[13px]"} />
|
<img src={StopImg} alt="play img " class={"w-[13px]"} />
|
||||||
停止计时
|
停止计时
|
||||||
</button> :
|
</button> :
|
||||||
|
|
|
@ -42,78 +42,97 @@ export const musicList = [
|
||||||
export default defineStore("work", () => {
|
export default defineStore("work", () => {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
list: [] as TomatoTarget[],
|
list: [] as TomatoTarget[],
|
||||||
timeList: [] as TomatoTime[],
|
timeList: [] as number[],
|
||||||
isPlaying: false as boolean,
|
isPlaying: false as boolean,
|
||||||
selectMusic: 0,
|
selectMusic: 0,
|
||||||
isStart: false as boolean,
|
isStart: false as boolean,
|
||||||
beginTime: -1 as number
|
beginTime: -1 as number,
|
||||||
|
})
|
||||||
|
const audio = new Audio()
|
||||||
|
const time = useTimeStore()
|
||||||
|
const remainingTime = computed(() => {
|
||||||
|
if (!state.isStart) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return dayjs(state.beginTime).add(1, 'minute').diff(dayjs(time.date), 'second')
|
||||||
})
|
})
|
||||||
|
|
||||||
const time = useTimeStore()
|
|
||||||
const beginTomatoTime = () => {
|
|
||||||
state.beginTime = dayjs().valueOf()
|
|
||||||
state.isStart = true
|
|
||||||
playMusic()
|
|
||||||
}
|
|
||||||
const stopTomatoTime = () => {
|
const stopTomatoTime = () => {
|
||||||
state.isStart = false
|
state.isStart = false
|
||||||
state.beginTime = -1
|
state.beginTime = -1
|
||||||
stopMusic()
|
stopMusic()
|
||||||
}
|
if (remainingTime.value <= 0) {
|
||||||
const remainingTime = computed(() => {
|
state.timeList.push(
|
||||||
const totalTime = TOTAL_TIME
|
dayjs().valueOf()
|
||||||
|
)
|
||||||
return dayjs(state.beginTime).add(15, 'minute').diff(dayjs(time.date), 'second')
|
|
||||||
})
|
|
||||||
const playAudio = computed(() => {
|
|
||||||
const audio = new Audio(musicList[state.selectMusic].music)
|
|
||||||
return audio
|
|
||||||
})
|
|
||||||
watch(() => remainingTime, (val) => {
|
|
||||||
if (val.value < 0) {
|
|
||||||
state.isPlaying = false
|
|
||||||
state.isStart = false
|
|
||||||
state.beginTime = -1
|
|
||||||
state.timeList.push({
|
|
||||||
date: dayjs().valueOf(),
|
|
||||||
finishTime: TOTAL_TIME
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
// watch(() => state.isPlaying, (val) => {
|
|
||||||
// if (val) {
|
|
||||||
// playAudio.value.play()
|
|
||||||
// } else {
|
|
||||||
// playAudio.value.pause()
|
|
||||||
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
const playMusic = () => {
|
|
||||||
state.isPlaying = true
|
|
||||||
playAudio.value.play()
|
|
||||||
}
|
}
|
||||||
|
const togglePlay = () => {
|
||||||
const pauseMusic = () => {
|
if (state.isPlaying) {
|
||||||
state.isPlaying = false
|
audio.pause()
|
||||||
playAudio.value.pause()
|
} else {
|
||||||
|
audio.play()
|
||||||
|
}
|
||||||
|
state.isPlaying = !state.isPlaying
|
||||||
|
}
|
||||||
|
const setTrack = (trackIndex: number) => {
|
||||||
|
state.selectMusic = trackIndex
|
||||||
|
audio.src = musicList[trackIndex].music
|
||||||
|
audio.loop = true
|
||||||
|
audio.load()
|
||||||
|
if (state.isPlaying) {
|
||||||
|
audio.play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const stopMusic = () => {
|
const stopMusic = () => {
|
||||||
|
audio.pause()
|
||||||
|
audio.currentTime = 0
|
||||||
state.isPlaying = false
|
state.isPlaying = false
|
||||||
playAudio.value.pause()
|
|
||||||
playAudio.value.currentTime = 0
|
|
||||||
}
|
}
|
||||||
|
const beginTomatoTime = () => {
|
||||||
|
state.beginTime = dayjs().valueOf()
|
||||||
|
state.isStart = true
|
||||||
|
setTrack(state.selectMusic)
|
||||||
|
togglePlay()
|
||||||
|
|
||||||
|
}
|
||||||
|
watch(remainingTime, (val) => {
|
||||||
|
if (val <= 0) {
|
||||||
|
stopTomatoTime()
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
const openShowModel = ref<undefined | null | TomatoTarget>()
|
const openShowModel = ref<undefined | null | TomatoTarget>()
|
||||||
const openFullscreen = ref(false)
|
const openFullscreen = ref(false)
|
||||||
|
const todayHour = computed(() => {
|
||||||
|
return state.timeList.filter(val => dayjs(val).isSame(dayjs(), 'day')).length
|
||||||
|
})
|
||||||
|
const yestodayHour = computed(() => {
|
||||||
|
return state.timeList.filter(val => dayjs(val).isSame(dayjs().subtract(1, 'day'), 'day')).length
|
||||||
|
})
|
||||||
|
const todayFinishTarget = computed(() => {
|
||||||
|
return state.list.filter(val => dayjs(val.finishTime).isSame(dayjs(), 'day') && val.isCompleted).length
|
||||||
|
})
|
||||||
|
const yesFinishTarget = computed(() => {
|
||||||
|
return state.list.filter(val => dayjs(val.finishTime).isSame(dayjs().subtract(1, 'day'), 'day') && val.isCompleted).length
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
openShowModel,
|
openShowModel,
|
||||||
openFullscreen,
|
openFullscreen,
|
||||||
beginTomatoTime,
|
beginTomatoTime,
|
||||||
remainingTime,
|
remainingTime,
|
||||||
playMusic,
|
togglePlay,
|
||||||
pauseMusic,
|
|
||||||
stopMusic,
|
stopMusic,
|
||||||
stopTomatoTime
|
stopTomatoTime,
|
||||||
|
todayHour,
|
||||||
|
yestodayHour,
|
||||||
|
todayFinishTarget,
|
||||||
|
yesFinishTarget,
|
||||||
|
setTrack
|
||||||
}
|
}
|
||||||
})
|
}, {
|
||||||
|
persist: true
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
/* Copyright 2021, Milkdown by Mirone. */
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [require('@tailwindcss/typography')]
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
|
||||||
darkMode: 'selector',
|
|
||||||
theme: {
|
|
||||||
fontFamily: {
|
|
||||||
'din': 'DIN-Black'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,7 +31,7 @@ export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
'~': fileURLToPath(new URL('./', import.meta.url))
|
'~': fileURLToPath(new URL('./public', import.meta.url))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|