index.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <script setup lang="ts">
  2. import { NForm, NFormItem, NInput, NButton, NCheckbox, NCarousel } from 'naive-ui'
  3. import { computed, onMounted, onUnmounted, reactive, ref, useTemplateRef } from 'vue'
  4. import topographySvg from '@/assets/topography.svg'
  5. import { usePersonalization } from '@/composable/usePersonalization'
  6. import ThemeDropdown from '@/layouts/header/Actions/component/ThemeDropdown.vue'
  7. import router from '@/router'
  8. import { useUserStore } from '@/stores/user'
  9. import twc from '@/utils/tailwindColor'
  10. import Illustration1 from './component/Illustration1.vue'
  11. import Illustration2 from './component/Illustration2.vue'
  12. import Illustration3 from './component/Illustration3.vue'
  13. import ThemeColorPopover from './component/ThemeColorPopover.vue'
  14. import type { FormItemRule } from 'naive-ui'
  15. defineOptions({
  16. name: 'SignIn',
  17. })
  18. const { isDark } = usePersonalization()
  19. const userStore = useUserStore()
  20. const loading = ref(false)
  21. const isRememberMed = ref(false)
  22. const textureMaskParams = reactive({
  23. size: '666px 666px',
  24. x: 0,
  25. y: 0,
  26. })
  27. const textureStyle = computed(() => {
  28. return {
  29. filter: isDark.value ? 'invert(0.18)' : 'invert(0.86)',
  30. maskImage: `radial-gradient(circle 200px at ${textureMaskParams.x}px ${textureMaskParams.y}px, #f0f 0%, transparent 100%)`,
  31. WebkitMaskImage: `radial-gradient(circle 200px at ${textureMaskParams.x}px ${textureMaskParams.y}px, #f0f 0%, transparent 100%)`,
  32. }
  33. })
  34. const signInFormRef = useTemplateRef<InstanceType<typeof NForm>>('signInFormRef')
  35. const signInForm = reactive({
  36. account: 'admin',
  37. password: '123456',
  38. })
  39. const signInFormRules: Record<string, FormItemRule[]> = {
  40. account: [{ required: true, message: '请输入账号', trigger: ['input'] }],
  41. password: [{ required: true, message: '请输入密码', trigger: ['input'] }],
  42. }
  43. function toLayouts() {
  44. const { r } = router.currentRoute.value.query
  45. setTimeout(() => {
  46. if (signInForm.account.includes('admin')) {
  47. userStore.setToken('admin')
  48. } else {
  49. userStore.setToken('user')
  50. }
  51. router.replace({
  52. path: (r as string) || '/',
  53. })
  54. loading.value = false
  55. }, 1000)
  56. }
  57. const handleSubmitClick = () => {
  58. signInFormRef.value?.validate((errors) => {
  59. if (!errors) {
  60. loading.value = true
  61. toLayouts()
  62. }
  63. })
  64. }
  65. function updateTexturePosition(x: number, y: number) {
  66. textureMaskParams.x = x
  67. textureMaskParams.y = y
  68. }
  69. function onMouseMove(e: MouseEvent) {
  70. updateTexturePosition(e.clientX, e.clientY)
  71. }
  72. function onTouchMove(e: TouchEvent) {
  73. if (e.touches.length > 0) {
  74. updateTexturePosition(e.touches[0].clientX, e.touches[0].clientY)
  75. }
  76. }
  77. onMounted(() => {
  78. window.addEventListener('mousemove', onMouseMove)
  79. window.addEventListener('touchmove', onTouchMove)
  80. })
  81. onUnmounted(() => {
  82. window.removeEventListener('mousemove', onMouseMove)
  83. window.removeEventListener('touchmove', onTouchMove)
  84. })
  85. </script>
  86. <template>
  87. <div
  88. class="relative flex h-svh items-center justify-center overflow-hidden bg-neutral-50 p-6 transition-[background-color] dark:bg-neutral-900"
  89. >
  90. <div
  91. class="absolute top-0 left-0 size-full bg-neutral-200/45 transition-[background-color] dark:bg-neutral-800/50"
  92. :style="{
  93. 'mask-image': `url(${topographySvg})`,
  94. '-webkit-mask-image': `url(${topographySvg})`,
  95. 'mask-repeat': 'repeat',
  96. 'mask-size': textureMaskParams.size,
  97. '-webkit-mask-repeat': 'repeat',
  98. '-webkit-mask-size': textureMaskParams.size,
  99. }"
  100. />
  101. <div
  102. class="pointer-events-none absolute inset-0 z-10 transition-[filter]"
  103. :style="{
  104. 'background-image': `url(${topographySvg})`,
  105. 'background-size': textureMaskParams.size,
  106. '-webkit-mask-repeat': 'no-repeat',
  107. 'mask-repeat': 'no-repeat',
  108. ...textureStyle,
  109. }"
  110. />
  111. <div class="relative z-50 flex h-[480px] w-[800px] justify-center rounded shadow-lg">
  112. <div
  113. class="flex-1 bg-neutral-50 py-6 pl-6 text-primary transition-[background-color] max-sm:hidden dark:bg-neutral-850"
  114. >
  115. <NCarousel
  116. draggable
  117. :show-dots="false"
  118. :show-arrow="false"
  119. loop
  120. autoplay
  121. >
  122. <div class="flex h-full items-center p-5">
  123. <Illustration1 />
  124. </div>
  125. <div class="flex h-full items-center p-5">
  126. <Illustration2 />
  127. </div>
  128. <div class="flex h-full items-center p-5">
  129. <Illustration3 />
  130. </div>
  131. </NCarousel>
  132. </div>
  133. <div
  134. class="relative flex w-full flex-col bg-white px-10 py-12 transition-[background-color] sm:w-[340px] dark:bg-neutral-800"
  135. >
  136. <div class="absolute top-0 left-0 z-50 flex w-full items-center justify-end gap-x-4 p-4">
  137. <ThemeColorPopover />
  138. <ThemeDropdown />
  139. </div>
  140. <div>
  141. <div>
  142. <h2 class="text-2xl">登&nbsp;录</h2>
  143. <p class="text-neutral-400 transition-[color] dark:text-neutral-500">SIGN IN</p>
  144. </div>
  145. <div class="mt-12">
  146. <NForm
  147. ref="signInFormRef"
  148. :model="signInForm"
  149. :show-require-mark="false"
  150. :rules="signInFormRules"
  151. >
  152. <NFormItem
  153. path="account"
  154. label="账号"
  155. >
  156. <NInput
  157. v-model:value="signInForm.account"
  158. placeholder="请输入账号"
  159. clearable
  160. :input-props="{
  161. autocomplete: 'off',
  162. }"
  163. >
  164. <template #prefix>
  165. <span class="mr-1.5 iconify size-5.5 ph--user-circle" />
  166. </template>
  167. </NInput>
  168. </NFormItem>
  169. <NFormItem
  170. path="password"
  171. label="密码"
  172. >
  173. <NInput
  174. v-model:value="signInForm.password"
  175. placeholder="请输入密码"
  176. type="password"
  177. clearable
  178. :input-props="{
  179. autocomplete: 'off',
  180. }"
  181. >
  182. <template #prefix>
  183. <span class="mr-1.5 iconify size-5.5 ph--lock-key" />
  184. </template>
  185. </NInput>
  186. </NFormItem>
  187. <div class="flex justify-between">
  188. <NCheckbox
  189. :theme-overrides="{
  190. border: `1px solid ${twc.neutral[isDark ? 650 : 300]}`,
  191. }"
  192. v-model:checked="isRememberMed"
  193. >记住我</NCheckbox
  194. >
  195. <NButton
  196. text
  197. size="small"
  198. >忘记密码</NButton
  199. >
  200. </div>
  201. <div class="mt-4">
  202. <NButton
  203. type="primary"
  204. :loading="loading"
  205. attr-type="button"
  206. block
  207. size="medium"
  208. @click="handleSubmitClick()"
  209. >
  210. 登&nbsp;录
  211. </NButton>
  212. </div>
  213. </NForm>
  214. </div>
  215. </div>
  216. </div>
  217. </div>
  218. </div>
  219. </template>