mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 04:08:44 +08:00 
			
		
		
		
	✨ 2023-05-10:feat: 新增锁屏功能
This commit is contained in:
		@@ -55,6 +55,7 @@
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
    "pinia-plugin-persist": "^1.0.0",
 | 
			
		||||
    "qrcode": "^1.5.3",
 | 
			
		||||
    "qs": "^6.11.2",
 | 
			
		||||
    "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
 | 
			
		||||
      :editor="editorRef"
 | 
			
		||||
      :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
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,9 @@ import avatarImg from '@/assets/imgs/avatar.gif'
 | 
			
		||||
import { useDesign } from '@/hooks/web/useDesign'
 | 
			
		||||
import { useTagsViewStore } from '@/store/modules/tagsView'
 | 
			
		||||
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' })
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +26,14 @@ const prefixCls = getPrefixCls('user-info')
 | 
			
		||||
const avatar = computed(() => userStore.user.avatar ?? avatarImg)
 | 
			
		||||
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 () => {
 | 
			
		||||
  try {
 | 
			
		||||
    await ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
 | 
			
		||||
@@ -33,8 +44,7 @@ const loginOut = async () => {
 | 
			
		||||
    await userStore.loginOut()
 | 
			
		||||
    tagsViewStore.delAllViews()
 | 
			
		||||
    replace('/login?redirect=/index')
 | 
			
		||||
  }
 | 
			
		||||
  catch { }
 | 
			
		||||
  } catch {}
 | 
			
		||||
}
 | 
			
		||||
const toProfile = async () => {
 | 
			
		||||
  push('/user/profile')
 | 
			
		||||
@@ -62,6 +72,10 @@ const toDocument = () => {
 | 
			
		||||
          <Icon icon="ep:menu" />
 | 
			
		||||
          <div @click="toDocument">{{ t('common.document') }}</div>
 | 
			
		||||
        </ElDropdownItem>
 | 
			
		||||
        <ElDropdownItem divided>
 | 
			
		||||
          <Icon icon="ep:lock" />
 | 
			
		||||
          <div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
 | 
			
		||||
        </ElDropdownItem>
 | 
			
		||||
        <ElDropdownItem divided @click="loginOut">
 | 
			
		||||
          <Icon icon="ep:switch-button" />
 | 
			
		||||
          <div>{{ t('common.loginOut') }}</div>
 | 
			
		||||
@@ -69,4 +83,31 @@ const toDocument = () => {
 | 
			
		||||
      </ElDropdownMenu>
 | 
			
		||||
    </template>
 | 
			
		||||
  </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>
 | 
			
		||||
 | 
			
		||||
<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',
 | 
			
		||||
    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: {
 | 
			
		||||
    noPermission: `Sorry, you don't have permission to access this page.`,
 | 
			
		||||
    pageError: 'Sorry, the page you visited does not exist.',
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,16 @@ export default {
 | 
			
		||||
    copySuccess: '复制成功',
 | 
			
		||||
    copyError: '复制失败'
 | 
			
		||||
  },
 | 
			
		||||
  lock: {
 | 
			
		||||
    lockScreen: '锁定屏幕',
 | 
			
		||||
    lock: '锁定',
 | 
			
		||||
    lockPassword: '锁屏密码',
 | 
			
		||||
    unlock: '点击解锁',
 | 
			
		||||
    backToLogin: '返回登录',
 | 
			
		||||
    entrySystem: '进入系统',
 | 
			
		||||
    placeholder: '请输入锁屏密码',
 | 
			
		||||
    message: '锁屏密码错误'
 | 
			
		||||
  },
 | 
			
		||||
  error: {
 | 
			
		||||
    noPermission: `抱歉,您无权访问此页面。`,
 | 
			
		||||
    pageError: '抱歉,您访问的页面不存在。',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import type { App } from 'vue'
 | 
			
		||||
import { createPinia } from 'pinia'
 | 
			
		||||
import piniaPersist from 'pinia-plugin-persist'
 | 
			
		||||
 | 
			
		||||
const store = createPinia()
 | 
			
		||||
store.use(piniaPersist)
 | 
			
		||||
 | 
			
		||||
export const setupStore = (app: App<Element>) => {
 | 
			
		||||
  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'
 | 
			
		||||
 | 
			
		||||
  declare type TimeoutHandle = ReturnType<typeof setTimeout>
 | 
			
		||||
  declare type IntervalHandle = ReturnType<typeof setInterval>
 | 
			
		||||
 | 
			
		||||
  type AxiosHeaders =
 | 
			
		||||
    | 'application/json'
 | 
			
		||||
    | 'application/x-www-form-urlencoded'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user