首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【愚公系列】2023年02月 WMS智能仓储系统-012.登录功能的实现

【愚公系列】2023年02月 WMS智能仓储系统-012.登录功能的实现

作者头像
愚公搬代码
发布2023-03-16 17:12:16
6350
发布2023-03-16 17:12:16
举报
文章被收录于专栏:历史专栏历史专栏历史专栏

文章目录


前言

1.业务流程说明

登录功能的业务流程主要有

1.在登录页面输入用户名和密码

2.调用后台接口进行验证

3.通过验证之后,根据后台得响应状态跳转到项目主页

2.登录业务的相关技术点

http是无状态的通过cookie在客户端记录状态通过session在服务器端记录状态通过token方式维持状态

3.登录—token原理分析

1.登录页面输入用户名和密码进行登录

2.服务器验证通过之后生成该用户的token并返回

3.客户端存储该token

4.后续所有的请求都携带该token发送请求

5.服务器端验证token是否通过

4.前端框架设计

因为进到具体的业务,前端架构在此做个说明,主要以后端业务为主

前端框架主要引用了两个开源业务

1、Vuetify 3

Vuetify 老牌 Vue UI 组件库,它提供了丰富的常用组件(有超过 100 个组件),适用于多数场景下的使用情况。Vuetify 基于谷歌的Material Design 样式开发,无需写一行 CSS 就能生成相当整洁清爽的界面功能。Vuetify 支持 PC 端和移动端,对移动端有特别棒的优化,响应式,配置简单,带有响应式网络系统,支持事件处理,支持多种浏览器,甚至连 IE 11 也支持。Vuetify 已经发布支持 Vue 3 的版本,如果正在考虑未来的迁移问题,可放心使用。

Vuetify 3官网:https://vuetifyjs.com/en/

在这里插入图片描述
在这里插入图片描述

Vuetify 3文档:https://vuetifyjs.com/en/getting-started/installation/

在这里插入图片描述
在这里插入图片描述

2、vxe-table

vxe-table是一个基于Vue的表格框架,支持增删改查、虚拟滚动、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、虚拟列表、模态窗口、自定义模板、渲染器、贼灵活的配置项、扩展接口等。

vxe-table面向现代浏览器,高效的简洁 API 设计,模块化表格、按需加载、扩展接口,为单行编辑表格而设计,支持增删改查及更多扩展,强大的功能的同时兼具性能。

vxe-table官网:https://gitcode.net/mirrors/xuliangzhan/vxe-table?utm_source=csdn_github_accelerator

在这里插入图片描述
在这里插入图片描述

vxe-table文档:https://vxetable.cn/#/table/start/install

在这里插入图片描述
在这里插入图片描述

一、登录功能的实现

1.登录页面设计

在这里插入图片描述
在这里插入图片描述
<template>
  <div class="loginContainer">
    <Logo />
    <div class="loginLeft">
      <img src="../../assets/img/loginLeft.png" style="width: 80%" />
    </div>
    <div class="loginRight">
      <div class="LanguagesSwitchContainer">
        <LanguagesSwitch />
      </div>
      <LoginForm />
    </div>
  </div>
</template>

<script lang="ts" setup>
import LoginForm from '@/components/login/login-form.vue'
import LanguagesSwitch from '@/components/system/languages.vue'
import Logo from '@/components/system/logo.vue'
</script>

