index.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <template>
  2. <div class="login-content" :style="{ 'background-image': 'url(' + login.config.background + ')' }">
  3. <el-row>
  4. <el-col :xs="0" :sm="0" :md="13" :lg="14" :xl="14"></el-col>
  5. <el-col class="main" :xs="24" :sm="24" :md="11" :lg="10" :xl="10">
  6. <transition name="el-zoom-in-center">
  7. <!-- 使用账号密码登录 -->
  8. <div v-if="login.type == 'input'" class="login-wrap sa-flex sa-flex-center sa-flex-1">
  9. <div class="admin-name">
  10. <div>
  11. <div class="sa-line-1">{{ t('modules.auth.welcomeTo', { appName }) }}</div>
  12. <div class="admin-welcome">{{ t('modules.auth.niceToSeeYou') }}</div>
  13. </div>
  14. </div>
  15. <el-form :model="form.data" :rules="form.rules" label-position="left" ref="formRef" label-width="0">
  16. <el-form-item prop="account">
  17. <el-input :class="form.data.account ? 'is-focus' : ''" v-model="form.data.account">
  18. <template #prefix>
  19. <div class="label">{{ t('modules.auth.account') }}</div>
  20. </template>
  21. </el-input>
  22. </el-form-item>
  23. <el-form-item prop="pwd">
  24. <el-input :class="form.data.pwd ? 'is-focus' : ''" v-model="form.data.pwd"
  25. :type="showPwd ? 'password' : 'text'" autocomplete="new-password">
  26. <template #prefix>
  27. <div class="label">{{ t('modules.auth.password') }}</div>
  28. </template>
  29. <template #suffix>
  30. <span v-if="showPwd" @click="showPwd = false">
  31. <sa-svg name="sa-password-hide" size="24"></sa-svg>
  32. </span>
  33. <span v-if="!showPwd" @click="showPwd = true">
  34. <sa-svg name="sa-password-show" size="24"></sa-svg>
  35. </span>
  36. </template>
  37. </el-input>
  38. </el-form-item>
  39. <div>
  40. <el-checkbox v-model="login.last.rememberMe" @change="changeRememberMe"
  41. :label="t('modules.auth.rememberMe')"></el-checkbox>
  42. </div>
  43. <el-button type="primary" :loading="loginLoading" @click="accountLogin">
  44. {{ t('modules.auth.login') }}
  45. </el-button>
  46. </el-form>
  47. </div>
  48. <!-- 有记住的用户 -->
  49. <div v-else-if="login.type == 'rememberMe'" class="login-wrap sa-flex sa-flex-center sa-flex-1">
  50. <div class="admin-name sa-flex sa-row-center">
  51. <sa-image :url="login.last.info.avatar" size="80" radius="40"></sa-image>
  52. </div>
  53. <el-form :model="form.data" :rules="form.rules" label-position="left" ref="formRef" label-width="0">
  54. <el-form-item prop="account">
  55. <div class="sa-flex-1 sa-flex sa-row-center">
  56. {{ login.last.info.nickname }}
  57. </div>
  58. </el-form-item>
  59. <el-form-item prop="pwd">
  60. <el-input :class="form.data.pwd ? 'is-focus' : ''" v-model="form.data.pwd"
  61. :type="showPwd ? 'password' : 'text'" autocomplete="new-password">
  62. <template #prefix>
  63. <div class="label">{{ t('modules.auth.password') }}</div>
  64. </template>
  65. <template #suffix>
  66. <span v-if="showPwd" @click="showPwd = false">
  67. <sa-svg name="sa-password-hide" size="24"></sa-svg>
  68. </span>
  69. <span v-if="!showPwd" @click="showPwd = true">
  70. <sa-svg name="sa-password-show" size="24"></sa-svg>
  71. </span>
  72. </template>
  73. </el-input>
  74. </el-form-item>
  75. <el-button type="primary" :loading="loginLoading" @click="accountLogin">登录
  76. </el-button>
  77. <el-button @click="changeLoginType('input')"> 使用其他账号登录 </el-button>
  78. </el-form>
  79. </div>
  80. </transition>
  81. </el-col>
  82. </el-row>
  83. </div>
  84. </template>
  85. <script setup>
  86. import { reactive, ref, unref, computed, onBeforeMount, onMounted, onUnmounted } from 'vue';
  87. import router from '@/sheep/router';
  88. import { useRoute } from 'vue-router';
  89. import adminApi from '@/sheep/local-data/admin';
  90. import storage from '@/sheep/utils/storage';
  91. import sheep from '@/sheep';
  92. import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
  93. import { ElMessage } from 'element-plus';
  94. import { useI18n } from 'vue-i18n';
  95. const { t } = useI18n();
  96. const accountStore = sheep.$store('account');
  97. const appStore = sheep.$store('app');
  98. const routeQuery = useRoute().query;
  99. if (routeQuery.token) {
  100. accountStore.setToken(`${routeQuery.token}`);
  101. }
  102. const appName = computed(() => appStore.info.name);
  103. const showPwd = ref(true);
  104. const loginLoading = ref(false);
  105. const formRef = ref(null);
  106. const form = reactive({
  107. data: {
  108. account: '',
  109. pwd: '',
  110. },
  111. get rules() {
  112. return {
  113. account: [{ required: true, message: t('modules.auth.accountRequired'), trigger: 'blur' }],
  114. pwd: [{ required: true, message: t('modules.auth.passwordRequired'), trigger: 'blur' }],
  115. };
  116. },
  117. });
  118. // 登录
  119. const accountLogin = async () => {
  120. // 表单验证
  121. unref(formRef).validate(async (valid) => {
  122. if (!valid) return;
  123. loginLoading.value = true;
  124. try {
  125. let submit = form.data;
  126. const { code } = await accountStore.login(submit);
  127. if (code == '200') {
  128. ElMessage.success(t('modules.auth.loginSuccess'));
  129. storage.set('lastLogin', login.last);
  130. await appStore.appLoad();
  131. router.push('/');
  132. }
  133. } catch (error) {
  134. console.error('登录失败:', error);
  135. // 错误信息通常由 store 或 request 拦截器处理,这里只记录日志
  136. } finally {
  137. loginLoading.value = false;
  138. }
  139. });
  140. };
  141. const login = reactive({
  142. config: {
  143. background: '',
  144. },
  145. type: 'input',
  146. last: {},
  147. });
  148. // 初始化登陆表单
  149. function checkLastLogin() {
  150. login.last = storage.get('lastLogin', { rememberMe: false });
  151. if (login.last.rememberMe) {
  152. login.type = 'rememberMe';
  153. form.data.account = login.last.info.account;
  154. }
  155. }
  156. function changeLoginType(type) {
  157. login.type = type;
  158. if (type == 'input') {
  159. form.data.account = '';
  160. form.data.pwd = '';
  161. initLogin();
  162. }
  163. }
  164. function changeRememberMe(e) {
  165. login.last.rememberMe = e;
  166. }
  167. onBeforeMount(async () => {
  168. // 检测登录态
  169. if (accountStore.isLogin) {
  170. await appStore.appLoad();
  171. router.push('/');
  172. } else {
  173. initLogin();
  174. checkLastLogin();
  175. }
  176. });
  177. onMounted(() => {
  178. document.addEventListener('keyup', enterKey);
  179. });
  180. onUnmounted(() => {
  181. document.removeEventListener('keyup', enterKey);
  182. });
  183. function enterKey(event) {
  184. const code = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
  185. if (code == 13) {
  186. accountLogin();
  187. }
  188. }
  189. async function initLogin() {
  190. const { data } = adminApi.loginConfig();
  191. login.config = data;
  192. login.config.background = login.config.background;
  193. }
  194. </script>
  195. <style lang="scss">
  196. .login-content {
  197. .el-form {
  198. width: 100%;
  199. .el-button {
  200. width: 100%;
  201. height: 48px;
  202. line-height: 1;
  203. margin-top: 10px;
  204. }
  205. .el-button--default {
  206. margin-top: 0;
  207. .sa-svg {
  208. font-size: 22px;
  209. margin-right: 6px;
  210. }
  211. }
  212. .el-form-item {
  213. margin-bottom: 32px;
  214. }
  215. .el-form-item__content {
  216. line-height: unset;
  217. }
  218. }
  219. }
  220. </style>
  221. <style lang="scss" scoped>
  222. .login-content {
  223. height: 100%;
  224. color: var(--sa-subtitle);
  225. background-size: cover;
  226. background-repeat: no-repeat;
  227. background-color: var(--sa-background-assist);
  228. @media only screen and (max-width: 768px) {
  229. background-position: center;
  230. }
  231. .el-row {
  232. height: 100%;
  233. .el-col {
  234. display: flex;
  235. align-items: center;
  236. }
  237. @media only screen and (max-width: 992px) {
  238. .el-col {
  239. justify-content: center;
  240. }
  241. }
  242. }
  243. .main {
  244. position: relative;
  245. }
  246. .login-wrap {
  247. width: 460px;
  248. flex-direction: column;
  249. padding: 56px 52px 64px;
  250. border-radius: 8px;
  251. background: var(--sa-background-assist);
  252. filter: drop-shadow(0 0 16px rgba(0, 0, 0, 0.2));
  253. position: absolute;
  254. .admin-name {
  255. width: 100%;
  256. height: 112px;
  257. font-size: 28px;
  258. text-align: center;
  259. .admin-welcome {
  260. font-size: 20px;
  261. margin-top: 16px;
  262. text-align: center;
  263. }
  264. .sa-image {
  265. margin-bottom: 32px;
  266. }
  267. }
  268. }
  269. .login-wrap {
  270. height: 500px;
  271. }
  272. @media only screen and (max-width: 768px) {
  273. .login-wrap {
  274. width: 100%;
  275. max-width: unset;
  276. height: 100%;
  277. border-radius: 0;
  278. padding: 56px 20px 64px;
  279. background: transparent;
  280. filter: unset;
  281. .admin-name {
  282. font-size: 26px;
  283. }
  284. }
  285. }
  286. :deep() {
  287. .el-input {
  288. --el-input-height: 48px;
  289. max-width: unset;
  290. .el-input__wrapper {
  291. position: relative;
  292. .label {
  293. display: flex;
  294. align-items: center;
  295. width: fit-content;
  296. height: 20px;
  297. position: absolute;
  298. top: 14px;
  299. left: 11px;
  300. pointer-events: none;
  301. font-size: 18px;
  302. color: var(--sa-subfont);
  303. }
  304. &.is-focus {
  305. .label {
  306. top: 4px;
  307. font-size: 12px;
  308. transition: ease-in-out 0.2s;
  309. }
  310. .el-input__inner {
  311. padding: 25px 0 9px;
  312. }
  313. }
  314. }
  315. &.is-focus {
  316. .label {
  317. top: 4px;
  318. font-size: 12px;
  319. transition: ease-in-out 0.2s;
  320. }
  321. .el-input__inner {
  322. padding: 25px 0 9px;
  323. }
  324. }
  325. }
  326. }
  327. }
  328. </style>