Compare commits
No commits in common. "bd2d533813787405b44cccdd3a248d6e1cc3e192" and "9e760ddfb25e69a6c764114b847fdb435a60a7c1" have entirely different histories.
bd2d533813
...
9e760ddfb2
11
index.html
11
index.html
|
|
@ -1,12 +1,9 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
||||||
/>
|
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB |
|
|
@ -7,8 +7,6 @@ import SettingsButton from './settings/SettingsButton'
|
||||||
import SettingsOverlay from './settings/SettingsOverlay'
|
import SettingsOverlay from './settings/SettingsOverlay'
|
||||||
import Sider from './layout/sider'
|
import Sider from './layout/sider'
|
||||||
import Dock from './layout/dock'
|
import Dock from './layout/dock'
|
||||||
import DirModal from './layout/grid/DirModal'
|
|
||||||
import GlobalMenu from './layout/GlobalMenu'
|
|
||||||
import LoginModal from './user/LoginModal'
|
import LoginModal from './user/LoginModal'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import asyncLoader from './utils/asyncLoader'
|
import asyncLoader from './utils/asyncLoader'
|
||||||
|
|
@ -36,8 +34,6 @@ const layout = useLayoutStore()
|
||||||
<div class="fixed z-40 right-[14%] top-8">
|
<div class="fixed z-40 right-[14%] top-8">
|
||||||
<Fox />
|
<Fox />
|
||||||
</div>
|
</div>
|
||||||
<DirModal />
|
|
||||||
<GlobalMenu />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
import { computed, defineComponent, onUnmounted, reactive } from 'vue'
|
|
||||||
import type { Block } from './layout.types'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import useLayoutStore from './useLayoutStore'
|
|
||||||
|
|
||||||
const defaultDisplay = {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
type: 'global' as Block | 'global' | 'dock'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useMenuStore = defineStore('menu', () => {
|
|
||||||
const display = reactive(defaultDisplay)
|
|
||||||
const mPos = {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
const handle = (e: MouseEvent) => {
|
|
||||||
mPos.x = e.pageX
|
|
||||||
mPos.y = e.pageY
|
|
||||||
}
|
|
||||||
window.addEventListener('mousemove', handle)
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('mousemove', handle)
|
|
||||||
})
|
|
||||||
const open = (type: (typeof defaultDisplay)['type']) => {
|
|
||||||
display.x = Math.min(window.innerWidth - 140, mPos.x)
|
|
||||||
display.y = Math.min(window.innerHeight - 240, mPos.y)
|
|
||||||
display.type = type
|
|
||||||
}
|
|
||||||
const show = computed(() => display.x > 0 && display.y > 0)
|
|
||||||
const dismiss = () => {
|
|
||||||
display.x = 0
|
|
||||||
display.y = 0
|
|
||||||
display.type = 'global'
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
display,
|
|
||||||
open,
|
|
||||||
dismiss,
|
|
||||||
show
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const Item = defineComponent({
|
|
||||||
props: {
|
|
||||||
alert: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits: {
|
|
||||||
click: () => true
|
|
||||||
},
|
|
||||||
setup(props, ctx) {
|
|
||||||
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',
|
|
||||||
{
|
|
||||||
'bg-red-500/80 hover:bg-red-500 text-white': props.alert,
|
|
||||||
'bg-white/80 hover:bg-white text-black/80': !props.alert
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
ctx.emit('click')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ctx.slots.default?.()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
const menu = useMenuStore()
|
|
||||||
const layout = useLayoutStore()
|
|
||||||
return () =>
|
|
||||||
menu.show && (
|
|
||||||
<div
|
|
||||||
v-outside-click={() => {
|
|
||||||
console.log(2333)
|
|
||||||
menu.dismiss()
|
|
||||||
}}
|
|
||||||
class="fixed px-2 pt-4 pb-2 bg-white/60 backdrop-blur shadow-lg rounded-lg overflow-hidden w-[120px]"
|
|
||||||
style={{
|
|
||||||
zIndex: '999',
|
|
||||||
left: menu.display.x + 'px',
|
|
||||||
top: menu.display.y + 'px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(() => {
|
|
||||||
if (menu.display.type === 'global') {
|
|
||||||
// 全局菜单
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
if (menu.display.type === 'dock') {
|
|
||||||
// dock 栏
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
const block = menu.display.type
|
|
||||||
if (!block.link) {
|
|
||||||
// 小组件
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Item
|
|
||||||
alert
|
|
||||||
onClick={() => {
|
|
||||||
// 删除链接
|
|
||||||
const idx = layout.currentPage.list.findIndex((el) => el.id === block.id)
|
|
||||||
if (idx < 0) return
|
|
||||||
layout.currentPage.list.splice(idx, 1)
|
|
||||||
menu.dismiss()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Item>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (block.link.startsWith('id:')) {
|
|
||||||
// 文件夹
|
|
||||||
const id = block.link.slice(3)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Item
|
|
||||||
onClick={() => {
|
|
||||||
block.w = 1
|
|
||||||
block.h = 1
|
|
||||||
menu.dismiss()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
小
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
onClick={() => {
|
|
||||||
block.w = 2
|
|
||||||
block.h = 2
|
|
||||||
menu.dismiss()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
大
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
alert
|
|
||||||
onClick={() => {
|
|
||||||
// 删除文件夹
|
|
||||||
const idx = layout.currentPage.list.findIndex((el) => el.id === block.id)
|
|
||||||
if (idx < 0) return
|
|
||||||
layout.currentPage.list.splice(idx, 1)
|
|
||||||
delete layout.state.dir[id]
|
|
||||||
menu.dismiss()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Item>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 链接
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Item>测试</Item>
|
|
||||||
<Item
|
|
||||||
alert
|
|
||||||
onClick={() => {
|
|
||||||
// 删除链接
|
|
||||||
let idx = layout.currentPage.list.findIndex((el) => el.id === block.id)
|
|
||||||
if (idx < 0) {
|
|
||||||
// 查找文件夹
|
|
||||||
idx = layout.state.dir[layout.openDir]?.list?.findIndex(
|
|
||||||
(el) => el.id === block.id
|
|
||||||
)
|
|
||||||
if (idx === undefined || idx < 0) {
|
|
||||||
// 查找 dock
|
|
||||||
idx = layout.state.dock.findIndex((el) => el?.id === block.id)
|
|
||||||
if (idx >= 0) {
|
|
||||||
layout.state.dock[idx] = null
|
|
||||||
menu.dismiss()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const list = layout.state.dir[layout.openDir].list
|
|
||||||
list.splice(idx, 1)
|
|
||||||
layout.checkDir(layout.openDir)
|
|
||||||
menu.dismiss()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
layout.currentPage.list.splice(idx, 1)
|
|
||||||
menu.dismiss()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Item>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { computed, defineComponent, ref, Transition } from 'vue'
|
||||||
computed,
|
|
||||||
defineComponent,
|
|
||||||
provide,
|
|
||||||
ref,
|
|
||||||
Transition,
|
|
||||||
type InjectionKey,
|
|
||||||
type Ref
|
|
||||||
} from 'vue'
|
|
||||||
import AdderPageBack from './AdderPageBack'
|
import AdderPageBack from './AdderPageBack'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
||||||
|
|
@ -14,8 +6,6 @@ import { MdKeyboardcommandkey, FaCompass, FaPencilRuler } from 'oh-vue-icons/ico
|
||||||
import CustomAdder from './CustomAdder'
|
import CustomAdder from './CustomAdder'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import ThemeProvider from '@/utils/ThemeProvider'
|
import ThemeProvider from '@/utils/ThemeProvider'
|
||||||
import WidgetAdder from './WidgetAdder'
|
|
||||||
import { Form, Input, Select } from 'ant-design-vue'
|
|
||||||
addIcons(MdKeyboardcommandkey, FaCompass, FaPencilRuler)
|
addIcons(MdKeyboardcommandkey, FaCompass, FaPencilRuler)
|
||||||
|
|
||||||
const ItemButton = defineComponent({
|
const ItemButton = defineComponent({
|
||||||
|
|
@ -60,14 +50,10 @@ const ItemButton = defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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 type = ref(1)
|
||||||
const addTo = ref(layout.state.currentPage)
|
|
||||||
provide(AddToToken, addTo)
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class={clsx('w-full h-full relative flex', isGame.value && 'bg-[#2c2e3e]')}>
|
<div class={clsx('w-full h-full relative flex', isGame.value && 'bg-[#2c2e3e]')}>
|
||||||
<ThemeProvider dark={isGame.value}>
|
<ThemeProvider dark={isGame.value}>
|
||||||
|
|
@ -110,25 +96,11 @@ export default defineComponent(() => {
|
||||||
}
|
}
|
||||||
onContextmenu={(e) => e.stopPropagation()}
|
onContextmenu={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Form class="w-full px-4 mt-4" layout="inline">
|
<div class="w-full h-[60px]"></div>
|
||||||
<Form.Item label="添加到">
|
|
||||||
<Select
|
|
||||||
style="width: 140px"
|
|
||||||
v-model:value={addTo.value}
|
|
||||||
options={layout.currentMode.pages.map((el, idx) => ({
|
|
||||||
label: el.label,
|
|
||||||
value: idx
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item>
|
|
||||||
<Input.Search class="w-[200px]" placeholder="搜索组件或网站" />
|
|
||||||
</Form.Item>
|
|
||||||
</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">
|
||||||
<Transition>
|
<Transition>
|
||||||
{type.value === 0 ? <WidgetAdder /> : type.value === 1 ? '' : <CustomAdder />}
|
{type.value === 0 ? '' : type.value === 1 ? '' : <CustomAdder />}
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { computed, defineComponent, inject, reactive, ref, watch } from 'vue'
|
import { computed, defineComponent, reactive, ref, watch } from 'vue'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import { Button, Form, Input, InputGroup } from 'ant-design-vue'
|
import { Button, Form, Input, InputGroup } from 'ant-design-vue'
|
||||||
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
||||||
|
|
@ -12,7 +12,6 @@ import { ColorPicker } from 'vue3-colorpicker'
|
||||||
import 'vue3-colorpicker/style.css'
|
import 'vue3-colorpicker/style.css'
|
||||||
import { globalToast } from '@/main'
|
import { globalToast } from '@/main'
|
||||||
import UploadAndCut from '@/utils/UploadAndCut'
|
import UploadAndCut from '@/utils/UploadAndCut'
|
||||||
import { AddToToken } from './AdderPage'
|
|
||||||
|
|
||||||
addIcons(MdUpload, MdImage, MdCheck)
|
addIcons(MdUpload, MdImage, MdCheck)
|
||||||
|
|
||||||
|
|
@ -156,7 +155,6 @@ export default defineComponent(() => {
|
||||||
if (val.name) form.name = val.name
|
if (val.name) form.name = val.name
|
||||||
if (val.icon) form.icon = val.icon
|
if (val.icon) form.icon = val.icon
|
||||||
})
|
})
|
||||||
const addTo = inject(AddToToken)
|
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
|
|
@ -249,7 +247,7 @@ export default defineComponent(() => {
|
||||||
form.type === 0 ? '' : form.text || form.name.substring(0, 2).toLocaleUpperCase(),
|
form.type === 0 ? '' : form.text || form.name.substring(0, 2).toLocaleUpperCase(),
|
||||||
label: form.name
|
label: form.name
|
||||||
}
|
}
|
||||||
layout.addBlock(data, addTo?.value)
|
layout.addBlock(data)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
确定
|
确定
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
import { computed, defineComponent } from 'vue'
|
|
||||||
import useLayoutStore from '../useLayoutStore'
|
|
||||||
import widgetList, { type Widget } from '@/widgets'
|
|
||||||
import { Button } from 'ant-design-vue'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
|
|
||||||
export const Item = defineComponent({
|
|
||||||
props: {
|
|
||||||
content: {
|
|
||||||
type: Object as () => Widget,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const layout = useLayoutStore()
|
|
||||||
const isGame = computed(() => layout.state.current === 0)
|
|
||||||
return () => (
|
|
||||||
<div
|
|
||||||
class={clsx('bg-white w-full h-full rounded-lg shadow p-4', {
|
|
||||||
'bg-white/20': isGame.value,
|
|
||||||
'bg-white/80': !isGame.value
|
|
||||||
})}
|
|
||||||
key={props.content.name}
|
|
||||||
>
|
|
||||||
<div class="flex">
|
|
||||||
<img src={props.content.icon} class="w-[48px] h-[48px] bg-cover" />
|
|
||||||
<div class="px-2 w-0 flex-grow">
|
|
||||||
<div
|
|
||||||
class={clsx('text text-sm', {
|
|
||||||
'text-white': isGame.value,
|
|
||||||
'text-black/80': !isGame.value
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{props.content.label}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class={clsx('text-[12px]', {
|
|
||||||
'text-white/80': isGame.value,
|
|
||||||
'text-black/60': !isGame.value
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{props.content.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<Button size="small" type="primary">
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => (
|
|
||||||
<div class="absolute left-0 top-0 w-full h-full overflow-y-auto gap-4">
|
|
||||||
<div
|
|
||||||
class="w-full grid grid-cols-3 grid-flow-row-dense pb-[200px]"
|
|
||||||
style="grid-auto-rows: 100px"
|
|
||||||
>
|
|
||||||
{widgetList.map((el) => (
|
|
||||||
<Item content={el} key={el.name} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
@ -1,41 +1,27 @@
|
||||||
import { computed, defineComponent, ref, toRaw } from 'vue'
|
import { computed, defineComponent } from 'vue'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import useSortable, { dragging, resetDragging } from '../grid/useSortable'
|
import useSortable, { dragging } from '../grid/useSortable'
|
||||||
import LinkBlock from '../grid/LinkBlock'
|
import LinkBlock from '../grid/LinkBlock'
|
||||||
|
|
||||||
export default defineComponent(() => {
|
export default defineComponent(() => {
|
||||||
const layout = useLayoutStore()
|
const layout = useLayoutStore()
|
||||||
const container = useSortable(
|
const container = useSortable(
|
||||||
computed(() => layout.state.dock),
|
computed(() => layout.state.dock),
|
||||||
ref('dock')
|
'dock'
|
||||||
)
|
)
|
||||||
const current = ref(-1)
|
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class="fixed bottom-4 left-1/2 -translate-x-1/2 p-4 rounded-lg bg-white/60 backdrop-blur flex gap-4 shadow-lg"
|
class="fixed bottom-4 left-1/2 -translate-x-1/2 p-4 rounded-lg bg-white/60 backdrop-blur flex gap-4 shadow-lg"
|
||||||
ref={container}
|
ref={container}
|
||||||
onMouseleave={() => {
|
|
||||||
current.value = -1
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{layout.state.dockLabels.split('').map((l, i) => {
|
{layout.state.dockLabels.split('').map((l, i) => {
|
||||||
const block = layout.state.dock[i]
|
const block = layout.state.dock[i]
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={'block-' + i + (block?.id || '')}
|
key={'block-' + i + (block?.id || '')}
|
||||||
class="w-[54px] h-[54px] rounded-lg overflow-hidden relative cursor-pointer transition-all"
|
class="w-[54px] h-[54px] rounded-lg overflow-hidden relative cursor-pointer"
|
||||||
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})`
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
id={block?.id || ''}
|
id={block?.id || ''}
|
||||||
onMousemove={() => {
|
|
||||||
current.value = i
|
|
||||||
}}
|
|
||||||
onDragover={(e) => e.preventDefault()}
|
onDragover={(e) => e.preventDefault()}
|
||||||
onDrop={() => {
|
onDrop={() => {
|
||||||
// 处理移入(当前有内容不可移入)
|
// 处理移入(当前有内容不可移入)
|
||||||
|
|
@ -45,19 +31,13 @@ export default defineComponent(() => {
|
||||||
if (oldIdx < 0) return
|
if (oldIdx < 0) return
|
||||||
const block = layout.currentPage.list[oldIdx]
|
const block = layout.currentPage.list[oldIdx]
|
||||||
if (!block) return
|
if (!block) return
|
||||||
layout.state.dock[i] = toRaw(block)
|
layout.state.dock[i] = block
|
||||||
layout.currentPage.list.splice(oldIdx, 1)
|
layout.currentPage.list.splice(oldIdx, 1)
|
||||||
resetDragging()
|
return
|
||||||
} else if (dragging.type && dragging.type !== 'dock') {
|
}
|
||||||
const list = layout.state.dir[dragging.type]?.list
|
if (dragging.type && dragging.type !== 'dock') {
|
||||||
if (!list) return
|
// TODO: 文件夹
|
||||||
const idx = list.findIndex((el) => el.id === dragging.id)
|
return
|
||||||
if (idx < 0) return
|
|
||||||
const block = list[idx]
|
|
||||||
layout.state.dock[i] = toRaw(block)
|
|
||||||
list.splice(idx, 1)
|
|
||||||
layout.checkDir(dragging.type)
|
|
||||||
resetDragging()
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import { defineComponent, ref, toRaw } from 'vue'
|
|
||||||
import type { Block } from '../layout.types'
|
|
||||||
import { dragging, resetDragging } from './useSortable'
|
|
||||||
import { v4 as uuid } from 'uuid'
|
|
||||||
import useLayoutStore from '../useLayoutStore'
|
|
||||||
import LinkBlock from './LinkBlock'
|
|
||||||
import DirBlock from './DirBlock'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as () => Block,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
idx: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const layout = useLayoutStore()
|
|
||||||
let it = 0
|
|
||||||
const hover = ref(false)
|
|
||||||
return () => (
|
|
||||||
<div
|
|
||||||
class="w-full h-full p-[var(--block-padding)] relative rounded-lg"
|
|
||||||
key={props.block.id}
|
|
||||||
id={props.block.id}
|
|
||||||
style={{
|
|
||||||
gridColumn: `span ${props.block.w}`,
|
|
||||||
gridRow: `span ${props.block.h}`,
|
|
||||||
transition: 'border .3s',
|
|
||||||
border: hover.value ? '2px solid rgba(255,255,255,.5)' : '2px solid rgba(255,255,255,0)'
|
|
||||||
}}
|
|
||||||
data-transportable={props.block.link && !props.block.link.startsWith('id:') ? '1' : ''}
|
|
||||||
onDragover={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
clearTimeout(it)
|
|
||||||
hover.value = true
|
|
||||||
it = setTimeout(() => {
|
|
||||||
hover.value = false
|
|
||||||
}, 300)
|
|
||||||
}}
|
|
||||||
onDrop={() => {
|
|
||||||
// 处理移入
|
|
||||||
if (!dragging.id) return
|
|
||||||
if (dragging.type === 'dock') {
|
|
||||||
const oldIdx = layout.state.dock.findIndex((el) => el?.id === dragging.id)
|
|
||||||
if (oldIdx < 0) return
|
|
||||||
const block = layout.state.dock[oldIdx]
|
|
||||||
if (!block) return
|
|
||||||
layout.currentPage.list.splice(props.idx, 0, block)
|
|
||||||
layout.state.dock[oldIdx] = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (dragging.type === 'page' && dragging.id !== props.block.id) {
|
|
||||||
// 合并为文件夹
|
|
||||||
const link = props.block.link
|
|
||||||
// 小组件无法合并
|
|
||||||
if (!link) return
|
|
||||||
const oldIdx = layout.currentPage.list.findIndex((el) => el.id === dragging.id)
|
|
||||||
if (oldIdx < 0) return
|
|
||||||
const oldBlock = layout.currentPage.list[oldIdx]
|
|
||||||
// 文件夹不能移入文件夹
|
|
||||||
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))
|
|
||||||
layout.currentPage.list.splice(oldIdx, 1)
|
|
||||||
resetDragging()
|
|
||||||
} else {
|
|
||||||
// 本身不是文件夹
|
|
||||||
const id = props.block.id
|
|
||||||
layout.state.dir[id] = {
|
|
||||||
label: props.block.label,
|
|
||||||
list: [toRaw(props.block), toRaw(oldBlock)]
|
|
||||||
}
|
|
||||||
layout.currentPage.list.splice(props.idx, 1)
|
|
||||||
layout.currentPage.list.splice(props.idx, 0, {
|
|
||||||
id: uuid(),
|
|
||||||
link: `id:${id}`,
|
|
||||||
name: '',
|
|
||||||
label: '新建文件夹',
|
|
||||||
icon: '',
|
|
||||||
text: '',
|
|
||||||
background: '',
|
|
||||||
color: '',
|
|
||||||
w: 1,
|
|
||||||
h: 1
|
|
||||||
})
|
|
||||||
layout.currentPage.list.splice(oldIdx, 1)
|
|
||||||
resetDragging()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dragging.type && dragging.type !== 'dock' && dragging.type !== 'page') {
|
|
||||||
const list = layout.state.dir[dragging.type]?.list
|
|
||||||
if (!list) return
|
|
||||||
const idx = list.findIndex((el) => el.id === dragging.id)
|
|
||||||
if (idx < 0) return
|
|
||||||
const block = list[idx]
|
|
||||||
layout.currentPage.list.splice(props.idx, 0, toRaw(block))
|
|
||||||
list.splice(idx, 1)
|
|
||||||
layout.checkDir(dragging.type)
|
|
||||||
resetDragging()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="w-full h-full overflow-hidden relative cursor-pointer shadow-lg"
|
|
||||||
style={{
|
|
||||||
borderRadius: `calc(var(--block-radius) * var(--block-size))`
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.block.link ? (
|
|
||||||
props.block.link.startsWith('id:') ? (
|
|
||||||
// 文件夹
|
|
||||||
<DirBlock block={props.block} big={props.block.w !== 1 || props.block.h !== 1} />
|
|
||||||
) : (
|
|
||||||
// 链接
|
|
||||||
<LinkBlock block={props.block} />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
// 小组件
|
|
||||||
<div></div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="absolute left-0 -bottom-2 text-sm text-white text-center w-full overflow-hidden text-ellipsis whitespace-nowrap break-all font-bold"
|
|
||||||
style="text-shadow: 0 0 4px rgba(0,0,0,.6)"
|
|
||||||
>
|
|
||||||
{layout.getLabel(props.block)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { computed, defineComponent } from 'vue'
|
|
||||||
import useLayoutStore from '../useLayoutStore'
|
|
||||||
import type { Block } from '../layout.types'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import LinkBlock from './LinkBlock'
|
|
||||||
import { useMenuStore } from '../GlobalMenu'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
block: {
|
|
||||||
type: Object as () => Block,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
big: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const layout = useLayoutStore()
|
|
||||||
const selectedDir = computed(() => {
|
|
||||||
const link = props.block.link
|
|
||||||
const id = link.slice(3)
|
|
||||||
const dir = layout.state.dir[id]
|
|
||||||
return (
|
|
||||||
dir || {
|
|
||||||
label: '',
|
|
||||||
list: []
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const menu = useMenuStore()
|
|
||||||
return () => (
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
layout.openDir = props.block.link.slice(3)
|
|
||||||
}}
|
|
||||||
class={clsx('w-full h-full bg-white/60 backdrop-blur grid', {
|
|
||||||
'grid-rows-3 grid-cols-3 gap-[6%] p-[8%]': props.big,
|
|
||||||
'grid-rows-2 grid-cols-2 gap-[8%] p-[10%]': !props.big
|
|
||||||
})}
|
|
||||||
onContextmenu={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
menu.open(props.block)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import { computed, defineComponent, ref, Transition, watch } from 'vue'
|
|
||||||
import useLayoutStore from '../useLayoutStore'
|
|
||||||
import useSortable from './useSortable'
|
|
||||||
import LinkBlock from './LinkBlock'
|
|
||||||
import type { Block } from '../layout.types'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
const layout = useLayoutStore()
|
|
||||||
const dir = ref({ label: '', list: [] as Block[] })
|
|
||||||
// 因为有拖拽,关闭弹框不能使内容消失
|
|
||||||
watch(
|
|
||||||
() => layout.state.dir[layout.openDir],
|
|
||||||
(val) => {
|
|
||||||
if (val) dir.value = val
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const container = useSortable(
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute left-0 top-0 w-full h-full bg-black/40 backdrop-blur"
|
|
||||||
onClick={() => {
|
|
||||||
layout.openDir = ''
|
|
||||||
}}
|
|
||||||
onDragenter={() => {
|
|
||||||
layout.openDir = ''
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<input
|
|
||||||
class="relative h-[40px] mb-2 w-[240px] bg-transparent outline-none border-none text-center text-white"
|
|
||||||
v-model={dir.value.label}
|
|
||||||
/>
|
|
||||||
<div class="relative w-[50%] min-h-[280px] max-h-[60vh] overflow-y-auto bg-white/60 backdrop-blur rounded-lg shadow-lg p-2">
|
|
||||||
<div
|
|
||||||
class="w-full grid justify-center grid-flow-row-dense pb-[200px]"
|
|
||||||
style="grid-template-columns:repeat(auto-fill, var(--block-size));grid-auto-rows:var(--block-size)"
|
|
||||||
ref={container}
|
|
||||||
>
|
|
||||||
{dir.value.list.map((block) => (
|
|
||||||
<div
|
|
||||||
class="w-full h-full p-[var(--block-padding)] relative rounded-lg"
|
|
||||||
key={block.id}
|
|
||||||
id={block.id}
|
|
||||||
data-transportable="1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="w-full h-full overflow-hidden relative cursor-pointer shadow-lg"
|
|
||||||
style={{
|
|
||||||
borderRadius: `calc(var(--block-radius) * var(--block-size))`
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
@ -1,35 +1,25 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import type { Block } from '../layout.types'
|
import type { Block } from '../layout.types'
|
||||||
import { useMenuStore } from '../GlobalMenu'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
block: {
|
block: {
|
||||||
type: Object as () => Block,
|
type: Object as () => Block,
|
||||||
required: true
|
required: true
|
||||||
},
|
|
||||||
brief: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const menu = useMenuStore()
|
|
||||||
return () => (
|
return () => (
|
||||||
<div
|
<div
|
||||||
class="w-full h-full flex justify-center items-center font-bold bg-cover bg-center bg-no-repeat"
|
class="w-full h-full flex justify-center items-center font-bold bg-cover bg-center bg-no-repeat"
|
||||||
onContextmenu={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
menu.open(props.block)
|
|
||||||
}}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: props.block.background || 'white',
|
backgroundColor: props.block.background || 'white',
|
||||||
color: props.block.color || 'black',
|
color: props.block.color || 'black',
|
||||||
backgroundImage: props.block.icon ? `url('${props.block.icon}')` : '',
|
backgroundImage: props.block.icon ? `url('${props.block.icon}')` : '',
|
||||||
fontSize: props.brief ? '12px' : 'calc(var(--block-size) / 5)'
|
fontSize: 'calc(var(--block-size) / 4.4)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{props.brief ? props.block.text[0] : props.block.text}</div>
|
<div>{props.block.text}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { computed, defineComponent, ref, toRaw } from 'vue'
|
import { computed, defineComponent, TransitionGroup } from 'vue'
|
||||||
import useLayoutStore from '../useLayoutStore'
|
import useLayoutStore from '../useLayoutStore'
|
||||||
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
||||||
import { MdAdd } from 'oh-vue-icons/icons'
|
import { MdAdd } from 'oh-vue-icons/icons'
|
||||||
import useRouterStore from '@/useRouterStore'
|
import useRouterStore from '@/useRouterStore'
|
||||||
import { globalToast } from '@/main'
|
import { globalToast } from '@/main'
|
||||||
import useSortable, { dragging, resetDragging } from './useSortable'
|
import LinkBlock from './LinkBlock'
|
||||||
import BlockWrapper from './BlockWrapper'
|
import useSortable, { dragging } from './useSortable'
|
||||||
|
|
||||||
addIcons(MdAdd)
|
addIcons(MdAdd)
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default defineComponent(() => {
|
||||||
const router = useRouterStore()
|
const router = useRouterStore()
|
||||||
const container = useSortable(
|
const container = useSortable(
|
||||||
computed(() => layout.currentPage.list),
|
computed(() => layout.currentPage.list),
|
||||||
ref('page')
|
'page'
|
||||||
)
|
)
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
|
|
@ -31,26 +31,20 @@ export default defineComponent(() => {
|
||||||
}}
|
}}
|
||||||
onDragover={(e) => e.preventDefault()}
|
onDragover={(e) => e.preventDefault()}
|
||||||
onDrop={() => {
|
onDrop={() => {
|
||||||
// 处理移入
|
// 处理移入(当前有内容不可移入)
|
||||||
if (!dragging.id) return
|
if (!dragging.id) return
|
||||||
if (dragging.type === 'dock') {
|
if (dragging.type === 'dock') {
|
||||||
const oldIdx = layout.state.dock.findIndex((el) => el?.id === dragging.id)
|
const oldIdx = layout.state.dock.findIndex((el) => el?.id === dragging.id)
|
||||||
if (oldIdx < 0) return
|
if (oldIdx < 0) return
|
||||||
const block = layout.state.dock[oldIdx]
|
const block = layout.state.dock[oldIdx]
|
||||||
if (!block) return
|
if (!block) return
|
||||||
layout.currentPage.list.push(toRaw(block))
|
layout.currentPage.list.push(block)
|
||||||
layout.state.dock[oldIdx] = null
|
layout.state.dock[oldIdx] = null
|
||||||
resetDragging()
|
return
|
||||||
} else if (dragging.type && dragging.type !== 'dock') {
|
}
|
||||||
const list = layout.state.dir[dragging.type]?.list
|
if (dragging.type && dragging.type !== 'dock') {
|
||||||
if (!list) return
|
// TODO: 文件夹
|
||||||
const idx = list.findIndex((el) => el.id === dragging.id)
|
return
|
||||||
if (idx < 0) return
|
|
||||||
const block = list[idx]
|
|
||||||
layout.currentPage.list.push(toRaw(block))
|
|
||||||
list.splice(idx, 1)
|
|
||||||
layout.checkDir(dragging.type)
|
|
||||||
resetDragging()
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -59,12 +53,65 @@ export default defineComponent(() => {
|
||||||
style="grid-template-columns:repeat(auto-fill, var(--block-size));grid-auto-rows:var(--block-size)"
|
style="grid-template-columns:repeat(auto-fill, var(--block-size));grid-auto-rows:var(--block-size)"
|
||||||
ref={container}
|
ref={container}
|
||||||
>
|
>
|
||||||
{layout.currentPage.list.map((block, idx) => (
|
<TransitionGroup>
|
||||||
<BlockWrapper key={block.id} idx={idx} block={block} />
|
{layout.currentPage.list.map((block, idx) => (
|
||||||
))}
|
<div
|
||||||
|
class="w-full h-full p-[var(--block-padding)] relative"
|
||||||
|
key={block.id}
|
||||||
|
id={block.id}
|
||||||
|
style={{
|
||||||
|
gridColumn: `span ${block.w}`,
|
||||||
|
gridRow: `span ${block.h}`
|
||||||
|
}}
|
||||||
|
data-transportable={block.link && !block.link.startsWith('id:') ? '1' : ''}
|
||||||
|
onDragover={(e) => e.preventDefault()}
|
||||||
|
onDrop={() => {
|
||||||
|
// 处理移入(当前有内容不可移入)
|
||||||
|
if (!dragging.id) return
|
||||||
|
if (dragging.type === 'dock') {
|
||||||
|
const oldIdx = layout.state.dock.findIndex((el) => el?.id === dragging.id)
|
||||||
|
if (oldIdx < 0) return
|
||||||
|
const block = layout.state.dock[oldIdx]
|
||||||
|
if (!block) return
|
||||||
|
layout.currentPage.list.splice(idx, 0, block)
|
||||||
|
layout.state.dock[oldIdx] = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (dragging.type && dragging.type !== 'dock') {
|
||||||
|
// TODO: 文件夹
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-full h-full overflow-hidden relative cursor-pointer shadow-lg"
|
||||||
|
style="border-radius:calc(var(--block-radius) * var(--block-size))"
|
||||||
|
>
|
||||||
|
{block.link ? (
|
||||||
|
block.link.startsWith('id:') ? (
|
||||||
|
// 文件夹
|
||||||
|
<div></div>
|
||||||
|
) : (
|
||||||
|
// 链接
|
||||||
|
<LinkBlock block={block} />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
// 小组件
|
||||||
|
<div></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="absolute left-0 -bottom-2 text-sm text-white text-center w-full overflow-hidden text-ellipsis whitespace-nowrap break-all font-bold"
|
||||||
|
style="text-shadow: 0 0 4px rgba(0,0,0,.6)"
|
||||||
|
>
|
||||||
|
{block.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</TransitionGroup>
|
||||||
<div class="w-full h-full flex justify-center items-center p-[var(--block-padding)] relative operation-button">
|
<div class="w-full h-full flex justify-center items-center p-[var(--block-padding)] relative operation-button">
|
||||||
<div
|
<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"
|
class="w-full h-full overflow-hidden rounded-[calc(var(--block-radius)_*_var(--block-size))] bg-white/80 backdrop-blur flex justify-center items-center cursor-pointer hover:scale-105 transition-all"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (layout.state.content[layout.state.current].pages[layout.state.currentPage]) {
|
if (layout.state.content[layout.state.current].pages[layout.state.currentPage]) {
|
||||||
router.path = 'global-adder'
|
router.path = 'global-adder'
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,13 @@
|
||||||
import Sortable from 'sortablejs'
|
import Sortable from 'sortablejs'
|
||||||
import { ref, watch, type Ref } from 'vue'
|
import { ref, watch, type Ref } from 'vue'
|
||||||
|
|
||||||
export const defaultDragging = {
|
export const dragging = {
|
||||||
type: '',
|
type: '',
|
||||||
id: '',
|
id: '',
|
||||||
transportable: false
|
transportable: false
|
||||||
}
|
}
|
||||||
export const dragging = {
|
|
||||||
...defaultDragging
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resetDragging() {
|
export default function useSortable(list: Ref<any[]>, type = '') {
|
||||||
Object.assign(dragging, defaultDragging)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function useSortable(list: Ref<any[]>, type: Ref<string>) {
|
|
||||||
const container = ref<HTMLDivElement | null>(null)
|
const container = ref<HTMLDivElement | null>(null)
|
||||||
watch(
|
watch(
|
||||||
container,
|
container,
|
||||||
|
|
@ -25,7 +18,7 @@ export default function useSortable(list: Ref<any[]>, type: Ref<string>) {
|
||||||
scroll: true,
|
scroll: true,
|
||||||
scrollSensitivity: 200,
|
scrollSensitivity: 200,
|
||||||
scrollSpeed: 10,
|
scrollSpeed: 10,
|
||||||
...(type.value === 'page'
|
...(type === 'page'
|
||||||
? {
|
? {
|
||||||
invertSwap: true,
|
invertSwap: true,
|
||||||
invertedSwapThreshold: 1,
|
invertedSwapThreshold: 1,
|
||||||
|
|
@ -34,7 +27,7 @@ export default function useSortable(list: Ref<any[]>, type: Ref<string>) {
|
||||||
: {}),
|
: {}),
|
||||||
ghostClass: 'opacity-20',
|
ghostClass: 'opacity-20',
|
||||||
onStart: (e: any) => {
|
onStart: (e: any) => {
|
||||||
dragging.type = type.value
|
dragging.type = type
|
||||||
dragging.id = e.item.id || ''
|
dragging.id = e.item.id || ''
|
||||||
// 只有链接才能被拖拽到其他区域
|
// 只有链接才能被拖拽到其他区域
|
||||||
dragging.transportable = e.item.dataset?.transportable ? true : false
|
dragging.transportable = e.item.dataset?.transportable ? true : false
|
||||||
|
|
@ -43,8 +36,8 @@ export default function useSortable(list: Ref<any[]>, type: Ref<string>) {
|
||||||
if (e.related.className.includes('operation-button') && e.willInsertAfter) return false
|
if (e.related.className.includes('operation-button') && e.willInsertAfter) return false
|
||||||
},
|
},
|
||||||
onEnd: (e) => {
|
onEnd: (e) => {
|
||||||
// 如果已经被 onDrop 操作,不会进行交换
|
// 同区域拖拽,直接交换位置
|
||||||
if (!dragging.id) return
|
if (dragging.type !== type) return
|
||||||
const oldIdx = e.oldIndex
|
const oldIdx = e.oldIndex
|
||||||
const newIdx = e.newIndex
|
const newIdx = e.newIndex
|
||||||
if (oldIdx === undefined || newIdx === undefined) return
|
if (oldIdx === undefined || newIdx === undefined) return
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export interface Layout {
|
||||||
]
|
]
|
||||||
current: 0 | 1 | 2 // 游戏,工作,轻娱
|
current: 0 | 1 | 2 // 游戏,工作,轻娱
|
||||||
currentPage: number
|
currentPage: number
|
||||||
dir: { [key: string]: { list: Block[]; label: string } }
|
dir: { [key: string]: Block[] }
|
||||||
dock: [
|
dock: [
|
||||||
Block | null,
|
Block | null,
|
||||||
Block | null,
|
Block | null,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ export default defineStore('layout', () => {
|
||||||
[ready, state],
|
[ready, state],
|
||||||
([re, s]) => {
|
([re, s]) => {
|
||||||
if (!re) return
|
if (!re) return
|
||||||
console.log(toRaw(s))
|
|
||||||
db.setItem('layout', toRaw(s))
|
db.setItem('layout', toRaw(s))
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
|
|
@ -64,7 +63,6 @@ export default defineStore('layout', () => {
|
||||||
// 添加图标
|
// 添加图标
|
||||||
const addBlock = (block: Block, _page: number = -1) => {
|
const addBlock = (block: Block, _page: number = -1) => {
|
||||||
const page = _page >= 0 ? _page : state.currentPage
|
const page = _page >= 0 ? _page : state.currentPage
|
||||||
console.log(page)
|
|
||||||
console.log(state.content[state.current].pages[page])
|
console.log(state.content[state.current].pages[page])
|
||||||
if (!state.content[state.current].pages[page]) {
|
if (!state.content[state.current].pages[page]) {
|
||||||
globalToast.warning('请先添加页面')
|
globalToast.warning('请先添加页面')
|
||||||
|
|
@ -74,30 +72,6 @@ export default defineStore('layout', () => {
|
||||||
pageList.push(block)
|
pageList.push(block)
|
||||||
globalToast.success('添加成功')
|
globalToast.success('添加成功')
|
||||||
}
|
}
|
||||||
const openDir = ref('')
|
|
||||||
// 文件夹只有一个时,将当前界面的文件夹替换为图标
|
|
||||||
const checkDir = (id: string) => {
|
|
||||||
const dir = state.dir[id]
|
|
||||||
if (!dir) return
|
|
||||||
if (dir && dir.list.length === 1) {
|
|
||||||
const item = dir.list[0]
|
|
||||||
if (!item) return
|
|
||||||
const idx = currentPage.value.list.findIndex((el) => el.link === 'id:' + id)
|
|
||||||
if (idx < 0) return
|
|
||||||
currentPage.value.list.splice(idx, 1, toRaw(item))
|
|
||||||
if (openDir.value === id) {
|
|
||||||
openDir.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getLabel = (block: Block) => {
|
|
||||||
if (block.link.startsWith('id:')) {
|
|
||||||
const dir = state.dir[block.link.slice(3)]
|
|
||||||
if (dir) return dir.label
|
|
||||||
}
|
|
||||||
return block.label || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
|
|
@ -106,9 +80,6 @@ export default defineStore('layout', () => {
|
||||||
currentPage,
|
currentPage,
|
||||||
isCompact,
|
isCompact,
|
||||||
background,
|
background,
|
||||||
addBlock,
|
addBlock
|
||||||
openDir,
|
|
||||||
checkDir,
|
|
||||||
getLabel
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { BlockType, type Block } from './layout.types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 block 类型
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {Block} b
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export function checkBlock(b: Block) {
|
||||||
|
if (!b.link) return BlockType.Widget
|
||||||
|
if (b.link.startsWith('id:')) return BlockType.Folder
|
||||||
|
return BlockType.Link
|
||||||
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => <div class="w-full h-full bg-red-50"></div>
|
|
||||||
})
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => <div class="w-full h-full bg-red-50"></div>
|
|
||||||
})
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => <div class="w-full h-full bg-red-50"></div>
|
|
||||||
})
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent(() => {
|
|
||||||
return () => <div class="w-full h-full bg-red-50"></div>
|
|
||||||
})
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import type { Widget } from '..'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'calendar',
|
|
||||||
label: '日历',
|
|
||||||
description: '日历信息',
|
|
||||||
icon: '/icons/calendarIcon.png',
|
|
||||||
modal: () => import('./Modal'),
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
w: 2,
|
|
||||||
h: 1,
|
|
||||||
label: '小',
|
|
||||||
component: () => import('./Small')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
w: 2,
|
|
||||||
h: 2,
|
|
||||||
label: '中',
|
|
||||||
component: () => import('./Middle')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
w: 4,
|
|
||||||
h: 2,
|
|
||||||
label: '大',
|
|
||||||
component: () => import('./Large')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as Widget
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import calendar from './calendar'
|
|
||||||
|
|
||||||
export interface Widget {
|
|
||||||
name: string // 小组件类型唯一标识
|
|
||||||
label: string // 小组件名称
|
|
||||||
description: string // 小组件描述
|
|
||||||
icon: string // 小组件图标
|
|
||||||
modal: () => any // 弹框组件
|
|
||||||
list: {
|
|
||||||
w: number
|
|
||||||
h: number
|
|
||||||
label: string
|
|
||||||
component: () => any
|
|
||||||
}[] // 不同尺寸小组件块
|
|
||||||
}
|
|
||||||
|
|
||||||
export default [calendar] as Widget[]
|
|
||||||
Loading…
Reference in New Issue