<style scoped lang="less">
.LanguagesSwitchContainer {
  position: absolute;
  right: 10px;
  top: 10px;
}
.loginContainer {
  width: 100%;
  height: 100%;
  display: flex;
  background-color: #fff;
  .loginLeft {
    width: 70%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .loginRight {
    width: 30%;
    background-color: #fafafa;
    display: flex;
    align-items: center;
  }
}
</style>
在这里插入图片描述
在这里插入图片描述

2.登录逻辑功能实现

2.1 登录逻辑页面

因为本项目已实际功能为主,其他小功能和设计掠过

<template>
  <div class="loginForm">
    <div class="titleText">
      <h5>{{ $t('login.welcomeTitle') }}</h5>
    </div>
    <div class="formContainer">
      <v-form ref="VFormRef" v-model="data.valid" lazy-validation @keydown.enter.prevent="method.login()">
        <v-text-field v-model="data.userName" required :rules="data.userNameVaildRules" :label="$t('login.userName')" variant="solo"></v-text-field>
        <v-text-field
          v-model="data.password"
          required
          :rules="data.passwordVaildRules"
          :autocomplete="false"
          :append-inner-icon="data.showPassword ? 'mdi-eye' : 'mdi-eye-off'"
          :type="data.showPassword ? 'text' : 'password'"
          :label="$t('login.password')"
          variant="solo"
          @click:append-inner="method.handleShowPassword()"
        ></v-text-field>
        <v-checkbox v-model="data.remember" :label="$t('login.rememberTips')"></v-checkbox>
        <v-btn color="purple" class="loginBtn" @click="method.login()">{{ $t('login.mainButtonLabel') }}</v-btn>
        <v-btn class="mt-2" color="#666" variant="plain" @click="method.openRegisterDialog">
          {{ i18n.global.t('login.registerTips') }}
        </v-btn>
      </v-form>
    </div>
    <userRegisterForm :show-dialog="data.showDialog" :form="data.dialogForm" @close="method.closeDialog" @saveSuccess="method.saveSuccess" />
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue'
import { Md5 } from 'ts-md5'
import i18n from '@/languages/i18n'
import { login, getUserAuthority } from '@/api/sys/login'
import { store } from '@/store'
import { hookComponent } from '@/components/system'
import { router } from '@/router/index'
import userRegisterForm from './user-register-form.vue'

// Get v-form ref
const VFormRef = ref()

const data = reactive({
  showDialog: false,
  valid: true,
  showPassword: false,
  userName: '',
  password: '',
  remember: false,
  dialogForm: {
    id: 0,
    user_num: '',
    user_name: '',
    auth_string: '',
    email: '',
    // sex: '',
    is_valid: true
  },
  userNameVaildRules: [(v: string) => !!v || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('login.userName') }!`],
  passwordVaildRules: [(v: string) => !!v || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('login.password') }!`]
})

const method = reactive({
  handleShowPassword: () => {
    data.showPassword = !data.showPassword
  },
  login: async () => {
    const { valid } = await VFormRef.value.validate()
    if (!valid) {
      return
    }
    const { data: loginRes } = await login({
      user_name: data.userName,
      password: Md5.hashStr(data.password)
    })

    if (loginRes.isSuccess) {
      const expiredTime = new Date().getTime() + loginRes.data.expire * 60 * 1000

      store.commit('user/setToken', loginRes.data.access_token)
      store.commit('user/setRefreshToken', loginRes.data.refresh_token)
      store.commit('user/setExpirationTime', expiredTime)
      store.commit('user/setEffectiveMinutes', loginRes.data.expire)
      store.commit('user/setUserInfo', loginRes.data)

      const { data: authorityRes } = await getUserAuthority(loginRes.data.userrole_id)
      if (!authorityRes.isSuccess) {
        hookComponent.$message({
          type: 'error',
          content: authorityRes.errorMessage
        })
        return
      }
      if (authorityRes.data.length <= 0) {
        hookComponent.$message({
          type: 'error',
          content: i18n.global.t('login.notAuthority')
        })
        return
      }
      store.commit('user/setUserMenuList', authorityRes.data)

      hookComponent.$message({
        type: 'success',
        content: i18n.global.t('login.loginSuccess')
      })

      // Remember user login info
      if (data.remember) {
        const rememberJSON = JSON.stringify({
          userName: window.btoa(encodeURIComponent(data.userName)),
          password: window.btoa(encodeURIComponent(data.password))
        })
        localStorage.setItem('userLoginInfo', rememberJSON)
      } else {
        localStorage.setItem('userLoginInfo', '')
      }
      // Jump home
      store.commit('system/setCurrentRouterPath', 'homepage')
      router.push('home')
    } else {
      hookComponent.$message({
        type: 'error',
        content: loginRes.errorMessage
      })
    }
  },
  openRegisterDialog: () => {
    data.dialogForm = {
      id: 0,
      user_num: '',
      user_name: '',
      auth_string: '',
      email: '',
      // sex: '',
      is_valid: true
    }
    data.showDialog = true
  },
  // Shut add or update dialog
  closeDialog: () => {
    data.showDialog = false
  },
  // after Add or update success.
  saveSuccess: () => {
    method.closeDialog()
  }
})
//在加载的时候查询localStorage是否保存数据,保存则默认填写用户名密码
onMounted(() => {
  // Get remember username and password
  const rememberJSON = localStorage.getItem('userLoginInfo')
  if (rememberJSON) {
    const obj = JSON.parse(rememberJSON)
    try {
      data.userName = decodeURIComponent(window.atob(obj.userName))
      data.password = decodeURIComponent(window.atob(obj.password))
    } catch {
      data.userName = window.atob(obj.userName)
      data.password = window.atob(obj.password)
    }
    data.remember = true
  }
})
</script>

