1. 新增登录页面 2. 新增登录接口 3. 新增stores/userToken.js 保存登录状态登录和退出登录 和 stores/activeCheck.js 保存登录超时和延时函数 4. components/core/Header完善退出登录点击事件 5. 新增cookie工具类 6. 新增登录工具类
已修改5个文件
已添加10个文件
504 ■■■■■ 文件已修改
src/App.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/loginApi.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/image/background_img.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/image/background_main.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/image/login_btn.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/image/pwd_icon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/core/Header.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/activeCheck.js 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/userToken.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/cookieUtil.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/loginUtil.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/LoginView.vue 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue
@@ -1,20 +1,20 @@
<template>
  <el-config-provider :locale="locale">
  <template v-if="$route.name == 'loginView'">
    <el-scrollbar>
      <div class="el-main__content">
        <Content></Content>
      </div>
    </el-scrollbar>
  </template>
  <el-config-provider v-else :locale="locale">
    <el-container class="el-container">
      <el-aside class="el-aside"
        ><SiderMenu
          :collapse="isCollapsed"
          @nav-page="navPage"
        ></SiderMenu
      ></el-aside>
      <el-aside class="el-aside">
        <SiderMenu :collapse="isCollapsed" @nav-page="navPage"></SiderMenu>
      </el-aside>
      <el-container>
        <el-header class="el-header"
          ><Header
            :navTitles="navTitles"
            :collapse="isCollapsed"
            @collapsed-sider="collapsedSider"
          ></Header
        ></el-header>
        <el-header class="el-header">
          <Header :navTitles="navTitles" :collapse="isCollapsed" @collapsed-sider="collapsedSider"></Header>
        </el-header>
        <el-main class="el-main">
          <el-scrollbar>
            <div class="el-main__content">
