feat: 侧边栏记录
This commit is contained in:
parent
b6e5c59a9c
commit
b03f804e35
|
@ -32,10 +32,12 @@
|
||||||
"@commitlint/cli": "^17.4.3",
|
"@commitlint/cli": "^17.4.3",
|
||||||
"@commitlint/config-conventional": "^17.4.3",
|
"@commitlint/config-conventional": "^17.4.3",
|
||||||
"@iconify/vue": "^4.1.0",
|
"@iconify/vue": "^4.1.0",
|
||||||
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/node": "^18.13.0",
|
"@types/node": "^18.13.0",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"axios": "^1.3.2",
|
"axios": "^1.3.2",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^13.1.1",
|
"lint-staged": "^13.1.1",
|
||||||
|
|
|
@ -5,10 +5,12 @@ specifiers:
|
||||||
'@commitlint/cli': ^17.4.3
|
'@commitlint/cli': ^17.4.3
|
||||||
'@commitlint/config-conventional': ^17.4.3
|
'@commitlint/config-conventional': ^17.4.3
|
||||||
'@iconify/vue': ^4.1.0
|
'@iconify/vue': ^4.1.0
|
||||||
|
'@types/crypto-js': ^4.1.1
|
||||||
'@types/node': ^18.13.0
|
'@types/node': ^18.13.0
|
||||||
'@vitejs/plugin-vue': ^4.0.0
|
'@vitejs/plugin-vue': ^4.0.0
|
||||||
autoprefixer: ^10.4.13
|
autoprefixer: ^10.4.13
|
||||||
axios: ^1.3.2
|
axios: ^1.3.2
|
||||||
|
crypto-js: ^4.1.1
|
||||||
eslint: ^8.34.0
|
eslint: ^8.34.0
|
||||||
husky: ^8.0.3
|
husky: ^8.0.3
|
||||||
lint-staged: ^13.1.1
|
lint-staged: ^13.1.1
|
||||||
|
@ -35,10 +37,12 @@ devDependencies:
|
||||||
'@commitlint/cli': 17.4.3
|
'@commitlint/cli': 17.4.3
|
||||||
'@commitlint/config-conventional': 17.4.3
|
'@commitlint/config-conventional': 17.4.3
|
||||||
'@iconify/vue': 4.1.0_vue@3.2.47
|
'@iconify/vue': 4.1.0_vue@3.2.47
|
||||||
|
'@types/crypto-js': 4.1.1
|
||||||
'@types/node': 18.13.0
|
'@types/node': 18.13.0
|
||||||
'@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.47
|
'@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.47
|
||||||
autoprefixer: 10.4.13_postcss@8.4.21
|
autoprefixer: 10.4.13_postcss@8.4.21
|
||||||
axios: 1.3.2
|
axios: 1.3.2
|
||||||
|
crypto-js: 4.1.1
|
||||||
eslint: 8.34.0
|
eslint: 8.34.0
|
||||||
husky: 8.0.3
|
husky: 8.0.3
|
||||||
lint-staged: 13.1.1
|
lint-staged: 13.1.1
|
||||||
|
@ -697,6 +701,10 @@ packages:
|
||||||
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
|
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/crypto-js/4.1.1:
|
||||||
|
resolution: {integrity: sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/json-schema/7.0.11:
|
/@types/json-schema/7.0.11:
|
||||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1529,6 +1537,10 @@ packages:
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/crypto-js/4.1.1:
|
||||||
|
resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/css-render/0.15.12:
|
/css-render/0.15.12:
|
||||||
resolution: {integrity: sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==}
|
resolution: {integrity: sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -3,9 +3,8 @@ import { HoverButton, SvgIcon, UserAvatar } from '@/components/common'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<footer class="flex items-center justify-between p-4 overflow-hidden border-t">
|
<footer class="flex items-center justify-between min-w-0 p-4 overflow-hidden border-t h-[70px]">
|
||||||
<UserAvatar />
|
<UserAvatar class="flex-1" />
|
||||||
|
|
||||||
<HoverButton tooltip="Setting">
|
<HoverButton tooltip="Setting">
|
||||||
<span class="text-xl text-[#4f555e]">
|
<span class="text-xl text-[#4f555e]">
|
||||||
<SvgIcon icon="ri:settings-4-line" />
|
<SvgIcon icon="ri:settings-4-line" />
|
||||||
|
|
|
@ -1,34 +1,14 @@
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { ref, watch } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { NButton, NLayoutSider, useMessage } from 'naive-ui'
|
import { NButton, NLayoutSider, useMessage } from 'naive-ui'
|
||||||
import List from './List.vue'
|
import List from './List.vue'
|
||||||
import Footer from './Footer.vue'
|
import Footer from './Footer.vue'
|
||||||
|
import { useAppStore } from '@/store'
|
||||||
|
|
||||||
interface Props {
|
const appStore = useAppStore()
|
||||||
collapsed?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emit {
|
|
||||||
(e: 'update:collapsed', value: boolean): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
collapsed: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
|
||||||
|
|
||||||
const ms = useMessage()
|
const ms = useMessage()
|
||||||
|
|
||||||
const collapsed = ref(props.collapsed)
|
const collapsed = ref(appStore.siderCollapsed ?? false)
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.collapsed,
|
|
||||||
(value: boolean) => {
|
|
||||||
collapsed.value = value
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
ms.info('Coming soon...')
|
ms.info('Coming soon...')
|
||||||
|
@ -36,7 +16,7 @@ function handleAdd() {
|
||||||
|
|
||||||
function handleCollapsed() {
|
function handleCollapsed() {
|
||||||
collapsed.value = !collapsed.value
|
collapsed.value = !collapsed.value
|
||||||
emit('update:collapsed', collapsed.value)
|
appStore.setSiderCollapsed(collapsed.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { ls } from '@/utils/storage'
|
||||||
|
|
||||||
|
export interface AppState {
|
||||||
|
siderCollapsed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultSetting() {
|
||||||
|
return { siderCollapsed: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAppSetting() {
|
||||||
|
const localSetting: AppState = ls.get('appSetting')
|
||||||
|
return localSetting ?? defaultSetting()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAppSetting(setting: AppState) {
|
||||||
|
ls.set('appSetting', setting)
|
||||||
|
}
|
|
@ -1,19 +1,16 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import type { AppState } from './helper'
|
||||||
interface AppState {
|
import { getAppSetting, setAppSetting } from './helper'
|
||||||
siderCollapsed: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAppStore = defineStore('app-store', {
|
export const useAppStore = defineStore('app-store', {
|
||||||
state: (): AppState => ({
|
state: (): AppState => getAppSetting(),
|
||||||
siderCollapsed: false,
|
|
||||||
}),
|
|
||||||
actions: {
|
actions: {
|
||||||
setSiderCollapsed(collapsed: boolean) {
|
setSiderCollapsed(collapsed: boolean) {
|
||||||
this.siderCollapsed = collapsed
|
this.siderCollapsed = collapsed
|
||||||
|
setAppSetting(this.$state)
|
||||||
},
|
},
|
||||||
toggleSiderCollapse() {
|
toggleSiderCollapse() {
|
||||||
this.siderCollapsed = !this.siderCollapsed
|
this.setSiderCollapsed(!this.siderCollapsed)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import CryptoJS from 'crypto-js'
|
||||||
|
|
||||||
|
const CryptoSecret = '__CRYPTO_SECRET__'
|
||||||
|
|
||||||
|
export function enCrypto(data: any) {
|
||||||
|
const str = JSON.stringify(data)
|
||||||
|
return CryptoJS.AES.encrypt(str, CryptoSecret).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deCrypto(data: string) {
|
||||||
|
const bytes = CryptoJS.AES.decrypt(data, CryptoSecret)
|
||||||
|
const str = bytes.toString(CryptoJS.enc.Utf8)
|
||||||
|
|
||||||
|
if (str)
|
||||||
|
return JSON.parse(str)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
export function isNumber<T extends number>(value: T | unknown): value is number {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Number]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isString<T extends string>(value: T | unknown): value is string {
|
||||||
|
return Object.prototype.toString.call(value) === '[object String]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBoolean<T extends boolean>(value: T | unknown): value is boolean {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Boolean]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNull<T extends null>(value: T | unknown): value is null {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Null]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUndefine<T extends undefined>(value: T | unknown): value is undefined {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Undefined]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObject<T extends object>(value: T | unknown): value is object {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isArray<T extends any[]>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Array]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFunction<T extends (...args: any[]) => any | void | never>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Function]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDate<T extends Date>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Date]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRegExp<T extends RegExp>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object RegExp]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPromise<T extends Promise<any>>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Promise]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSet<T extends Set<any>>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Set]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMap<T extends Map<any, any>>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Map]'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFile<T extends File>(value: T | unknown): value is T {
|
||||||
|
return Object.prototype.toString.call(value) === '[object File]'
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './local'
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { deCrypto, enCrypto } from '../crypto'
|
||||||
|
|
||||||
|
interface StorageData<T = any> {
|
||||||
|
value: T
|
||||||
|
expire: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLocalStorage() {
|
||||||
|
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7 // 7 days
|
||||||
|
|
||||||
|
function set<T = any>(key: string, value: T, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||||
|
const storageData: StorageData<T> = {
|
||||||
|
value,
|
||||||
|
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
|
||||||
|
}
|
||||||
|
const json = enCrypto(storageData)
|
||||||
|
window.localStorage.setItem(key, json)
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(key: string) {
|
||||||
|
const json = window.localStorage.getItem(key)
|
||||||
|
if (json) {
|
||||||
|
let storageData: StorageData | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageData = deCrypto(json)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// Prevent failure
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageData) {
|
||||||
|
const { value, expire } = storageData
|
||||||
|
if (expire === null || expire >= Date.now())
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(key)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(key: string) {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
window.localStorage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
set,
|
||||||
|
get,
|
||||||
|
remove,
|
||||||
|
clear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ls = createLocalStorage()
|
Loading…
Reference in New Issue