<style scoped lang="less">
.loginForm {
  // min-height: ;
  height: 50%;
  width: 100%;
  box-sizing: border-box;
  padding: 16px;
  .titleText {
    box-sizing: border-box;
    padding: 20px;
    h5 {
      font-size: 1.5rem !important;
      font-weight: 500;
      line-height: 2rem;
      letter-spacing: normal !important;
      font-family: inter, sans-serif, -apple-system, blinkmacsystemfont, Segoe UI, roboto, Helvetica Neue, arial, sans-serif, 'Apple Color Emoji',
        'Segoe UI Emoji', Segoe UI Symbol !important;
      text-transform: none !important;
    }
  }
  .formContainer {
    box-sizing: border-box;
    padding: 12px 20px;
    .v-btn {
      width: 100%;
    }
    .v-text-field {
      margin-top: 10px;
    }
    .v-checkbox {
      color: #b2b0b5;
      margin-inline-start: -0.5625rem;
      margin-top: -10px;
      height: 60px;
    }
  }
  // There is style pollution Or vuetify itself has problems, replace the required verification color manually
  :deep(.v-messages) {
    color: #b00020 !important;
  }
}

.loginBtn {
  height: 45px;
}
</style>
在这里插入图片描述
在这里插入图片描述

2.2 接口请求

这边主要涉及登录接口和获取菜单接口

/// <summary>
/// login
/// </summary>
/// <param name="loginAccount">user's account infomation</param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("/login")]
public async Task<ResultModel<LoginOutputViewModel>> LoginAsync(LoginInputViewModel loginAccount)
{

    var user = await _accountService.Login(loginAccount,CurrentUser);
    if (user != null)
    {
        var result = _tokenManager.GenerateToken(
            new CurrentUser
            {
                user_id = user.user_id,
                user_name = user.user_name,
                user_num = user.user_num,
                user_role = user.user_role,
                tenant_id = user.tenant_id
            }
            );
        string rt = this._tokenManager.GenerateRefreshToken();

        user.access_token = result.token;
        user.expire = result.expire;
        user.refresh_token = rt;

        await _cacheManager.TokenSet(user.user_id, "WebRefreshToken", rt, _tokenManager.GetRefreshTokenExpireMinute());

        return ResultModel<LoginOutputViewModel>.Success(user);
    }
    else
    {
        return ResultModel<LoginOutputViewModel>.Error(_stringLocalizer["login_failed"]);
    }
}
[HttpGet("authority")]
public async  Task<ResultModel<List<MenuViewModel>>> GetMenusByRoleId(int userrole_id)
{
    var data = await _rolemenuService.GetMenusByRoleId(userrole_id);
    if (data.Any())
    {
        return ResultModel<List<MenuViewModel>>.Success(data);
    }
    else
    {
        return ResultModel<List<MenuViewModel>>.Success(new List<MenuViewModel>());
    }
}

二、注册逻辑功能实现

1.注册页面设计

在这里插入图片描述
在这里插入图片描述
<template>
  <v-dialog v-model="isShow" width="20%" transition="dialog-top-transition" :persistent="true">
    <template #default>
      <v-card>
        <v-toolbar color="white" :title="`${$t('login.register')}`"></v-toolbar>
        <v-card-text>
          <v-form ref="formRef">
            <v-text-field
              v-model="data.form.user_name"
              :label="$t('base.userManagement.user_register_name')"
              :rules="data.rules.user_name"
              variant="outlined"
            ></v-text-field>
            <v-text-field
              v-model="data.form.auth_string"
              :label="$t('base.userManagement.auth_string')"
              :rules="data.rules.auth_string"
              variant="outlined"
              :append-icon="data.isShowPassword ? 'mdi-eye' : 'mdi-eye-off'"
              :type="data.isShowPassword ? 'text' : 'password'"
              @click:append="data.isShowPassword = !data.isShowPassword"
            ></v-text-field>
            <v-select
              v-model="data.form.sex"
              :items="data.combobox.sex"
              item-title="label"
              item-value="value"
              :rules="data.rules.sex"
              :label="$t('base.userManagement.sex')"
              variant="outlined"
              clearable
            ></v-select>
            <v-text-field
              v-model="data.form.email"
              :label="$t('base.userManagement.email')"
              :rules="data.rules.email"
              variant="outlined"
            ></v-text-field>
          </v-form>
        </v-card-text>
        <v-card-actions class="justify-end">
          <v-btn variant="text" @click="method.closeDialog">{{ $t('system.page.close') }}</v-btn>
          <v-btn color="primary" variant="text" @click="method.submit">{{ $t('system.page.submit') }}</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </v-dialog>