@@ -87,6 +87,7 @@
  /* background-color: aqua; */
  /* overflow: auto; */
}
.back-top {
  display: flex;
  align-items: center;
src/api/index.js
@@ -1,5 +1,8 @@
import axios from 'axios';
import pinia from '../stores/index'
import { ElMessage } from 'element-plus';
import { useActiveCheck } from '@/stores/activeCheck';
import { router } from '@/router/index';
const debug = false;
@@ -32,12 +35,24 @@
});
$fytz.imgUrl = `${ip2_file}images/`;
const activeCheck = useActiveCheck(pinia);
function resetLoginTime() {
  if (activeCheck.isActive()) {
    // é‡ç½®ç™»å½•时限
    activeCheck.updateLoginTime()
  }
}
//添加拦截器
[$fysp, $fytz].forEach((i) => {
  // æ·»åŠ è¯·æ±‚æ‹¦æˆªå™¨
  i.interceptors.request.use(
    function (config) {
      // åœ¨å‘送请求之前做些什么
      // æ·»åŠ ç™»å½•éªŒè¯
      if (router.currentRoute._value.fullPath !== '/common/loginView') {
        resetLoginTime()
      }
      // if (import.meta.env.DEV) {
      //   console.log('==>请求开始');
      //   console.log(`${config.baseURL}${config.url}`);
src/api/loginApi.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
import { $fysp } from './index';
export default {
  /**
   * ç™»å½•接口
   */
  login({ username, password }) {
    return $fysp
      .post(`/userinfo/login`, {
        acountname: username,
        departmentname: '',
        dguid: '',
        extension1: '',
        extension2: '',
        extension3: '',
        guid: '',
        isenable: true,
        password: password,
        realname: '',
        remark: '',
        telephone: '',
        usertype: '',
        usertypeid: '',
        wechatid: '',
        workno: ''
      })
      .then((res) => res.data);
  }
};
src/assets/image/background_img.jpg
src/assets/image/background_main.png
src/assets/image/login_btn.jpg
src/assets/image/pwd_icon.png
src/components.d.ts
@@ -20,7 +20,6 @@
    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
    ElCalendar: typeof import('element-plus/es')['ElCalendar']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCascader: typeof import('element-plus/es')['ElCascader']
@@ -36,9 +35,6 @@
    ElDialog: typeof import('element-plus/es')['ElDialog']
    ElDivider: typeof import('element-plus/es')['ElDivider']
    ElDrawer: typeof import('element-plus/es')['ElDrawer']
    ElDropdown: typeof import('element-plus/es')['ElDropdown']
    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
    ElEmpty: typeof import('element-plus/es')['ElEmpty']
    ElForm: typeof import('element-plus/es')['ElForm']
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
@@ -47,20 +43,13 @@
    ElImage: typeof import('element-plus/es')['ElImage']
    ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
    ElLink: typeof import('element-plus/es')['ElLink']
    ElMain: typeof import('element-plus/es')['ElMain']
    ElMenu: typeof import('element-plus/es')['ElMenu']
    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
    ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
    ElPopover: typeof import('element-plus/es')['ElPopover']
    ElRadio: typeof import('element-plus/es')['ElRadio']
    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
    ElRow: typeof import('element-plus/es')['ElRow']
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    ElSegmented: typeof import('element-plus/es')['ElSegmented']
@@ -69,7 +58,6 @@
    ElStep: typeof import('element-plus/es')['ElStep']
    ElSteps: typeof import('element-plus/es')['ElSteps']
    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
    ElSwitch: typeof import('element-plus/es')['ElSwitch']
    ElTable: typeof import('element-plus/es')['ElTable']
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
@@ -77,7 +65,6 @@
    ElTag: typeof import('element-plus/es')['ElTag']
    ElText: typeof import('element-plus/es')['ElText']
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
    ElTransfer: typeof import('element-plus/es')['ElTransfer']
    ElTree: typeof import('element-plus/es')['ElTree']
    ElUpload: typeof import('element-plus/es')['ElUpload']
    Footer: typeof import('./components/core/Footer.vue')['default']
src/components/core/Header.vue
@@ -10,13 +10,21 @@
    </el-col>
    <el-col :span="12" class="logout">
      <FYBgTaskDialog></FYBgTaskDialog>
      <el-button icon="SwitchButton">退出登录</el-button>
      <el-button icon="SwitchButton" @click="logout">退出登录</el-button>
    </el-col>
  </el-row>
</template>
<script>
import { useUserStore } from '@/stores/userToken'
import { useRouter } from 'vue-router';
import { ElNotification } from 'element-plus';
export default {
  setup() {
    const userStore = useUserStore()
    const router = useRouter()
    return { userStore, router }
  },
  name: 'CoreHeader',
  props: {
    collapse: {
@@ -49,6 +57,17 @@
    collapsedSider() {
      this.isCollapsed = !this.isCollapsed;
      this.$emit('collapsedSider', this.isCollapsed);
    },
    logout() {
      this.userStore.logout()
      this.router.push('/common/loginView')
      ElNotification({
        title: `退出成功`,
        message: `退出成功`,
        type: 'success',
        // offset: 170,
        position: 'bottom-left',
      });
    }
  }
};
src/router/index.js
@@ -2,7 +2,9 @@
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import pinia from '../stores/index'
import { useLoadingStore } from '../stores/loadingStore'
import { useActiveCheck } from '@/stores/activeCheck';
import { useUserStore } from '@/stores/userToken';
import loginUtil from '../utils/loginUtil';
const routes = [
  // {
  //   //整改审核
@@ -208,19 +210,42 @@
    name: 'docTest',
    path: '/common/docTest',
    component: () => import('@/views/DocTest.vue')
  },
  {
    //登陆
    name: 'loginView',
    path: '/common/loginView',
    component: () => import('@/views/LoginView.vue')
  }
]
];
const router = createRouter({
  // history: createWebHistory(import.meta.env.BASE_URL)
  history: createWebHashHistory(),
  routes: routes
})
});
const loadingStore = useLoadingStore(pinia)
const loadingStore = useLoadingStore(pinia);
const activeCheck = useActiveCheck(pinia);
const userStore = useUserStore(pinia);
// eslint-disable-next-line no-unused-vars
router.afterEach((to, from) => {
  loadingStore.clearLoading()
})
export { router, routes }
  loadingStore.clearLoading();
});
function loginJudge() {
  // å¦‚果是未登录 å°è¯•从cookie登录
  if (!userStore.isLoggedIn()) {
    loginUtil.loginFromCookie();
  }
  // å¦‚果登录超时 è·³è½¬åˆ°ç™»å½•页面
  if (!activeCheck.isActive()) {
    router.push('/common/loginView');
  }
}
router.beforeEach((to, from) => {
  // æ·»åŠ ç™»å½•éªŒè¯
  if (to.fullPath !== '/common/loginView') {
    loginJudge();
  }
});
export { router, routes };
src/stores/activeCheck.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
import { defineStore } from 'pinia';
// ç™»å½•时限 å•位为min
const maxActiveTime = 15;
export const useActiveCheck = defineStore('activeCheck', {
  state: () => ({
    loginTime: null
  }),
  actions: {
    // æ›´æ–°ç™»é™†æ—¶é—´ä¸ºå½“前时间
    updateLoginTime() {
      this.loginTime = new Date();
    },
    // åˆ¤æ–­ç™»é™†æ˜¯å¦è¶…æ—¶
    isActive() {
      if (this.loginTime == null) {
        return false;
      }
      const now = new Date();
      // æœ€æ—©çš„æœ‰æ•ˆæ—¶é—´
      const earliestActiveTime = now.getTime() - maxActiveTime * 60 * 1000;
      const currLoginTime = this.loginTime.getTime();
      return currLoginTime > earliestActiveTime;
    },
    clearLoginTime() {
      this.loginTime = null;
    }
  },
  getters: {}
});
src/stores/userToken.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
import loginApi from '@/api/loginApi.js';
import loginUtil from '@/utils/loginUtil.js';
import pinia from './index';
import { defineStore } from 'pinia';
import { useActiveCheck } from './activeCheck';
export const useUserStore = defineStore('userToken', {
  state: () => ({
    user: null
  }),
  actions: {
    login({ username, password, user = null }, onSuccess, onError) {
      // é€šè¿‡ä¼ é€’ user å¯¹è±¡ç™»å½•
      if (user && user != null && user != {}) {
        loginUtil.addUserCookie(user);
        this.user = user;
        const activeCheck = useActiveCheck(pinia);
        activeCheck.updateLoginTime();
        onSuccess && onSuccess();
        return;
      }
      // é€šè¿‡ä¼ é€’ ç”¨æˆ·åå¯†ç  ç™»å½•
      loginApi
        .login({ username, password })
        .then((res) => {
          if (res.guid != 'null') {
            loginUtil.addUserCookie(res);
            this.user = res;
            const activeCheck = useActiveCheck(pinia);
            activeCheck.updateLoginTime();
            onSuccess();
          } else {
            onError();
          }
        })
        .catch((error) => {
          onError();
          throw error;
        });
    },
    logout() {
      this.user = null;
      const activeCheck = useActiveCheck(pinia);
      activeCheck.clearLoginTime();
      loginUtil.deleteUserCookie();
    },
    isLoggedIn() {
      return this.user != null;
    }
  },
  getters: {}
});
src/utils/cookieUtil.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
export default {
  //读Cookie
  getCookie(objName) {
    //获取指定名称的cookie的值
    var arrStr = document.cookie.split(';');
    for (var i = 0; i < arrStr.length; i++) {
      var temp = arrStr[i].split('=');
      if (temp[0].trim() == objName) return unescape(temp[1]); //解码
    }
    return '';
  },
  // åˆ é™¤
  deleteCookie(name) {
    document.cookie = name + '=' + '1;expires=Thu, 01 Jan 1970 00:00:00 GMT';
  }
};
src/utils/loginUtil.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
import cookieUtil from './cookieUtil';
import pinia from '../stores/index';
import { useUserStore } from '../stores/userToken';
export default {
  // æ·»åŠ cookie中用户登录信息
  addUserCookie(user) {
    /**添加设置cookie**/
    let userObj = 'user=' + escape(JSON.stringify(user));
    var expires = new Date();
    expires.setTime(expires.getTime() + 0.5 * 24 * 60 * 60 * 1000);
    //path=/,表示cookie能在整个网站下使用,path=/temp,表示cookie只能在temp目录下使用
    //    path = ';path=/html'
    //GMT(Greenwich Mean Time)是格林尼治平时,现在的标准时间,协调世界时是UTC
    //参数days只能是数字型
    var _expires = ';expires=' + expires.toGMTString();
    document.cookie = userObj + _expires;
  },
  // åˆ é™¤cookie中用户登录信息
  deleteUserCookie() {
    cookieUtil.deleteCookie('user')
  },
  // ä»Žcookie登录
  loginFromCookie() {
    const userStore = useUserStore(pinia);
    const userCookie = cookieUtil.getCookie('user');
    if (userCookie && userCookie != undefined && userCookie != {}) {
      userStore.login({ user: JSON.parse(userCookie) });
    }
  }
};
src/views/LoginView.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
<template>
    <div class="container">
        <div class="header">
            <h1 class="header-top">生态环境线上监管大数据管理平台</h1>
        </div>
        <div class="main">
            <div class="main-login">
                <div class="width100 main-top-p"><span>用户登录</span></div>
                <el-form :model="formObj" :rules="rules" ref="formRef">
                    <el-form-item prop="username">
                        <el-input v-model="formObj.username" placeholder="请输入用户名" class="user-input"></el-input>
                    </el-form-item>
                    <el-form-item prop="password">
                        <el-input v-model="formObj.password" placeholder="请输入密码" type='password'
                            class="user-input"></el-input>
                    </el-form-item>
                </el-form>
                <div class="width100 media-footer">
                    <label class="pwd-label">
                        <input type="checkbox" class="check-pwd" />记住密码
                    </label>
                    <span class="change-pwd">忘记密码?</span>
                </div>
                <div class="footer width100">
                    <el-button class="login-btn u-loginBtn" @click="onSubmit(false)">登录</el-button>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
