等待开发组件布局
This commit is contained in:
parent
1e9a05ca33
commit
ceb05a4f62
|
@ -23,6 +23,7 @@
|
|||
"oh-vue-icons": "^1.0.0-rc3",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.3",
|
||||
"sortablejs": "^1.15.3",
|
||||
"ua-parser-js": "^1.0.38",
|
||||
"uuid": "^10.0.0",
|
||||
"v-viewer": "^3.0.13",
|
||||
|
@ -34,6 +35,7 @@
|
|||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/ali-oss": "^6.16.11",
|
||||
"@types/node": "^20.14.5",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
|
|
|
@ -8,11 +8,15 @@ import SettingsOverlay from './settings/SettingsOverlay'
|
|||
import Sider from './layout/sider'
|
||||
import LoginModal from './user/LoginModal'
|
||||
import { computed } from 'vue'
|
||||
import asyncLoader from './utils/asyncLoader'
|
||||
import useLayoutStore from './layout/useLayoutStore'
|
||||
const Grid = asyncLoader(() => import('./layout/grid'))
|
||||
const settings = useSettingsStore()
|
||||
const blockSize = computed(() => settings.state.blockSize + 'rem')
|
||||
const blockPadding = computed(() => settings.state.blockPadding + 'rem')
|
||||
const mainWidth = computed(() => settings.state.mainWidth + '%')
|
||||
const blockRadius = computed(() => settings.state.blockRadius + 'rem')
|
||||
const blockRadius = computed(() => settings.state.blockRadius)
|
||||
const layout = useLayoutStore()
|
||||
</script>
|
||||
<template>
|
||||
<div class="fixed left-0 top-0 w-full h-screen style-root" @contextmenu.prevent>
|
||||
|
@ -23,6 +27,7 @@ const blockRadius = computed(() => settings.state.blockRadius + 'rem')
|
|||
<SettingsButton />
|
||||
<Sider />
|
||||
<LoginModal />
|
||||
<Grid v-if="layout.ready" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import asyncLoader from './utils/asyncLoader'
|
|||
addIcons(MdClose, MdOpeninfull, MdClosefullscreen)
|
||||
const SearchPage = asyncLoader(() => import('@/layout/header/search/SearchPage'))
|
||||
|
||||
const noFullList: RouteStr[] = ['global-search']
|
||||
const noFullList: RouteStr[] = ['global-search', 'global-adder']
|
||||
|
||||
export default defineComponent(() => {
|
||||
const router = useRouterStore()
|
||||
|
@ -21,7 +21,7 @@ export default defineComponent(() => {
|
|||
full.value = false
|
||||
})
|
||||
return () => (
|
||||
<div class="fixed left-0 top-0 z-20 w-full">
|
||||
<div class="fixed left-0 top-0 z-50 w-full">
|
||||
{/* 背景遮罩 */}
|
||||
<Transition>
|
||||
{show.value && (
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import { defineComponent, onMounted } from 'vue'
|
||||
import useLayoutStore from '../useLayoutStore'
|
||||
import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
||||
import { MdAdd } from 'oh-vue-icons/icons'
|
||||
import useRouterStore from '@/useRouterStore'
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
addIcons(MdAdd)
|
||||
|
||||
export default defineComponent(() => {
|
||||
const layout = useLayoutStore()
|
||||
const router = useRouterStore()
|
||||
let container: HTMLDivElement | null = null
|
||||
onMounted(() => {
|
||||
if (!container) return
|
||||
new Sortable(container, {
|
||||
animation: 400,
|
||||
scroll: true,
|
||||
scrollSensitivity: 200,
|
||||
scrollSpeed: 10,
|
||||
invertSwap: true,
|
||||
invertedSwapThreshold: 1,
|
||||
filter: '.operation-button',
|
||||
ghostClass: 'opacity-20',
|
||||
dragClass: 'dragging-block',
|
||||
onStart: (e) => {
|
||||
// layout.moving.id = e.item.id
|
||||
// layout.moving.type = e.item.dataset?.type || ''
|
||||
},
|
||||
onMove: (e) => {
|
||||
if (e.related.className.includes('operation-button') && e.willInsertAfter) return false
|
||||
},
|
||||
onEnd: (e) => {
|
||||
// const oldPath = e.from.dataset.path
|
||||
// const newPath = e.to.dataset.path
|
||||
// if (e.oldIndex === undefined || e.newIndex === undefined || !oldPath || !newPath) return
|
||||
// const oldIndex = e.oldIndex
|
||||
// const newIndex = e.newIndex
|
||||
// const oldList = useSortList(oldPath as any)
|
||||
// const newList = useSortList(newPath as any)
|
||||
// const item = oldList[oldIndex]
|
||||
// if (!item || !isReactive(oldList) || !isReactive(newList)) return
|
||||
// oldList.splice(oldIndex, 1)
|
||||
// newList.splice(newIndex, 0, item)
|
||||
// layout.moving.id = ''
|
||||
// layout.moving.type = ''
|
||||
}
|
||||
})
|
||||
})
|
||||
return () => (
|
||||
<div
|
||||
class="absolute left-0 top-0 w-full h-screen overflow-y-auto no-scrollbar pt-[240px] px-[calc((100%_-_var(--main-width))_/_2)]"
|
||||
onScroll={(e) => {
|
||||
const h = (e.target as any).scrollTop
|
||||
if (h > 60) {
|
||||
// 需要移动搜索框和时间
|
||||
layout.isCompact = true
|
||||
} else {
|
||||
layout.isCompact = false
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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);grid-template-rows:var(--block-size)"
|
||||
ref={(el) => (container = el as any)}
|
||||
>
|
||||
<div class="w-full h-full flex justify-center items-center p-[var(--block-padding)] relative operation-button">
|
||||
<div
|
||||
class="w-full h-full overflow-hidden rounded-[calc(var(--block-radius)_*_var(--block-size))] bg-white/80 backdrop-blur flex justify-center items-center cursor-pointer hover:scale-105 transition-all"
|
||||
onClick={() => {
|
||||
router.path = 'global-adder'
|
||||
}}
|
||||
>
|
||||
<OhVueIcon name="md-add" fill="white" scale={2} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
|
@ -2,6 +2,7 @@ import useSettingsStore from '@/settings/useSettingsStore'
|
|||
import useTimeStore from '@/utils/useTimeStore'
|
||||
import { Lunar } from 'lunar-typescript'
|
||||
import { computed, defineComponent, Transition } from 'vue'
|
||||
import useLayoutStore from '../useLayoutStore'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
|
@ -28,21 +29,39 @@ export default defineComponent({
|
|||
dayWeek
|
||||
}
|
||||
})
|
||||
|
||||
const settings = useSettingsStore()
|
||||
const layout = useLayoutStore()
|
||||
return () => (
|
||||
<div class="shadow-text tracking-widest font-mono text-white/80 font-bold">
|
||||
<div
|
||||
class="absolute z-20 shadow-text tracking-widest font-mono font-bold h-[110px] transition-all"
|
||||
style={{
|
||||
color: layout.isCompact ? 'white' : 'rgba(255,255,255,.8)',
|
||||
transitionDuration: '.4s',
|
||||
left: layout.isCompact ? '20px' : '50%',
|
||||
top: layout.isCompact ? '20px' : '50px',
|
||||
transform: layout.isCompact ? '' : 'translate(-50%,0)'
|
||||
}}
|
||||
>
|
||||
<Transition>
|
||||
{settings.state.showDate && (
|
||||
<div class="text-[4rem] leading-[4rem]">{text.value.timeStr}</div>
|
||||
<div
|
||||
class={
|
||||
'transition-all ' +
|
||||
(layout.isCompact ? 'text-[1.4rem] leading-[1.4rem]' : 'text-[4rem] leading-[4rem]')
|
||||
}
|
||||
>
|
||||
{text.value.timeStr}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
<Transition>
|
||||
{settings.state.showTime && (
|
||||
<div class="flex justify-center items-center gap-4 mt-4">
|
||||
<div
|
||||
class={'flex items-center gap-4 mt-4 ' + (layout.isCompact ? '' : 'justify-center')}
|
||||
>
|
||||
<div>{text.value.dateStr}</div>
|
||||
<div>星期{info.value.dayWeek}</div>
|
||||
<div>{info.value.day}</div>
|
||||
<Transition>{!layout.isCompact && <div>星期{info.value.dayWeek}</div>}</Transition>
|
||||
<Transition>{!layout.isCompact && <div>{info.value.day}</div>}</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
|
|
|
@ -9,25 +9,8 @@ export default defineComponent({
|
|||
setup() {
|
||||
return () => (
|
||||
<>
|
||||
<div
|
||||
class="absolute z-20"
|
||||
style={{
|
||||
left: '50%',
|
||||
top: '4rem',
|
||||
transform: 'translate(-50%,0)'
|
||||
}}
|
||||
>
|
||||
<GlobalTime />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute left-1/2 -translate-x-1/2 z-20"
|
||||
style={{
|
||||
top: '12rem'
|
||||
}}
|
||||
>
|
||||
<Search />
|
||||
</div>
|
||||
<GlobalTime />
|
||||
<Search />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,15 +5,18 @@ import useSearchConfigStore from './useSearchConfigStore'
|
|||
import SearchConfig from './SearchConfig'
|
||||
import SearchHistory from './SearchHistory'
|
||||
import SearchSuggestion from './SearchSuggestion'
|
||||
import useLayoutStore from '@/layout/useLayoutStore'
|
||||
|
||||
export default defineComponent(() => {
|
||||
const settings = useSettingsStore()
|
||||
const search = useSearchStore()
|
||||
const searchConfig = useSearchConfigStore()
|
||||
const layout = useLayoutStore()
|
||||
return () => (
|
||||
<div
|
||||
class="relative"
|
||||
class="absolute left-1/2 -translate-x-1/2 z-20 transition-all"
|
||||
style={{
|
||||
top: layout.isCompact ? '40px' : '172px',
|
||||
width: settings.state.searchWidth + 'rem'
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -42,5 +42,4 @@ export interface Layout {
|
|||
Block | null
|
||||
]
|
||||
simple: boolean
|
||||
loading: boolean
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { OhVueIcon, addIcons } from 'oh-vue-icons'
|
|||
import { PxHeadset, PxAddBox, PxCheck } from 'oh-vue-icons/icons'
|
||||
import useRouterStore from '@/useRouterStore'
|
||||
import useLayoutStore from '../useLayoutStore'
|
||||
import useUserStore from '@/user/useUserStore'
|
||||
|
||||
initIcons()
|
||||
addIcons(PxHeadset, PxAddBox, PxCheck)
|
||||
|
@ -57,6 +58,7 @@ export default defineComponent(() => {
|
|||
const selected = ref(icons[0])
|
||||
const router = useRouterStore()
|
||||
const layout = useLayoutStore()
|
||||
const user = useUserStore()
|
||||
const label = ref('')
|
||||
watch(
|
||||
selected,
|
||||
|
@ -66,106 +68,114 @@ export default defineComponent(() => {
|
|||
{ immediate: true }
|
||||
)
|
||||
return () => (
|
||||
<div class="fixed left-6 top-1/2 -translate-y-1/2 h-[600px] z-30">
|
||||
<div class="w-[56px] h-full rounded-[28px] bg-black/70 backdrop-blur flex flex-col justify-between items-center">
|
||||
<ModeSwitch />
|
||||
<div class="w-full h-[64px]" />
|
||||
<div class="w-full h-0 flex-grow overflow-auto relative no-scrollbar">
|
||||
<TransitionGroup>
|
||||
{layout.state.content[layout.state.current].map((el, idx) => (
|
||||
<Item
|
||||
key={idx}
|
||||
name={el.name}
|
||||
label={el.label}
|
||||
idx={idx}
|
||||
active={layout.state.currentPage === idx}
|
||||
onClick={() => {
|
||||
layout.state.currentPage = idx
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
<Transition>
|
||||
{layout.state.content[layout.state.current]?.length > 0 && (
|
||||
<Transition>
|
||||
{layout.ready && (
|
||||
<div class="fixed left-6 top-1/2 -translate-y-1/2 h-[600px] z-30">
|
||||
<div class="w-[56px] h-full rounded-[28px] bg-black/70 backdrop-blur flex flex-col justify-between items-center">
|
||||
<ModeSwitch />
|
||||
<div class="w-full h-[64px]" />
|
||||
<div class="w-full h-0 flex-grow overflow-auto relative no-scrollbar">
|
||||
<TransitionGroup>
|
||||
{layout.state.content[layout.state.current].map((el, idx) => (
|
||||
<Item
|
||||
key={idx}
|
||||
name={el.name}
|
||||
label={el.label}
|
||||
idx={idx}
|
||||
active={layout.state.currentPage === idx}
|
||||
onClick={() => {
|
||||
layout.state.currentPage = idx
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
<Transition>
|
||||
{layout.state.content[layout.state.current]?.length > 0 && (
|
||||
<div
|
||||
class="absolute w-full h-[56px] rounded-lg bg-white/40 left-0 transition-all"
|
||||
style={{
|
||||
transitionDuration: '.3s',
|
||||
top: `${layout.state.currentPage * 56}px`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="w-full h-4" />
|
||||
<Item
|
||||
name="px-add-box"
|
||||
label="添加"
|
||||
onClick={() => {
|
||||
showEdit.value = true
|
||||
}}
|
||||
/>
|
||||
<Item
|
||||
name="px-headset"
|
||||
label="反馈"
|
||||
onClick={() => {
|
||||
router.path = 'settings-fallback'
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="w-[56px] h-[56px] bg-white/40 rounded-full border-white border-[2px] border-solid overflow-hidden cursor-pointer"
|
||||
onClick={() => {
|
||||
if (user.isLogin) {
|
||||
router.path = 'settings-user'
|
||||
} else {
|
||||
router.path = 'global-login'
|
||||
}
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
{/* 添加页面 */}
|
||||
<Transition name="page-adder">
|
||||
{showEdit.value && (
|
||||
<div
|
||||
class="absolute w-full h-[56px] rounded-lg bg-white/40 left-0 transition-all"
|
||||
style={{
|
||||
transitionDuration: '.3s',
|
||||
top: `${layout.state.currentPage * 56}px`
|
||||
class="absolute left-[70px] bottom-0 w-56 rounded-lg p-4 bg-white/40 backdrop-blur shadow-lg"
|
||||
v-outside-click={() => {
|
||||
showEdit.value = false
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<input
|
||||
class="rounded bg-black/10 text-center text-sm w-full py-1"
|
||||
v-model={label.value}
|
||||
maxlength={2}
|
||||
/>
|
||||
<div class="flex flex-wrap gap-1 mt-2">
|
||||
{icons.map((el) => (
|
||||
<div
|
||||
class={
|
||||
'p-1 rounded cursor-pointer transition-all ' +
|
||||
(selected.value.name === el.name
|
||||
? 'text-black/80 bg-white shadow'
|
||||
: 'text-black/60 hover:shadow hover:text-black/80 hover:bg-white')
|
||||
}
|
||||
onClick={() => {
|
||||
selected.value = { ...el }
|
||||
}}
|
||||
>
|
||||
<OhVueIcon name={el.name} fill="black" scale={1.3} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
class="w-full mt-2 py-1 rounded-lg bg-white text-center text-sm shadow hover:shadow-lg transition-all cursor-pointer"
|
||||
onClick={() => {
|
||||
layout.state.content[layout.state.current].push({
|
||||
list: [],
|
||||
label: label.value,
|
||||
name: selected.value.name
|
||||
})
|
||||
}}
|
||||
>
|
||||
<OhVueIcon name="px-check" />
|
||||
添加页面
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="w-full h-4" />
|
||||
<Item
|
||||
name="px-add-box"
|
||||
label="添加"
|
||||
onClick={() => {
|
||||
showEdit.value = true
|
||||
}}
|
||||
/>
|
||||
<Item
|
||||
name="px-headset"
|
||||
label="反馈"
|
||||
onClick={() => {
|
||||
router.path = 'settings-fallback'
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="w-[56px] h-[56px] bg-white/40 rounded-full border-white border-[2px] border-solid overflow-hidden cursor-pointer"
|
||||
onClick={() => {
|
||||
router.path = 'settings-user'
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
{/* 添加页面 */}
|
||||
<Transition name="page-adder">
|
||||
{showEdit.value && (
|
||||
<div
|
||||
class="absolute left-[70px] bottom-0 w-56 rounded-lg p-4 bg-white/40 backdrop-blur shadow-lg"
|
||||
v-outside-click={() => {
|
||||
showEdit.value = false
|
||||
}}
|
||||
>
|
||||
<input
|
||||
class="rounded bg-black/10 text-center text-sm w-full py-1"
|
||||
v-model={label.value}
|
||||
maxlength={2}
|
||||
/>
|
||||
<div class="flex flex-wrap gap-1 mt-2">
|
||||
{icons.map((el) => (
|
||||
<div
|
||||
class={
|
||||
'p-1 rounded cursor-pointer transition-all ' +
|
||||
(selected.value.name === el.name
|
||||
? 'text-black/80 bg-white shadow'
|
||||
: 'text-black/60 hover:shadow hover:text-black/80 hover:bg-white')
|
||||
}
|
||||
onClick={() => {
|
||||
selected.value = { ...el }
|
||||
}}
|
||||
>
|
||||
<OhVueIcon name={el.name} fill="black" scale={1.3} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
class="w-full mt-2 py-1 rounded-lg bg-white text-center text-sm shadow hover:shadow-lg transition-all cursor-pointer"
|
||||
onClick={() => {
|
||||
layout.state.content[layout.state.current].push({
|
||||
list: [],
|
||||
label: label.value,
|
||||
name: selected.value.name
|
||||
})
|
||||
}}
|
||||
>
|
||||
<OhVueIcon name="px-check" />
|
||||
添加页面
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { Layout } from './layout.types'
|
||||
import { reactive, ref, toRaw, watch } from 'vue'
|
||||
import { computed, reactive, ref, toRaw, watch } from 'vue'
|
||||
import db from '@/db'
|
||||
|
||||
const defaultLayout: Layout = {
|
||||
|
@ -9,8 +9,7 @@ const defaultLayout: Layout = {
|
|||
currentPage: 0,
|
||||
dir: {},
|
||||
dock: [null, null, null, null, null, null, null, null, null, null],
|
||||
simple: false,
|
||||
loading: true
|
||||
simple: false
|
||||
}
|
||||
|
||||
export default defineStore('layout', () => {
|
||||
|
@ -36,8 +35,15 @@ export default defineStore('layout', () => {
|
|||
state.currentPage = 0
|
||||
}
|
||||
)
|
||||
const currentPageList = computed(() =>
|
||||
state.simple ? [] : state.content[state.current]?.[state.currentPage]?.list || []
|
||||
)
|
||||
// 是让时间和搜索改变位置,使画面更紧凑 —— @/layout/grid
|
||||
const isCompact = ref(false)
|
||||
return {
|
||||
state,
|
||||
ready
|
||||
ready,
|
||||
currentPageList,
|
||||
isCompact
|
||||
}
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ export default defineStore(
|
|||
blockSize: 6,
|
||||
blockPadding: 1,
|
||||
mainWidth: 70,
|
||||
blockRadius: 1,
|
||||
blockRadius: 0.2,
|
||||
// 搜索
|
||||
searchWidth: 30,
|
||||
searchRadius: 24
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { defineComponent, reactive, Transition } from 'vue'
|
||||
import useRouterStore from '@/useRouterStore'
|
||||
import request from '@/utils/request'
|
||||
import useUserStore from './useUserStore'
|
||||
|
||||
export default defineComponent(() => {
|
||||
const router = useRouterStore()
|
||||
const user = useUserStore()
|
||||
const form = reactive({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
return () => (
|
||||
<div class="fixed left-0 top-0 z-20 w-full">
|
||||
<div class="fixed left-0 top-0 z-50 w-full">
|
||||
<Transition>
|
||||
{router.path === 'global-login' && (
|
||||
<div
|
||||
|
@ -36,10 +38,11 @@ export default defineComponent(() => {
|
|||
<input placeholder="密码" type="password" v-model={form.password} />
|
||||
<button
|
||||
onClick={() => {
|
||||
request('POST', '/api/user/login', {
|
||||
data: form
|
||||
request<string>('POST', '/api/user/login', {
|
||||
data: form,
|
||||
returnType: 'text'
|
||||
}).then((res) => {
|
||||
console.log(res)
|
||||
user.token = res
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,5 +1,43 @@
|
|||
import request from '@/utils/request'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
interface UserInfo {
|
||||
id: string
|
||||
username: string
|
||||
created: string
|
||||
vipUntil: string
|
||||
gender: number
|
||||
birthday: string
|
||||
brief: string
|
||||
email: string
|
||||
password: string
|
||||
type: string
|
||||
updated: string
|
||||
avatar: string
|
||||
openId: string
|
||||
}
|
||||
|
||||
export default defineStore('user', () => {
|
||||
return {}
|
||||
const token = ref(localStorage.getItem('token') || '')
|
||||
watch(token, (val) => {
|
||||
localStorage.setItem('token', val)
|
||||
})
|
||||
const profile = ref<null | UserInfo>(null)
|
||||
watch(
|
||||
token,
|
||||
(val) => {
|
||||
if (!val) return
|
||||
request<UserInfo>('GET', '/api/profile').then((res) => {
|
||||
profile.value = res
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
const isLogin = computed(() => !!token.value && !!profile.value)
|
||||
return {
|
||||
token,
|
||||
profile,
|
||||
isLogin
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue