✨ 2023-05-10:feat: 新增锁屏功能
This commit is contained in:
parent
4e39c11d7c
commit
f8580fdf2a
@ -55,6 +55,7 @@
|
|||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
|
"pinia-plugin-persist": "^1.0.0",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"qs": "^6.11.2",
|
"qs": "^6.11.2",
|
||||||
"steady-xml": "^0.1.0",
|
"steady-xml": "^0.1.0",
|
||||||
|
BIN
src/assets/imgs/avatar.jpg
Normal file
BIN
src/assets/imgs/avatar.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
@ -185,7 +185,7 @@ defineExpose({
|
|||||||
<Toolbar
|
<Toolbar
|
||||||
:editor="editorRef"
|
:editor="editorRef"
|
||||||
:editorId="editorId"
|
:editorId="editorId"
|
||||||
class="border-0 b-b-1 border-[var(--el-border-color)] border-solid"
|
class="border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]"
|
||||||
/>
|
/>
|
||||||
<!-- 编辑器 -->
|
<!-- 编辑器 -->
|
||||||
<Editor
|
<Editor
|
||||||
|
@ -5,6 +5,9 @@ import avatarImg from '@/assets/imgs/avatar.gif'
|
|||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import LockDialog from './components/LockDialog.vue'
|
||||||
|
import LockPage from './components/LockPage.vue'
|
||||||
|
import { useLockStore } from '@/store/modules/lock'
|
||||||
|
|
||||||
defineOptions({ name: 'UserInfo' })
|
defineOptions({ name: 'UserInfo' })
|
||||||
|
|
||||||
@ -23,6 +26,14 @@ const prefixCls = getPrefixCls('user-info')
|
|||||||
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
||||||
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
||||||
|
|
||||||
|
// 锁定屏幕
|
||||||
|
const lockStore = useLockStore()
|
||||||
|
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const lockScreen = () => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const loginOut = async () => {
|
const loginOut = async () => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
|
await ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
|
||||||
@ -33,8 +44,7 @@ const loginOut = async () => {
|
|||||||
await userStore.loginOut()
|
await userStore.loginOut()
|
||||||
tagsViewStore.delAllViews()
|
tagsViewStore.delAllViews()
|
||||||
replace('/login?redirect=/index')
|
replace('/login?redirect=/index')
|
||||||
}
|
} catch {}
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
const toProfile = async () => {
|
const toProfile = async () => {
|
||||||
push('/user/profile')
|
push('/user/profile')
|
||||||
@ -62,6 +72,10 @@ const toDocument = () => {
|
|||||||
<Icon icon="ep:menu" />
|
<Icon icon="ep:menu" />
|
||||||
<div @click="toDocument">{{ t('common.document') }}</div>
|
<div @click="toDocument">{{ t('common.document') }}</div>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
|
<ElDropdownItem divided>
|
||||||
|
<Icon icon="ep:lock" />
|
||||||
|
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
||||||
|
</ElDropdownItem>
|
||||||
<ElDropdownItem divided @click="loginOut">
|
<ElDropdownItem divided @click="loginOut">
|
||||||
<Icon icon="ep:switch-button" />
|
<Icon icon="ep:switch-button" />
|
||||||
<div>{{ t('common.loginOut') }}</div>
|
<div>{{ t('common.loginOut') }}</div>
|
||||||
@ -69,4 +83,31 @@ const toDocument = () => {
|
|||||||
</ElDropdownMenu>
|
</ElDropdownMenu>
|
||||||
</template>
|
</template>
|
||||||
</ElDropdown>
|
</ElDropdown>
|
||||||
|
|
||||||
|
<LockDialog v-if="dialogVisible" v-model="dialogVisible" />
|
||||||
|
|
||||||
|
<teleport to="body">
|
||||||
|
<transition name="fade-bottom" mode="out-in">
|
||||||
|
<LockPage v-if="getIsLock" />
|
||||||
|
</transition>
|
||||||
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.fade-bottom-enter-active,
|
||||||
|
.fade-bottom-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity 0.25s,
|
||||||
|
transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-bottom-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-bottom-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
98
src/layout/components/UserInfo/src/components/LockDialog.vue
Normal file
98
src/layout/components/UserInfo/src/components/LockDialog.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { useLockStore } from '@/store/modules/lock'
|
||||||
|
import avatarImg from '@/assets/imgs/avatar.gif'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
|
const { getPrefixCls } = useDesign()
|
||||||
|
const prefixCls = getPrefixCls('lock-dialog')
|
||||||
|
|
||||||
|
const { required } = useValidator()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const lockStore = useLockStore()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
||||||
|
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const dialogVisible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => {
|
||||||
|
console.log('set: ', val)
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogTitle = ref(t('lock.lockScreen'))
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
password: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
password: [required()]
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const handleLock = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
dialogVisible.value = false
|
||||||
|
lockStore.setLockInfo({
|
||||||
|
...formData.value,
|
||||||
|
isLock: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="500px"
|
||||||
|
max-height="170px"
|
||||||
|
:class="prefixCls"
|
||||||
|
:title="dialogTitle"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<img :src="avatar" alt="" class="w-70px h-70px rounded-[50%]" />
|
||||||
|
<span class="text-14px my-10px text-[var(--top-header-text-color)]">
|
||||||
|
{{ userName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||||
|
<el-form-item :label="t('lock.lockPassword')" prop="password">
|
||||||
|
<el-input
|
||||||
|
type="password"
|
||||||
|
v-model="formData.password"
|
||||||
|
:placeholder="'请输入' + t('lock.lockPassword')"
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton type="primary" @click="handleLock">{{ t('lock.lock') }}</ElButton>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:global(.v-lock-dialog) {
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
max-width: calc(100vw - 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
272
src/layout/components/UserInfo/src/components/LockPage.vue
Normal file
272
src/layout/components/UserInfo/src/components/LockPage.vue
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { resetRouter } from '@/router'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { useLockStore } from '@/store/modules/lock'
|
||||||
|
import { useNow } from './useNow'
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import avatarImg from '@/assets/imgs/avatar.gif'
|
||||||
|
|
||||||
|
const tagsViewStore = useTagsViewStore()
|
||||||
|
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
const { replace } = useRouter()
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const password = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const errMsg = ref(false)
|
||||||
|
const showDate = ref(true)
|
||||||
|
|
||||||
|
const { getPrefixCls } = useDesign()
|
||||||
|
const prefixCls = getPrefixCls('lock-page')
|
||||||
|
|
||||||
|
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
|
||||||
|
const userName = computed(() => userStore.user.nickname ?? 'Admin')
|
||||||
|
|
||||||
|
const lockStore = useLockStore()
|
||||||
|
|
||||||
|
const { hour, month, minute, meridiem, year, day, week } = useNow(true)
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 解锁
|
||||||
|
async function unLock() {
|
||||||
|
if (!password.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let pwd = password.value
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const res = await lockStore.unLock(pwd)
|
||||||
|
errMsg.value = !res
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回登录
|
||||||
|
async function goLogin() {
|
||||||
|
await userStore.loginOut().catch(() => {})
|
||||||
|
// 登出后清理
|
||||||
|
wsCache.clear()
|
||||||
|
tagsViewStore.delAllViews()
|
||||||
|
resetRouter() // 重置静态路由表
|
||||||
|
lockStore.resetLockInfo()
|
||||||
|
replace('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShowForm(show = false) {
|
||||||
|
showDate.value = show
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="prefixCls"
|
||||||
|
class="fixed inset-0 flex h-screen w-screen bg-black items-center justify-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="`${prefixCls}__unlock`"
|
||||||
|
class="absolute top-0 left-1/2 flex pt-5 h-16 items-center justify-center sm:text-md xl:text-xl text-white flex-col cursor-pointer transform translate-x-1/2"
|
||||||
|
@click="handleShowForm(false)"
|
||||||
|
v-show="showDate"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:lock" />
|
||||||
|
<span>{{ t('lock.unlock') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-screen h-screen justify-center items-center">
|
||||||
|
<div :class="`${prefixCls}__hour`" class="relative mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5">
|
||||||
|
<span>{{ hour }}</span>
|
||||||
|
<span class="meridiem absolute left-5 top-5 text-md xl:text-xl" v-show="showDate">
|
||||||
|
{{ meridiem }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div :class="`${prefixCls}__minute w-2/5 h-2/5 md:h-4/5 `">
|
||||||
|
<span> {{ minute }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="fade-slide">
|
||||||
|
<div :class="`${prefixCls}-entry`" v-show="!showDate">
|
||||||
|
<div :class="`${prefixCls}-entry-content`">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<img :src="avatar" alt="" class="w-70px h-70px rounded-[50%]" />
|
||||||
|
<span class="text-14px my-10px text-[var(--logo-title-text-color)]">
|
||||||
|
{{ userName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ElInput
|
||||||
|
type="password"
|
||||||
|
:placeholder="t('lock.placeholder')"
|
||||||
|
class="enter-x"
|
||||||
|
v-model="password"
|
||||||
|
/>
|
||||||
|
<span :class="`text-14px ${prefixCls}-entry__err-msg enter-x`" v-if="errMsg">
|
||||||
|
{{ t('lock.message') }}
|
||||||
|
</span>
|
||||||
|
<div :class="`${prefixCls}-entry__footer enter-x`">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="mt-2 mr-2 enter-x"
|
||||||
|
link
|
||||||
|
:disabled="loading"
|
||||||
|
@click="handleShowForm(true)"
|
||||||
|
>
|
||||||
|
{{ t('common.back') }}
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="mt-2 mr-2 enter-x"
|
||||||
|
link
|
||||||
|
:disabled="loading"
|
||||||
|
@click="goLogin"
|
||||||
|
>
|
||||||
|
{{ t('lock.backToLogin') }}
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
class="mt-2"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="unLock()"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
{{ t('lock.entrySystem') }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<div class="absolute bottom-5 w-full text-gray-300 xl:text-xl 2xl:text-3xl text-center enter-y">
|
||||||
|
<div class="text-5xl mb-4 enter-x" v-show="!showDate">
|
||||||
|
{{ hour }}:{{ minute }} <span class="text-3xl">{{ meridiem }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$prefix-cls: '#{$namespace}-lock-page';
|
||||||
|
|
||||||
|
// Small screen / tablet
|
||||||
|
$screen-sm: 576px;
|
||||||
|
|
||||||
|
// Medium screen / desktop
|
||||||
|
$screen-md: 768px;
|
||||||
|
|
||||||
|
// Large screen / wide desktop
|
||||||
|
$screen-lg: 992px;
|
||||||
|
|
||||||
|
// Extra large screen / full hd
|
||||||
|
$screen-xl: 1200px;
|
||||||
|
|
||||||
|
// Extra extra large screen / large desktop
|
||||||
|
$screen-2xl: 1600px;
|
||||||
|
|
||||||
|
$error-color: #ed6f6f;
|
||||||
|
|
||||||
|
.#{$prefix-cls} {
|
||||||
|
z-index: 3000;
|
||||||
|
|
||||||
|
&__unlock {
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__hour,
|
||||||
|
&__minute {
|
||||||
|
display: flex;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #bababa;
|
||||||
|
background-color: #141313;
|
||||||
|
border-radius: 30px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-md) {
|
||||||
|
span:not(.meridiem) {
|
||||||
|
font-size: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $screen-md) {
|
||||||
|
span:not(.meridiem) {
|
||||||
|
font-size: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $screen-sm) {
|
||||||
|
span:not(.meridiem) {
|
||||||
|
font-size: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: $screen-lg) {
|
||||||
|
span:not(.meridiem) {
|
||||||
|
font-size: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $screen-xl) {
|
||||||
|
span:not(.meridiem) {
|
||||||
|
font-size: 260px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: $screen-2xl) {
|
||||||
|
span:not(.meridiem) {
|
||||||
|
font-size: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-entry {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
width: 70px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #bababa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__err-msg {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: $error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
60
src/layout/components/UserInfo/src/components/useNow.ts
Normal file
60
src/layout/components/UserInfo/src/components/useNow.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { dateUtil } from '@/utils/dateUtil'
|
||||||
|
import { reactive, toRefs } from 'vue'
|
||||||
|
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
|
||||||
|
|
||||||
|
export function useNow(immediate = true) {
|
||||||
|
let timer: IntervalHandle
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
week: '',
|
||||||
|
day: 0,
|
||||||
|
hour: '',
|
||||||
|
minute: '',
|
||||||
|
second: 0,
|
||||||
|
meridiem: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
const now = dateUtil()
|
||||||
|
|
||||||
|
const h = now.format('HH')
|
||||||
|
const m = now.format('mm')
|
||||||
|
const s = now.get('s')
|
||||||
|
|
||||||
|
state.year = now.get('y')
|
||||||
|
state.month = now.get('M') + 1
|
||||||
|
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
|
||||||
|
state.day = now.get('date')
|
||||||
|
state.hour = h
|
||||||
|
state.minute = m
|
||||||
|
state.second = s
|
||||||
|
|
||||||
|
state.meridiem = now.format('A')
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
update()
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = setInterval(() => update(), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
tryOnMounted(() => {
|
||||||
|
immediate && start()
|
||||||
|
})
|
||||||
|
|
||||||
|
tryOnUnmounted(() => {
|
||||||
|
stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
start,
|
||||||
|
stop
|
||||||
|
}
|
||||||
|
}
|
@ -56,6 +56,16 @@ export default {
|
|||||||
copySuccess: 'Copy Success',
|
copySuccess: 'Copy Success',
|
||||||
copyError: 'Copy Error'
|
copyError: 'Copy Error'
|
||||||
},
|
},
|
||||||
|
lock: {
|
||||||
|
lockScreen: 'Lock screen',
|
||||||
|
lock: 'Lock',
|
||||||
|
lockPassword: 'Lock screen password',
|
||||||
|
unlock: 'Click to unlock',
|
||||||
|
backToLogin: 'Back to login',
|
||||||
|
entrySystem: 'Entry the system',
|
||||||
|
placeholder: 'Please enter the lock screen password',
|
||||||
|
message: 'Lock screen password error'
|
||||||
|
},
|
||||||
error: {
|
error: {
|
||||||
noPermission: `Sorry, you don't have permission to access this page.`,
|
noPermission: `Sorry, you don't have permission to access this page.`,
|
||||||
pageError: 'Sorry, the page you visited does not exist.',
|
pageError: 'Sorry, the page you visited does not exist.',
|
||||||
|
@ -56,6 +56,16 @@ export default {
|
|||||||
copySuccess: '复制成功',
|
copySuccess: '复制成功',
|
||||||
copyError: '复制失败'
|
copyError: '复制失败'
|
||||||
},
|
},
|
||||||
|
lock: {
|
||||||
|
lockScreen: '锁定屏幕',
|
||||||
|
lock: '锁定',
|
||||||
|
lockPassword: '锁屏密码',
|
||||||
|
unlock: '点击解锁',
|
||||||
|
backToLogin: '返回登录',
|
||||||
|
entrySystem: '进入系统',
|
||||||
|
placeholder: '请输入锁屏密码',
|
||||||
|
message: '锁屏密码错误'
|
||||||
|
},
|
||||||
error: {
|
error: {
|
||||||
noPermission: `抱歉,您无权访问此页面。`,
|
noPermission: `抱歉,您无权访问此页面。`,
|
||||||
pageError: '抱歉,您访问的页面不存在。',
|
pageError: '抱歉,您访问的页面不存在。',
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import piniaPersist from 'pinia-plugin-persist'
|
||||||
|
|
||||||
const store = createPinia()
|
const store = createPinia()
|
||||||
|
store.use(piniaPersist)
|
||||||
|
|
||||||
export const setupStore = (app: App<Element>) => {
|
export const setupStore = (app: App<Element>) => {
|
||||||
app.use(store)
|
app.use(store)
|
||||||
|
52
src/store/modules/lock.ts
Normal file
52
src/store/modules/lock.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { store } from '@/store'
|
||||||
|
|
||||||
|
interface lockInfo {
|
||||||
|
isLock?: boolean
|
||||||
|
password?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LockState {
|
||||||
|
lockInfo: lockInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 芋艿:【锁屏】这里有报错,后续解决下
|
||||||
|
export const useLockStore = defineStore('lock', {
|
||||||
|
state: (): LockState => {
|
||||||
|
return {
|
||||||
|
lockInfo: {
|
||||||
|
// isLock: false, // 是否锁定屏幕
|
||||||
|
// password: '' // 锁屏密码
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
getLockInfo(): lockInfo {
|
||||||
|
return this.lockInfo
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
setLockInfo(lockInfo: lockInfo) {
|
||||||
|
this.lockInfo = lockInfo
|
||||||
|
},
|
||||||
|
resetLockInfo() {
|
||||||
|
this.lockInfo = {}
|
||||||
|
},
|
||||||
|
unLock(password: string) {
|
||||||
|
if (this.lockInfo?.password === password) {
|
||||||
|
this.resetLockInfo()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist: {
|
||||||
|
enabled: true,
|
||||||
|
strategies: [{ key: 'lock', storage: localStorage }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useLockStoreWithOut = () => {
|
||||||
|
return useLockStore(store)
|
||||||
|
}
|
18
src/utils/dateUtil.ts
Normal file
18
src/utils/dateUtil.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Independent time operation tool to facilitate subsequent switch to dayjs
|
||||||
|
*/
|
||||||
|
// TODO 芋艿:【锁屏】可能后面删除掉
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
const DATE_FORMAT = 'YYYY-MM-DD'
|
||||||
|
|
||||||
|
export function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string {
|
||||||
|
return dayjs(date).format(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string {
|
||||||
|
return dayjs(date).format(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dateUtil = dayjs
|
3
types/global.d.ts
vendored
3
types/global.d.ts
vendored
@ -14,6 +14,9 @@ declare global {
|
|||||||
|
|
||||||
type LocaleType = 'zh-CN' | 'en'
|
type LocaleType = 'zh-CN' | 'en'
|
||||||
|
|
||||||
|
declare type TimeoutHandle = ReturnType<typeof setTimeout>
|
||||||
|
declare type IntervalHandle = ReturnType<typeof setInterval>
|
||||||
|
|
||||||
type AxiosHeaders =
|
type AxiosHeaders =
|
||||||
| 'application/json'
|
| 'application/json'
|
||||||
| 'application/x-www-form-urlencoded'
|
| 'application/x-www-form-urlencoded'
|
||||||
|
Loading…
Reference in New Issue
Block a user