import { useFormConfirm } from '@/composables/formConfirm';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
import pinia from '@/stores/index';
import { useUserStore } from '@/stores/userToken'
import { ElNotification } from 'element-plus';
const router = useRouter();
const rules = reactive({
    username: [
        {
            required: true,
            message: '用户名不能为空',
            trigger: 'change'
        }
    ],
    password: [
        {
            required: true,
            message: '密码不能为空',
            trigger: 'change'
        }
    ],
})
const userStore = useUserStore(pinia);
function login() {
    userStore.login({ username: formObj.value.username, password: formObj.value.password }, () => {
        router.push('/fysp/procheck')
        ElNotification({
            title: `登录成功`,
            message: `登录成功`,
            type: 'success',
            // offset: 170,
            position: 'bottom-left',
        });
    }, () => {
        router.push('/common/loginView')
        ElNotification({
            title: `登录失败`,
            message: `用户名或密码错误`,
            type: 'error',
            // offset: 170,
            position: 'bottom-left',
        });
    })
}
//表单操作函数
const { formObj, formRef, onSubmit } =
    useFormConfirm({
        submit: {
            do: login
        }
    });
</script>
<style scoped>
html {
    font-size: 62.5%;
}
body {
    margin: 0 !important;
}
.container {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    background-image: url(@/assets/image/background_img.jpg);
}
.header {
    margin-top: 5rem;
}
.header-top {
    color: #fefffd;
    font-size: 5rem;
    letter-spacing: 0.5rem;
}
.main {
    width: 30rem;
    height: 29rem;
    opacity: 0.8;
    background-color: #092367;
    background-image: url(@/assets/image/background_main.png);
    background-size: 100%;
}
.main-top-p {
    text-align: center;
    font-size: 2rem;
    color: white;
    letter-spacing: 0.5rem;
}
.background-img {
    width: 100%;
    height: 100%;
}
.main-login {
    padding: 5rem;
    padding-bottom: 0.5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.width100 {
    width: 100%;
    margin: 1rem 0;
}
.media-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 1rem;
    margin-top: 3rem;
}
.pwd-label {
    color: white;
}
.user-input::-webkit-input-placeholder {
    color: white;
    font-size: 2rem;
    line-height: 2rem;
}
.user-input {
    /* margin-top: 2rem; */
    border: 2px solid #046eb0;
    border-radius: 8px;
    width: 100%;
    font-size: 1rem;
    color: white;
    /* padding: 0.8rem 0 0 4rem; */
    background-color: #03286a;
}
.check-pwd::before {
    border: 1px solid #0270ae;
    background-color: #042866;
}
.change-pwd {
    color: #00aded;
}
.login-btn {
    width: 100%;
    padding: 0.8rem;
    font-size: 1rem;
    border-radius: 9px;
    color: white;
    background-image: url(@/assets/image/login_btn.jpg);
    background-size: 100%;
    opacity: 1;
    border: none;
    /* margin-top: 3rem; */
}
.icon-user {
    background-image: url(@/assets/image/user_icon.png);
    background-repeat: no-repeat;
    /*设置图片不重复*/
    height: 100%;
}
.icon-pwd {
    background-image: url(@/assets/image/pwd_icon.png);
    background-repeat: no-repeat;
    /*设置图片不重复*/
    height: 80%;
}
::v-deep .el-input__inner {
    width: 300px;
}
</style>