+
+ {searchConfig.defaultList.map((el) => (
+
+ ))}
-
-
- {searchConfig.defaultList.map((el) => (
+
+
+ {searchConfig.customList.map((el) => (
{
+ showAdder.value = obj
+ }}
/>
))}
-
-
- {searchConfig.customList.map((el) => (
- {
- showAdder.value = obj
- }}
- />
- ))}
-
-
}
- onClick={() => {
- showAdder.value = null
- }}
- >
- 添加自定义搜索引擎
-
-
+
}
+ onClick={() => {
+ showAdder.value = null
+ }}
+ >
+ 添加自定义搜索引擎
+
-
{
- if (!e) {
- showAdder.value = undefined
- }
- }}
- title="添加搜索引擎"
- footer={false}
- >
-
-
-
+
+
{
+ if (!e) {
+ showAdder.value = undefined
+ }
+ }}
+ title="添加搜索引擎"
+ footer={false}
+ >
+
+
)
})
diff --git a/src/layout/layout.types.ts b/src/layout/layout.types.ts
index 5065080..c14f24b 100644
--- a/src/layout/layout.types.ts
+++ b/src/layout/layout.types.ts
@@ -22,24 +22,25 @@ export interface Block {
extra?: any
}
-export type LayoutPages = { list: Block[]; icon: string; label: string }[]
+export type LayoutPages = { list: Block[]; label: string; name: string }[]
export interface Layout {
content: [LayoutPages, LayoutPages, LayoutPages]
current: 0 | 1 | 2 // 游戏,工作,轻娱
currentPage: number
dir: { [key: string]: Block[] }
- dock: {
- q: Block | null
- w: Block | null
- e: Block | null
- r: Block | null
- a: Block | null
- s: Block | null
- d: Block | null
- f: Block | null
- b: Block | null
- }
+ dock: [
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null,
+ Block | null
+ ]
simple: boolean
loading: boolean
}
diff --git a/src/layout/sider/ModeSwitch.tsx b/src/layout/sider/ModeSwitch.tsx
new file mode 100644
index 0000000..2dc9dcf
--- /dev/null
+++ b/src/layout/sider/ModeSwitch.tsx
@@ -0,0 +1,179 @@
+import { defineComponent, ref, watch } from 'vue'
+import useLayoutStore from '../useLayoutStore'
+import { OhVueIcon, addIcons } from 'oh-vue-icons'
+import { MdVideogameassetTwotone, MdWorkhistoryTwotone, MdStarsTwotone } from 'oh-vue-icons/icons'
+
+addIcons(MdVideogameassetTwotone, MdWorkhistoryTwotone, MdStarsTwotone)
+
+export default defineComponent(() => {
+ const hover = ref(false)
+ const selected = ref<0 | 1 | 2>(0)
+ const layout = useLayoutStore()
+ watch(
+ () => layout.state.current,
+ (val) => {
+ selected.value = val
+ },
+ { immediate: true }
+ )
+ return () => (
+
(hover.value = true)}
+ onMouseleave={() => (hover.value = false)}
+ >
+
+ {/* 转盘 */}
+
+
+
+ 游戏
+
+
+
+ 工作
+
+
+
+ 休闲
+
+
+
+
+ )
+})
diff --git a/src/layout/sider/icons.ts b/src/layout/sider/icons.ts
new file mode 100644
index 0000000..cac19ca
--- /dev/null
+++ b/src/layout/sider/icons.ts
@@ -0,0 +1,159 @@
+import { addIcons } from 'oh-vue-icons'
+import {
+ PxHome,
+ PxSpeedFast,
+ PxCalculator,
+ PxArticleMultiple,
+ PxVideo,
+ PxEdit,
+ PxCode,
+ PxImageGallery,
+ PxGamepad,
+ PxShoppingBag,
+ PxCoin,
+ PxBookOpen,
+ PxCar,
+ PxCloud,
+ PxCommand,
+ PxHumanHandsup,
+ PxCameraAlt,
+ PxDevicePhone,
+ PxHeart,
+ PxBus,
+ PxMusic,
+ PxComment,
+ PxTeach,
+ PxTrending,
+ PxMailMultiple
+} from 'oh-vue-icons/icons'
+export function initIcons() {
+ addIcons(
+ PxHome,
+ PxSpeedFast,
+ PxCalculator,
+ PxArticleMultiple,
+ PxVideo,
+ PxEdit,
+ PxCode,
+ PxImageGallery,
+ PxGamepad,
+ PxShoppingBag,
+ PxCoin,
+ PxBookOpen,
+ PxCar,
+ PxCloud,
+ PxCommand,
+ PxHumanHandsup,
+ PxCameraAlt,
+ PxDevicePhone,
+ PxHeart,
+ PxBus,
+ PxMusic,
+ PxComment,
+ PxTeach,
+ PxTrending,
+ PxMailMultiple
+ )
+}
+export default [
+ {
+ name: 'px-home',
+ label: '首页'
+ },
+ {
+ name: 'px-speed-fast',
+ label: '效率'
+ },
+ {
+ name: 'px-calculator',
+ label: '工具'
+ },
+ {
+ name: 'px-article-multiple',
+ label: '咨询'
+ },
+ {
+ name: 'px-video',
+ label: '影音'
+ },
+ {
+ name: 'px-edit',
+ label: '设计'
+ },
+ {
+ name: 'px-code',
+ label: '编程'
+ },
+ {
+ name: 'px-image-gallery',
+ label: '图库'
+ },
+ {
+ name: 'px-gamepad',
+ label: '游戏'
+ },
+ {
+ name: 'px-shopping-bag',
+ label: '购物'
+ },
+ {
+ name: 'px-coin',
+ label: '金融'
+ },
+ {
+ name: 'px-book-open',
+ label: '阅读'
+ },
+ {
+ name: 'px-car',
+ label: '汽车'
+ },
+ {
+ name: 'px-cloud',
+ label: '网络'
+ },
+ {
+ name: 'px-command',
+ label: '产品'
+ },
+ {
+ name: 'px-human-handsup',
+ label: '创意'
+ },
+ {
+ name: 'px-camera-alt',
+ label: '摄影'
+ },
+ {
+ name: 'px-device-phone',
+ label: '科技'
+ },
+ {
+ name: 'px-heart',
+ label: '健康'
+ },
+ {
+ name: 'px-bus',
+ label: '旅游'
+ },
+ {
+ name: 'px-music',
+ label: '音乐'
+ },
+ {
+ name: 'px-comment',
+ label: '社交'
+ },
+ {
+ name: 'px-teach',
+ label: '学习'
+ },
+ {
+ name: 'px-trending',
+ label: '股票'
+ },
+ {
+ name: 'px-mail-multiple',
+ label: '邮箱'
+ }
+]
diff --git a/src/layout/sider/index.tsx b/src/layout/sider/index.tsx
new file mode 100644
index 0000000..bcf3c8b
--- /dev/null
+++ b/src/layout/sider/index.tsx
@@ -0,0 +1,171 @@
+import { defineComponent, ref, Transition, TransitionGroup, watch } from 'vue'
+import ModeSwitch from './ModeSwitch'
+import icons, { initIcons } from './icons'
+import { OhVueIcon, addIcons } from 'oh-vue-icons'
+import { PxHeadset, PxAddBox, PxCheck } from 'oh-vue-icons/icons'
+import useRouterStore from '@/useRouterStore'
+import useLayoutStore from '../useLayoutStore'
+
+initIcons()
+addIcons(PxHeadset, PxAddBox, PxCheck)
+const Item = defineComponent({
+ props: {
+ name: {
+ type: String,
+ default: ''
+ },
+ label: {
+ type: String,
+ default: ''
+ },
+ idx: {
+ type: Number,
+ default: 0
+ },
+ active: {
+ type: Boolean,
+ default: false
+ }
+ },
+ emits: ['click'],
+ setup(props, ctx) {
+ const hover = ref(false)
+ return () => (
+
{
+ hover.value = true
+ }}
+ onMouseleave={() => {
+ hover.value = false
+ }}
+ onClick={() => {
+ ctx.emit('click')
+ }}
+ >
+
+ {props.label}
+
+ )
+ }
+})
+export default defineComponent(() => {
+ const showEdit = ref(false)
+ const selected = ref(icons[0])
+ const router = useRouterStore()
+ const layout = useLayoutStore()
+ const label = ref('')
+ watch(
+ selected,
+ (val) => {
+ label.value = val.label
+ },
+ { immediate: true }
+ )
+ return () => (
+
+
+
+
+
+
+
- {
+ showEdit.value = true
+ }}
+ />
+
- {
+ router.path = 'settings-fallback'
+ }}
+ />
+
{
+ router.path = 'settings-user'
+ }}
+ >
+
+ {/* 添加页面 */}
+
+ {showEdit.value && (
+ {
+ showEdit.value = false
+ }}
+ >
+
+
+ {icons.map((el) => (
+
{
+ selected.value = { ...el }
+ }}
+ >
+
+
+ ))}
+
+
{
+ layout.state.content[layout.state.current].push({
+ list: [],
+ label: label.value,
+ name: selected.value.name
+ })
+ }}
+ >
+
+ 添加页面
+
+
+ )}
+
+
+ )
+})
diff --git a/src/layout/useLayoutStore.ts b/src/layout/useLayoutStore.ts
index 307acb6..b4583a5 100644
--- a/src/layout/useLayoutStore.ts
+++ b/src/layout/useLayoutStore.ts
@@ -1,30 +1,43 @@
-import { useForageStore } from '@/db'
import { defineStore } from 'pinia'
import type { Layout } from './layout.types'
+import { reactive, ref, toRaw, watch } from 'vue'
+import db from '@/db'
const defaultLayout: Layout = {
content: [[], [], []],
current: 0,
currentPage: 0,
dir: {},
- dock: {
- q: null,
- w: null,
- e: null,
- r: null,
- a: null,
- s: null,
- d: null,
- f: null,
- b: null
- },
+ dock: [null, null, null, null, null, null, null, null, null, null],
simple: false,
loading: true
}
export default defineStore('layout', () => {
- const state = useForageStore('layout', defaultLayout)
+ const state = reactive(defaultLayout)
+ const ready = ref(false)
+ db.getItem
('layout').then((res) => {
+ if (res) {
+ Object.assign(state, res)
+ }
+ ready.value = true
+ })
+ watch(
+ [ready, state],
+ ([re, s]) => {
+ if (!re) return
+ db.setItem('layout', toRaw(s))
+ },
+ { deep: true }
+ )
+ watch(
+ () => state.current,
+ () => {
+ state.currentPage = 0
+ }
+ )
return {
- state
+ state,
+ ready
}
})
diff --git a/src/main.css b/src/main.css
index 009ec79..8c3b320 100644
--- a/src/main.css
+++ b/src/main.css
@@ -40,7 +40,7 @@ body {
top: -3px;
}
.ant-modal-content {
- background-color: rgba(255, 255, 255, 0.9) !important;
+ background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: blur(20px);
}
.ant-modal-header {
@@ -135,3 +135,25 @@ body {
opacity: 0;
transform: scale(0.4);
}
+
+/* 页面添加框动画 */
+.page-adder-enter-active,
+.page-adder-leave-active {
+ transform-origin: center bottom;
+ transition:
+ transform 0.3s ease-in-out,
+ left 0.3s ease-in-out,
+ opacity 0.3s ease-in-out;
+}
+
+.page-adder-enter-from,
+.page-adder-leave-to {
+ opacity: 0;
+ transform: scale(0.4);
+ left: 0;
+}
+
+.no-scrollbar {
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
diff --git a/src/settings/SettingsButton.tsx b/src/settings/SettingsButton.tsx
index 2d47141..7756199 100644
--- a/src/settings/SettingsButton.tsx
+++ b/src/settings/SettingsButton.tsx
@@ -7,7 +7,7 @@ export default defineComponent(() => {
const router = useRouterStore()
return () => (
{
router.path = 'settings-background'
diff --git a/src/settings/SettingsOverlay.tsx b/src/settings/SettingsOverlay.tsx
index de3c0ba..ec5a728 100644
--- a/src/settings/SettingsOverlay.tsx
+++ b/src/settings/SettingsOverlay.tsx
@@ -2,7 +2,7 @@ import useRouterStore from '@/useRouterStore'
import asyncLoader from '@/utils/asyncLoader'
import { computed, defineComponent, Transition } from 'vue'
-const ProfilePage = asyncLoader(() => import('@/user/UserPage'))
+const UserPage = asyncLoader(() => import('@/user/UserPage'))
const BackgroundPage = asyncLoader(() => import('@/layout/background/BackgroundPage'))
const SettingsTab = defineComponent({
@@ -38,11 +38,11 @@ export default defineComponent(() => {
const router = useRouterStore()
const show = computed(() => router.path.startsWith('settings-'))
return () => (
-
+
{/* 背景遮罩 */}
{show.value && (
{
router.path = ''
}}
@@ -51,7 +51,7 @@ export default defineComponent(() => {
{/* 弹框主体 */}
{show.value && (
-
+
{
-
+
{router.path === 'settings-user' ? (
-
+
) : router.path === 'settings-background' ? (
) : null}
diff --git a/src/useRouterStore.ts b/src/useRouterStore.ts
index 47a3ee9..d33bfa9 100644
--- a/src/useRouterStore.ts
+++ b/src/useRouterStore.ts
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
import { ref } from 'vue'
export type WidgetStr = 'ai' | 'calendar'
-export type GlobalStr = 'search' | 'block' | 'adder'
+export type GlobalStr = 'search' | 'block' | 'adder' | 'login'
export type SettingStr =
| 'user'
| 'background'
diff --git a/src/user/LoginModal.tsx b/src/user/LoginModal.tsx
new file mode 100644
index 0000000..b28a991
--- /dev/null
+++ b/src/user/LoginModal.tsx
@@ -0,0 +1,58 @@
+import { defineComponent, reactive, Transition } from 'vue'
+import useRouterStore from '@/useRouterStore'
+import request from '@/utils/request'
+
+export default defineComponent(() => {
+ const router = useRouterStore()
+ const form = reactive({
+ email: '',
+ password: ''
+ })
+ return () => (
+
+
+ {router.path === 'global-login' && (
+ {
+ router.path = ''
+ }}
+ >
+ )}
+
+
+ {router.path === 'global-login' && (
+
+
+
+
+
+
+ 登录 Fatfox 新标签页
+
+
+ 测试用
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ )
+})
diff --git a/src/user/UserPage.tsx b/src/user/UserPage.tsx
index 7d8820f..25fcda4 100644
--- a/src/user/UserPage.tsx
+++ b/src/user/UserPage.tsx
@@ -1,5 +1,19 @@
+import useRouterStore from '@/useRouterStore'
+import { Button } from 'ant-design-vue'
import { defineComponent } from 'vue'
-export default defineComponent(() => () => (
- this is user
-))
+export default defineComponent(() => {
+ const router = useRouterStore()
+ return () => (
+
+ this is user
+
+
+ )
+})
diff --git a/src/utils/ImageUploader.tsx b/src/utils/ImageUploader.tsx
index e7032a7..9a65d56 100644
--- a/src/utils/ImageUploader.tsx
+++ b/src/utils/ImageUploader.tsx
@@ -56,7 +56,7 @@ export default defineComponent({
}}
/>
(
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
+ url: `${'/api' | '/cdn'}${string}`,
+ requestConfig?: {
+ data?: any
+ dataType?: 'json' | 'form'
+ returnType?: 'json' | 'text' | 'blob'
+ }
+) {
+ const { data, dataType = 'json', returnType = 'json' } = requestConfig || {}
+ const fp = localStorage.getItem('fp') || ''
+ const config: any = {
+ method,
+ headers: {
+ Authorization: 'Bearer ' + localStorage.getItem('token'),
+ device,
+ fp
+ }
+ }
+ if (!['GET', 'DELETE'].includes(method)) {
+ if (dataType === 'json') {
+ config.body = JSON.stringify(data)
+ config.headers['Content-Type'] = 'application/json'
+ } else {
+ config.body = new FormData()
+ for (const key in data) {
+ config.body.append(key, data[key])
+ }
+ }
+ }
+ const path = url.replace(/^\/api/, apiBase).replace(/^\/cdn/, cdnBase)
+ return fetch(path, config).then((res) => {
+ if (res.status >= 200 && res.status < 400) {
+ return res[returnType]()
+ } else {
+ return res.text().then((err) => {
+ if (res.status === 401) {
+ localStorage.setItem('token', '')
+ return Promise.reject('登录已过期,请重新登录')
+ }
+ return Promise.reject(err)
+ })
+ }
+ }) as Promise
+}