</template>

<script lang="ts" setup>
import { reactive, computed, ref, watch } from 'vue'
import { Md5 } from 'ts-md5'
import { UserVO } from '@/types/Base/UserManagement'
import i18n from '@/languages/i18n'
import { hookComponent } from '@/components/system/index'
import { registerUser } from '@/api/base/userManagement'

const formRef = ref()
const emit = defineEmits(['close', 'saveSuccess'])

const props = defineProps<{
  showDialog: boolean
  form: UserVO
}>()

const isShow = computed(() => props.showDialog)

const data = reactive({
  isShowPassword: false,
  form: ref<UserVO>({
    id: 0,
    user_num: '',
    user_name: '',
    auth_string: '',
    email: '',
    sex: '',
    is_valid: true
  }),
  rules: {
    user_num: [],
    user_name: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.userManagement.user_register_name') }!`
    ],
    auth_string: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.userManagement.auth_string') }!`],
    email: [(val: string) => method.verifyMailbox(val)],
    sex: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.userManagement.sex') }!`],
    contact_tel: [],
    is_valid: []
  },
  combobox: ref<{
    sex: {
      label: string
      value: string
    }[]
  }>({
    sex: []
  })
})

const method = reactive({
  // Verify mailbox
  verifyMailbox: (val: string) => {
    if (!val) {
      return true
    }
    const RE = new RegExp(
      /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
    )
    if (RE.test(val)) {
      return true
    }
    return i18n.global.t('system.tips.vaildEmail')
  },
  // Get the options required by the drop-down box
  getCombobox: () => {
    // Static drop-down box
    const sexOptions = ['male', 'female']
    data.combobox.sex = []
    for (const sex of sexOptions) {
      data.combobox.sex.push({
        label: i18n.global.t(`system.combobox.sex.${ sex }`),
        value: sex
      })
    }
  },
  closeDialog: () => {
    emit('close')
  },
  submit: async () => {
    const { valid } = await formRef.value.validate()

    if (valid) {
      const form = {
        id: 0,
        user_num: '',
        user_name: data.form.user_name,
        auth_string: Md5.hashStr(data.form.auth_string as string),
        email: data.form.email,
        sex: data.form.sex,
        is_valid: true
      }

      const { data: res } = await registerUser(form)
      if (!res.isSuccess) {
        hookComponent.$message({
          type: 'error',
          content: res.errorMessage
        })
        return
      }
      hookComponent.$message({
        type: 'success',
        content: `${ i18n.global.t('system.page.submit') }${ i18n.global.t('system.tips.success') }`
      })
      emit('saveSuccess')
    } else {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
    }
  }
})

watch(
  () => isShow.value,
  (val) => {
    if (val) {
      method.getCombobox()
      data.form = props.form
    }
  }
)
</script>

<style scoped lang="less">
.v-form {
  div {
    margin-bottom: 7px;
  }
}
</style>
在这里插入图片描述
在这里插入图片描述

2.接口请求

[AllowAnonymous]
[HttpPost("register")]
public async Task<ResultModel<string>> Register(RegisterViewModel viewModel)
{
    var (flag, msg) = await _userService.Register(viewModel);
    if (flag)
    {
        return ResultModel<string>.Success(msg);
    }
    else
    {
        return ResultModel<string>.Error(msg);
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
    • 1.业务流程说明
      • 2.登录业务的相关技术点
        • 3.登录—token原理分析
          • 4.前端框架设计
          • 一、登录功能的实现
            • 1.登录页面设计
              • 2.登录逻辑功能实现
                • 2.1 登录逻辑页面
                • 2.2 接口请求
            • 二、注册逻辑功能实现
              • 1.注册页面设计
                • 2.接口请求
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档