vue-element-plus-admin(vue3)二次开发入门指南(3)-修改登录模块,接口通用返回JSON模型 与后台进行交互

部署运行你感兴趣的模型镜像

 修改前端登录模块

 修改页面逻辑

   1. 根据自己的需要修改登录页,假设需页面是如下所示

2. 找到页面文件夹src-》views-》Login, 此文件夹就是所有的login模块所需要的页面内容

  

3. 找到页面文件夹src-》api-》login, 这里包含所有和后端交互的对象和接口访问功能

基于我们登录请求的参数包括 用户名, 密码 和 登录类型,修改api/login/types.ts 文件如下

//用于login 页面请求后端模型对象
export interface UserLoginType {
  username: string
  password: string
  loginType: string
}
//用于登录后返回内容的模型对象
export interface UserType {
  username: string
  password: string
  role: string
  roleId: string
  loginType: string
  token: string
}

修改api/login/index.ts 文件如下

//使用axios框架封装的请求
import request from '@/axios'
import type { UserLoginType, UserType } from './types'

interface RoleParams {
  roleName?: string
  userName?: string
}

//新建与后台交互的apis
//登录的后台的URL
const LOGIN_API_URL = '/config/console/api/login'
//注销登录的后台URL
const LOGIN_OUT_API_URL = '/config/console/api/route/loginOut'
//获得左边导航栏配置的URL
const ROLE_API_URL = '/config/console/api/route/roleList'

//登录请求方法
export const loginApi = (data: UserLoginType): Promise<IResponse<UserType>> => {
  console.log(data)
  return request.post({ url: LOGIN_API_URL, data })
}

//注销登录的方法
export const loginOutApi = (): Promise<IResponse> => {
  return request.get({ url: LOGIN_OUT_API_URL })
}

//获得后端导航配置的端口
export const getUserRoleApi = (
  params: RoleParams
): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
  console.log('请求UserRoleAPi', params)
  return request.get({ url: ROLE_API_URL, params })
}

export const getUserListApi = ({ params }: AxiosConfig) => {
  return request.get<{
    code: string
    data: {
      list: UserType[]
      total: number
    }
  }>({ url: '/mock/user/list', params })
}

export const getAdminRoleApi = (
  params: RoleParams
): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
  console.log('请求adminRoleAPi', params)
  return request.get({ url: '/mock/role/list', params })
}

export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
  console.log('请求TestRoleAPi', params)
  return request.get({ url: '/mock/role/list2', params })
}

NOTE:我们看到我们定义后端URL路由的时候是以/config/console/api,这是为了同时支持mock和后端访问,因为在vite里设置的mock代理是以api开头的,为了区分没有以api开头,大家可以根据自己需要自己定义后端路径

修改src-》views-》Login/components/LoginForm.vue 文件

<script setup lang="tsx">
import { reactive, ref, watch, onMounted, unref } from 'vue'
import { Form, FormSchema } from '@/components/Form'
import { useI18n } from '@/hooks/web/useI18n'
import { ElCheckbox, ElLink } from 'element-plus'
import { useForm } from '@/hooks/web/useForm'
//引入后端交互端口
import { loginApi, getTestRoleApi, getUserRoleApi } from '@/api/login'
import { useAppStore } from '@/store/modules/app'
import { usePermissionStore } from '@/store/modules/permission'
import { useRouter } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
import { UserType, UserLoginType } from '@/api/login/types'
import { useValidator } from '@/hooks/web/useValidator'
import { useUserStore } from '@/store/modules/user'
import { BaseButton } from '@/components/Button'

const { required } = useValidator()

const emit = defineEmits(['to-register'])

const appStore = useAppStore()

const userStore = useUserStore()

const permissionStore = usePermissionStore()

const { currentRoute, addRoute, push } = useRouter()
//用来国际化的键值对定义
const { t } = useI18n()

//检验登录页面的
const rules = {
  username: [required()],
  password: [required()],
  loginType: [required()]
}

//定义登录form表单的内容
const schema = reactive<FormSchema[]>([
  //表单的标题
  {
    field: 'title',
    colProps: {
      span: 24
    },
    formItemProps: {
      slots: {
        default: () => {
          ////国际化文件中定义
          return <h2 class="text-2xl font-bold text-center w-[100%]">{t('login.login')}</h2>
        }
      }
    }
  },
  //form 用户名input项
  {
    field: 'username',
    //国际化文件中定义
    label: t('login.username'),
    // value: 'admin',
    component: 'Input',
    colProps: {
      span: 24
    },
    componentProps: {
      placeholder: 'admin or test'
    }
  },
  //form 密码input项
  {
    field: 'password',
    label: t('login.password'),
    // value: 'admin',
    component: 'InputPassword',
    colProps: {
      span: 24
    },
    componentProps: {
      style: {
        width: '100%'
      },
      //提示
      placeholder: 'admin or test',
      // 按下enter键触发登录
      onKeydown: (_e: any) => {
        if (_e.key === 'Enter') {
          _e.stopPropagation() // 阻止事件冒泡
          signIn()
        }
      }
    }
  },
  //form logintype 的select 选项
  {
    field: 'loginType',
    //国际化定义
    label: t('login.loginType'),
    // value: 'admin',
    component: 'Select',
    colProps: {
      span: 24
    },
    componentProps: {
      style: {
        width: '100%'
      },
      //placeholder: 'sign type',
      // 按下enter键触发登
      options: [
        { label: 'SAP', value: 'secSAPR3' },
        { label: 'Enterprise', value: 'secEnterprise' }
      ]
    },
    value: 'secSAPR3'
  },
  {
    field: 'tool',
    colProps: {
      span: 24
    },
    formItemProps: {
      slots: {
        default: () => {
          return (
            <>
              <div class="flex justify-between items-center w-[100%]">
                <ElCheckbox v-model={remember.value} label={t('login.remember')} size="small" />
              </div>
            </>
          )
        }
      }
    }
  },
  {
    field: 'login',
    colProps: {
      span: 24
    },
    formItemProps: {
      slots: {
        default: () => {
          return (
            <>
              <div class="w-[100%]">
                <BaseButton
                  loading={loading.value}
                  type="primary"
                  class="w-[100%]"
                  onClick={signIn}
                >
                  {t('login.login')}
                </BaseButton>
              </div>
            </>
          )
        }
      }
    }
  }
  //删除不需要的选项
  /** 
  {
    field: 'other',
    component: 'Divider',
    label: t('login.otherLogin'),
    componentProps: {
      contentPosition: 'center'
    }
  },

  {
    field: 'otherIcon',
    colProps: {
      span: 24
    },
    formItemProps: {
      slots: {
        default: () => {
          return (
            <>
              <div class="flex justify-between w-[100%]">
                <Icon
                  icon="vi-ant-design:github-filled"
                  size={iconSize}
                  class="cursor-pointer ant-icon"
                  color={iconColor}
                  hoverColor={hoverColor}
                />
                <Icon
                  icon="vi-ant-design:wechat-filled"
                  size={iconSize}
                  class="cursor-pointer ant-icon"
                  color={iconColor}
                  hoverColor={hoverColor}
                />
                <Icon
                  icon="vi-ant-design:alipay-circle-filled"
                  size={iconSize}
                  color={iconColor}
                  hoverColor={hoverColor}
                  class="cursor-pointer ant-icon"
                />
                <Icon
                  icon="vi-ant-design:weibo-circle-filled"
                  size={iconSize}
                  color={iconColor}
                  hoverColor={hoverColor}
                  class="cursor-pointer ant-icon"
                />
              </div>
            </>
          )
        }
      }
    }
  }
    */
])

const iconSize = 30

const remember = ref(userStore.getRememberMe)

//对记住密码进行重新赋值
const initLoginInfo = () => {
  const loginInfo = userStore.getLoginInfo
  if (loginInfo) {
    const { username, password, loginType } = loginInfo
    setValues({ username, password, loginType })
  }
}

//在登录页面onMounted 阶段进行操作()
onMounted(() => {
  initLoginInfo()
})

const { formRegister, formMethods } = useForm()
const { getFormData, getElFormExpose, setValues } = formMethods

const loading = ref(false)

const iconColor = '#999'

const hoverColor = 'var(--el-color-primary)'

const redirect = ref<string>('')

watch(
  () => currentRoute.value,
  (route: RouteLocationNormalizedLoaded) => {
    redirect.value = route?.query?.redirect as string
  },
  {
    immediate: true
  }
)

// 登录整个流程
const signIn = async () => {
  //得到验证表单对象
  const formRef = await getElFormExpose()
  //验证执行
  await formRef?.validate(async (isValid) => {
    if (isValid) {
      loading.value = true
      //得到login的请求参数内容
      const formData = await getFormData<UserLoginType>()

      try {
        //访问后台并返回登录结果
        const res = await loginApi(formData)

        console.log(res)
        //是否有内容
        if (res) {
          // 是否记住我
          if (unref(remember)) {
            //如何记住我,把登录的参数存储在本地浏览器
            userStore.setLoginInfo({
              username: formData.username,
              password: formData.password,
              loginType: formData.loginType
            })
          } else {
            //如果没有选择记住我清空本地登录参数
            userStore.setLoginInfo(undefined)
          }
          //存储到本地浏览器的内容
          userStore.setRememberMe(unref(remember))
          userStore.setUserInfo(res.data)
          //设置返回的JWT token内容
          userStore.setToken(res.data.token)
          //设置登录后每次请求后端在http头中的token名称
          userStore.setTokenKey('token')

          // 是否使用动态路由
          if (appStore.getDynamicRouter) {
            //使用动态路由,从后端取得导航栏内容
            console.log('run dynamic route')
            getRole()
          } else {
            console.log('run static route')
            await permissionStore.generateRoutes('static').catch(() => {})
            permissionStore.getAddRouters.forEach((route) => {
              addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
            })
            permissionStore.setIsAddRouters(true)
            push({ path: redirect.value || permissionStore.addRouters[0].path })
          }
        }
      } finally {
        loading.value = false
      }
    }
  })
}

// 获取角色信息
const getRole = async () => {
  const formData = await getFormData<UserType>()
  const params = { userName: formData.username }
  //获得服务器角色
  const res =
    appStore.getDynamicRouter && appStore.getServerDynamicRouter
      ? await getUserRoleApi(params)
      : await getTestRoleApi(params)
  console.log('dynamic route data', JSON.stringify(res))

  if (res) {
    const routers = res.data || []
    userStore.setRoleRouters(routers)
    appStore.getDynamicRouter && appStore.getServerDynamicRouter
      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})

    permissionStore.getAddRouters.forEach((route) => {
      addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
    })
    permissionStore.setIsAddRouters(true)
    push({ path: redirect.value || permissionStore.addRouters[0].path })
  }
}

// 去注册页面, 我们不需要此功能
const toRegister = () => {
  emit('to-register')
}
</script>

<template>
  <Form
    :schema="schema"
    :rules="rules"
    label-position="top"
    hide-required-asterisk
    size="large"
    class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
    @register="formRegister"
  />
</template>

修改修改src-》views-》Login/Login.vue 文件内容如下

<script setup lang="ts">
import { LoginForm } from './components'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { LocaleDropdown } from '@/components/LocaleDropdown'
import { useI18n } from '@/hooks/web/useI18n'
import { underlineToHump } from '@/utils'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
import { ref } from 'vue'
import { ElScrollbar } from 'element-plus'

const { getPrefixCls } = useDesign()

const prefixCls = getPrefixCls('login')

const appStore = useAppStore()

const { t } = useI18n()

const isLogin = ref(true)

const toRegister = () => {
  isLogin.value = false
}

const toLogin = () => {
  isLogin.value = true
}
</script>

<template>
  <div
    :class="prefixCls"
    class="h-[100%] relative lt-xl:bg-[var(--login-bg-color)] lt-sm:px-10px lt-xl:px-10px lt-md:px-10px"
  >
    <ElScrollbar class="h-full">
      <div class="relative flex mx-auto min-h-100vh">
        <div
          :class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
        >
          <div class="flex items-center relative text-white">
            <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
            <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
          </div>
          <div class="flex justify-center items-center h-[calc(100%-60px)]">
            <TransitionGroup
              appear
              tag="div"
              enter-active-class="animate__animated animate__bounceInLeft"
            >
              <!-- 定义在国际化的欢迎词-->
              <img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
              <div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
              <div class="mt-5 font-normal text-white text-14px" key="3">
                {{ t('login.message') }}
              </div>
            </TransitionGroup>
          </div>
        </div>
        <div class="flex-1 p-30px lt-sm:p-10px dark:bg-[var(--login-bg-color)] relative">
          <div
            class="flex justify-between items-center text-white at-2xl:justify-end at-xl:justify-end"
          >
            <div class="flex items-center at-2xl:hidden at-xl:hidden">
              <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
              <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
            </div>

            <div class="flex justify-end items-center space-x-10px">
              <ThemeSwitch />
              <LocaleDropdown class="lt-xl:text-white dark:text-white" />
            </div>
          </div>
          <Transition appear enter-active-class="animate__animated animate__bounceInRight">
            <div
              class="h-full flex items-center m-auto w-[100%] at-2xl:max-w-500px at-xl:max-w-500px at-md:max-w-500px at-lg:max-w-500px"
            >
              <!--
              <LoginForm
                v-if="isLogin"
                class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
                @to-register="toRegister"
              />
              <RegisterForm
                v-else
                class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
                @to-login="toLogin"
              />
              引入修改后的新的 登录页面
              -->
              <LoginForm class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white" />
            </div>
          </Transition>
        </div>
      </div>
    </ElScrollbar>
  </div>
</template>

<style lang="less" scoped>
@prefix-cls: ~'@{adminNamespace}-login';

.@{prefix-cls} {
  overflow: auto;

  &__left {
    &::before {
      position: absolute;
      top: 0;
      left: 0;
      z-index: -1;
      width: 100%;
      height: 100%;
      background-image: url('@/assets/svgs/login-bg.svg');
      background-position: center;
      background-repeat: no-repeat;
      content: '';
    }
  }
}
</style>

修改后页面如图所示

Note:代码里还有很多不需要的内容,大家可以根据自己的需求删除

修改国际化显示的标签

我们看到欢迎词和登录类型没有显示我们想要的,接下来我们来设置这些在国际化下的键值对,使我们的程序支持多中语言

首先找到src-》locales文件夹,国际化的文件都在此文件夹下

我们看到LoginForm.vue 中的登录类型的定义的key值是login.loginType,如下图

我们在src-》locales/zh-CN.ts 里找到key,在里面添加loginType配置项

在locales/zh-CN.ts 修改如下图

修改后,登录页面如下图

如果想让页面同时支持英文,只需要在在在locales/en.ts 修改如下图

点击登录的页面的语言选择就可以改变语言选择

选择English就可以显示英文界面

修改欢迎词,只需要修改locales/zh-CN.ts 文件中被src-》views-》Login/Login.vue引用的的login.welcome和login.message 配置项

配置项如下图

显示如下图

创建后端java web 内容

本文使用的spring boot webflux作为web后台,后台只是做数据的模拟,没有进行后台逻辑设计,对于其他语言经验的小伙伴,可以非常容易的替换其他web后台。

 具体项目请下载: git clone https://gitee.com/tigershi123/finance-back-parent.git

根据前端src/api/login文件夹下API 交互,创建后台模型

根据前端的模型

//用于login 页面请求后端模型对象
export interface UserLoginType {
  username: string
  password: string
  loginType: string
}
//用于登录后返回内容的模型对象
export interface UserType {
  username: string
  password: string
  role: string
  roleId: string
  loginType: string
  token: string
}
//用于role分类
interface RoleParams {
  roleName?: string
  userName?: string
}

根据UserLoginType创建如下java模型

public class User implements Serializable {


    private static final long serialVersionUID = 7614263512668832594L;


    private String username;
    private String password;
    private String loginType;
    private String newPassword;

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNewPassword() {
        return newPassword;
    }

    public void setNewPassword(String newPassword) {
        this.newPassword = newPassword;
    }
}

根据RoleParams创建java模型

package com.finance.vue3.model;

import java.io.Serial;
import java.io.Serializable;

public class RoleParams implements Serializable {
    @Serial
    private static final long serialVersionUID = -6811801571410972581L;
    private String roleName;
    private String userName;

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

前端的UserType使用的Java的HashMap类做代替,如果需要可以根据UserType生成java类

根据/type/router.d.ts(注意是项目根文件夹下的/type文件夹)文件中的AppCustomRouteRecordRaw interface 如图

此模型主要是用于左边导航栏的动态路由数据的组织

package com.finance.vue3.model;

import java.io.Serial;
import java.io.Serializable;
import java.util.List;

public class AppCustomRouteRecordRaw implements Serializable {

        @Serial
        private static final long serialVersionUID = -4044078442792309744L;

        private String name;
        private RouteMetaCustom meta;
        private String component;
        private String path;
        private String redirect;
        private List<AppCustomRouteRecordRaw> children;

        public List<AppCustomRouteRecordRaw> getChildren() {
                return children;
        }

        public void setChildren(List<AppCustomRouteRecordRaw> children) {
                this.children = children;
        }

        public String getComponent() {
                return component;
        }

        public void setComponent(String component) {
                this.component = component;
        }

        public RouteMetaCustom getMeta() {
                return meta;
        }

        public void setMeta(RouteMetaCustom meta) {
                this.meta = meta;
        }

        public String getName() {
                return name;
        }

        public void setName(String name) {
                this.name = name;
        }

        public String getPath() {
                return path;
        }

        public void setPath(String path) {
                this.path = path;
        }

        public String getRedirect() {
                return redirect;
        }

        public void setRedirect(String redirect) {
                this.redirect = redirect;
        }
}

JAVA业务逻辑

package com.finance.vue3.web.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.finance.vue3.model.*;
import com.finance.vue3.service.JWTService;
import com.finance.vue3.utils.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Component
public class HttpLoginUI {
    //user
    private final static String routeLogin = "/config/console/api/login";
    private final static String routeLoginOut = "/config/console/api/route/loginOut";
    private final static String routeRoleList= "/config/console/api/route/roleList";

    private static Logger logger = LoggerFactory.getLogger(HttpLoginUI.class);
    @Autowired
    private JWTService jwtService;

    //login登录
    @Bean
    public RouterFunction<ServerResponse> loginRouterFunction(ResourceLoader resourceLoader) {
        return route(POST(routeLogin).and(accept(MediaType.APPLICATION_JSON)), request -> {
                    String bastPath = request.uri().toString();
                    logger.info("request login path:{}", bastPath);


                    Mono<User> loginUserMono = request.bodyToMono(User.class);

                    return loginUserMono.flatMap(user -> {
                        logger.info("-username----------{}----{}",user.getUsername(), user.getLoginType());
                        try {
                            String token = jwtService.createAPPToken(user);
                            Map<String, String> map = new HashMap<String, String>();
                            logger.info("responseToken:{}" , token);
                            map.put("username", user.getUsername());
                            map.put("token", token);
                            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                                    .body( Mono.just(JSON.toJSONString( APIResponseDTO.buildSuccess(map))), String.class);

                        }catch (Exception e){
                            APIResponseDTO respErr = APIResponseDTO.buildFailure(-1);
                            respErr.setMsg("error");

                            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                                    .body( Mono.just(JSON.toJSONString(respErr)), String.class);
                        }

                    });
                }
        );
    }



    //loginOut退出
    @Bean
    public RouterFunction<ServerResponse> loginOutRouterFunction(ResourceLoader resourceLoader) {

        return route(GET(routeLoginOut).and(accept(MediaType.APPLICATION_JSON)), request -> {
                    String bastPath = request.uri().toString();
                    logger.info("request login path:{}", bastPath);
                    Mono<User> loginUserMono = request.bodyToMono(User.class);
                    return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                            .body( Mono.just(JSON.toJSONString( APIResponseDTO.buildSuccess(null))), String.class);
                }
        );
    }



    //roleList根据用户名拿到路由
    @Bean
    public RouterFunction<ServerResponse> roleListRouterFunction(ResourceLoader resourceLoader) {
        return route(GET(routeRoleList).and(accept(MediaType.APPLICATION_JSON)), request -> {
                    String bastPath = request.uri().toString();
                    logger.info("request roleList path:{}", bastPath);

                    String username = request.queryParam("userName").get().trim();
                    logger.info("userName;{}", username);
                    String str = new String(getRoleListFileContent());
                    List<AppCustomRouteRecordRaw> list = JSONArray.parseArray(str, AppCustomRouteRecordRaw.class);
                    String val = JSON.toJSONString(APIResponseDTO.buildSuccess(list), SerializerFeature.PrettyFormat);
                    logger.info("result:{}", val);
                    return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                            .body(BodyInserters.fromValue(val));
                }
        );
    }

    
    private byte[] getRoleListFileContent(){
        ClassPathResource classPathResource = new ClassPathResource("roleList.json");
        InputStream inputStream = null;
        try {
            inputStream = classPathResource.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return FileUtils.readFile(inputStream);
    }
    

}

roleList 文件是根据mock的导航类内容组织的

[
  {
    "path": "/dashboard",
    "component": "#",
    "redirect": "/dashboard/analysis",
    "name": "Dashboard",
    "meta": {
      "title": "router.dashboard",
      "icon": "vi-ant-design:dashboard-filled",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "analysis",
        "component": "views/Dashboard/Analysis",
        "name": "Analysis",
        "meta": {
          "title": "router.analysis",
          "noCache": true,
          "affix": true
        }
      },
      {
        "path": "workplace",
        "component": "views/Dashboard/Workplace",
        "name": "Workplace",
        "meta": {
          "title": "router.workplace",
          "noCache": true,
          "affix": true
        }
      }
    ]
  },
  {
    "path": "/external-link",
    "component": "#",
    "meta": {},
    "name": "ExternalLink",
    "children": [
      {
        "path": "https://element-plus-admin-doc.cn/",
        "name": "DocumentLink",
        "meta": {
          "title": "router.document",
          "icon": "vi-clarity:document-solid"
        }
      }
    ]
  },
  {
    "path": "/guide",
    "component": "#",
    "name": "Guide",
    "meta": {},
    "children": [
      {
        "path": "index",
        "component": "views/Guide/Guide",
        "name": "GuideDemo",
        "meta": {
          "title": "router.guide",
          "icon": "vi-cib:telegram-plane"
        }
      }
    ]
  },
  {
    "path": "/components",
    "component": "#",
    "redirect": "/components/form/default-form",
    "name": "ComponentsDemo",
    "meta": {
      "title": "router.component",
      "icon": "vi-bx:bxs-component",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "form",
        "component": "##",
        "name": "Form",
        "meta": {
          "title": "router.form",
          "alwaysShow": true
        },
        "children": [
          {
            "path": "default-form",
            "component": "views/Components/Form/DefaultForm",
            "name": "DefaultForm",
            "meta": {
              "title": "router.defaultForm"
            }
          },
          {
            "path": "use-form",
            "component": "views/Components/Form/UseFormDemo",
            "name": "UseForm",
            "meta": {
              "title": "UseForm"
            }
          }
        ]
      },
      {
        "path": "table",
        "component": "##",
        "redirect": "/components/table/default-table",
        "name": "TableDemo",
        "meta": {
          "title": "router.table",
          "alwaysShow": true
        },
        "children": [
          {
            "path": "default-table",
            "component": "views/Components/Table/DefaultTable",
            "name": "DefaultTable",
            "meta": {
              "title": "router.defaultTable"
            }
          },
          {
            "path": "use-table",
            "component": "views/Components/Table/UseTableDemo",
            "name": "UseTable",
            "meta": {
              "title": "UseTable"
            }
          },
          {
            "path": "tree-table",
            "component": "views/Components/Table/TreeTable",
            "name": "TreeTable",
            "meta": {
              "title": "TreeTable"
            }
          },
          {
            "path": "table-image-preview",
            "component": "views/Components/Table/TableImagePreview",
            "name": "TableImagePreview",
            "meta": {
              "title": "router.PicturePreview"
            }
          },
          {
            "path": "table-video-preview",
            "component": "views/Components/Table/TableVideoPreview",
            "name": "TableVideoPreview",
            "meta": {
              "title": "router.tableVideoPreview"
            }
          },
          {
            "path": "card-table",
            "component": "views/Components/Table/CardTable",
            "name": "CardTable",
            "meta": {
              "title": "router.cardTable"
            }
          }
        ]
      },
      {
        "path": "editor-demo",
        "component": "##",
        "redirect": "/components/editor-demo/editor",
        "name": "EditorDemo",
        "meta": {
          "title": "router.editor",
          "alwaysShow": true
        },
        "children": [
          {
            "path": "editor",
            "component": "views/Components/Editor/Editor",
            "name": "Editor",
            "meta": {
              "title": "router.richText"
            }
          },
          {
            "path": "json-editor",
            "component": "views/Components/Editor/JsonEditor",
            "name": "JsonEditor",
            "meta": {
              "title": "router.jsonEditor"
            }
          },
          {
            "path": "code-editor",
            "component": "views/Components/Editor/CodeEditor",
            "name": "CodeEditor",
            "meta": {
              "title": "router.codeEditor"
            }
          }
        ]
      },
      {
        "path": "search",
        "component": "views/Components/Search",
        "name": "Search",
        "meta": {
          "title": "router.search"
        }
      },
      {
        "path": "descriptions",
        "component": "views/Components/Descriptions",
        "name": "Descriptions",
        "meta": {
          "title": "router.descriptions"
        }
      },
      {
        "path": "image-viewer",
        "component": "views/Components/ImageViewer",
        "name": "ImageViewer",
        "meta": {
          "title": "router.imageViewer"
        }
      },
      {
        "path": "dialog",
        "component": "views/Components/Dialog",
        "name": "Dialog",
        "meta": {
          "title": "router.dialog"
        }
      },
      {
        "path": "icon",
        "component": "views/Components/Icon",
        "name": "Icon",
        "meta": {
          "title": "router.icon"
        }
      },
      {
        "path": "icon-picker",
        "component": "views/Components/IconPicker",
        "name": "IconPicker",
        "meta": {
          "title": "router.iconPicker"
        }
      },
      {
        "path": "echart",
        "component": "views/Components/Echart",
        "name": "Echart",
        "meta": {
          "title": "router.echart"
        }
      },
      {
        "path": "count-to",
        "component": "views/Components/CountTo",
        "name": "CountTo",
        "meta": {
          "title": "router.countTo"
        }
      },
      {
        "path": "qrcode",
        "component": "views/Components/Qrcode",
        "name": "Qrcode",
        "meta": {
          "title": "router.qrcode"
        }
      },
      {
        "path": "highlight",
        "component": "views/Components/Highlight",
        "name": "Highlight",
        "meta": {
          "title": "router.highlight"
        }
      },
      {
        "path": "infotip",
        "component": "views/Components/Infotip",
        "name": "Infotip",
        "meta": {
          "title": "router.infotip"
        }
      },
      {
        "path": "input-password",
        "component": "views/Components/InputPassword",
        "name": "InputPassword",
        "meta": {
          "title": "router.inputPassword"
        }
      },
      {
        "path": "waterfall",
        "component": "views/Components/Waterfall",
        "name": "Waterfall",
        "meta": {
          "title": "router.waterfall"
        }
      },
      {
        "path": "image-cropping",
        "component": "views/Components/ImageCropping",
        "name": "ImageCropping",
        "meta": {
          "title": "router.imageCropping"
        }
      },
      {
        "path": "video-player",
        "component": "views/Components/VideoPlayer",
        "name": "VideoPlayer",
        "meta": {
          "title": "router.videoPlayer"
        }
      },
      {
        "path": "avatars",
        "component": "views/Components/Avatars",
        "name": "Avatars",
        "meta": {
          "title": "router.avatars"
        }
      },
      {
        "path": "i-agree",
        "component": "views/Components/IAgree",
        "name": "IAgree",
        "meta": {
          "title": "router.iAgree"
        }
      },
      {
        "path": "tree",
        "component": "views/Components/Tree",
        "name": "Tree",
        "meta": {
          "title": "router.tree"
        }
      }
    ]
  },
  {
    "path": "/function",
    "component": "#",
    "redirect": "/function/multipleTabs",
    "name": "Function",
    "meta": {
      "title": "router.function",
      "icon": "vi-ri:function-fill",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "multipleTabs",
        "component": "views/Function/MultipleTabs",
        "name": "MultipleTabs",
        "meta": {
          "title": "router.multipleTabs"
        }
      },
      {
        "path": "multiple-tabs-demo/:id",
        "component": "views/Function/MultipleTabsDemo",
        "name": "MultipleTabsDemo",
        "meta": {
          "hidden": true,
          "title": "router.details",
          "canTo": true
        }
      },
      {
        "path": "request",
        "component": "views/Function/Request",
        "name": "Request",
        "meta": {
          "title": "router.request"
        }
      },
      {
        "path": "test",
        "component": "views/Function/Test",
        "name": "Test",
        "meta": {
          "title": "router.permission",
          "permission": [
            "add",
            "edit",
            "delete"
          ]
        }
      }
    ]
  },
  {
    "path": "/hooks",
    "component": "#",
    "redirect": "/hooks/useWatermark",
    "name": "Hooks",
    "meta": {
      "title": "hooks",
      "icon": "vi-ic:outline-webhook",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "useWatermark",
        "component": "views/hooks/useWatermark",
        "name": "UseWatermark",
        "meta": {
          "title": "useWatermark"
        }
      },
      {
        "path": "useTagsView",
        "component": "views/hooks/useTagsView",
        "name": "UseTagsView",
        "meta": {
          "title": "useTagsView"
        }
      },
      {
        "path": "useValidator",
        "component": "views/hooks/useValidator",
        "name": "UseValidator",
        "meta": {
          "title": "useValidator"
        }
      },
      {
        "path": "useCrudSchemas",
        "component": "views/hooks/useCrudSchemas",
        "name": "UseCrudSchemas",
        "meta": {
          "title": "useCrudSchemas"
        }
      },
      {
        "path": "useClipboard",
        "component": "views/hooks/useClipboard",
        "name": "UseClipboard",
        "meta": {
          "title": "useClipboard"
        }
      },
      {
        "path": "useNetwork",
        "component": "views/hooks/useNetwork",
        "name": "UseNetwork",
        "meta": {
          "title": "useNetwork"
        }
      }
    ]
  },
  {
    "path": "/level",
    "component": "#",
    "redirect": "/level/menu1/menu1-1/menu1-1-1",
    "name": "Level",
    "meta": {
      "title": "router.level",
      "icon": "vi-carbon:skill-level-advanced"
    },
    "children": [
      {
        "path": "menu1",
        "name": "Menu1",
        "component": "##",
        "redirect": "/level/menu1/menu1-1/menu1-1-1",
        "meta": {
          "title": "router.menu1"
        },
        "children": [
          {
            "path": "menu1-1",
            "name": "Menu11",
            "component": "##",
            "redirect": "/level/menu1/menu1-1/menu1-1-1",
            "meta": {
              "title": "router.menu11",
              "alwaysShow": true
            },
            "children": [
              {
                "path": "menu1-1-1",
                "name": "Menu111",
                "component": "views/Level/Menu111",
                "meta": {
                  "title": "router.menu111"
                }
              }
            ]
          },
          {
            "path": "menu1-2",
            "name": "Menu12",
            "component": "views/Level/Menu12",
            "meta": {
              "title": "router.menu12"
            }
          }
        ]
      },
      {
        "path": "menu2",
        "name": "Menu2Demo",
        "component": "views/Level/Menu2",
        "meta": {
          "title": "router.menu2"
        }
      }
    ]
  },
  {
    "path": "/example",
    "component": "#",
    "redirect": "/example/example-dialog",
    "name": "Example",
    "meta": {
      "title": "router.example",
      "icon": "vi-ep:management",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "example-dialog",
        "component": "views/Example/Dialog/ExampleDialog",
        "name": "ExampleDialog",
        "meta": {
          "title": "router.exampleDialog"
        }
      },
      {
        "path": "example-page",
        "component": "views/Example/Page/ExamplePage",
        "name": "ExamplePage",
        "meta": {
          "title": "router.examplePage"
        }
      },
      {
        "path": "example-add",
        "component": "views/Example/Page/ExampleAdd",
        "name": "ExampleAdd",
        "meta": {
          "title": "router.exampleAdd",
          "noTagsView": true,
          "noCache": true,
          "hidden": true,
          "showMainRoute": true,
          "activeMenu": "/example/example-page"
        }
      },
      {
        "path": "example-edit",
        "component": "views/Example/Page/ExampleEdit",
        "name": "ExampleEdit",
        "meta": {
          "title": "router.exampleEdit",
          "noTagsView": true,
          "noCache": true,
          "hidden": true,
          "showMainRoute": true,
          "activeMenu": "/example/example-page"
        }
      },
      {
        "path": "example-detail",
        "component": "views/Example/Page/ExampleDetail",
        "name": "ExampleDetail",
        "meta": {
          "title": "router.exampleDetail",
          "noTagsView": true,
          "noCache": true,
          "hidden": true,
          "showMainRoute": true,
          "activeMenu": "/example/example-page"
        }
      }
    ]
  },
  {
    "path": "/error",
    "component": "#",
    "redirect": "/error/404",
    "name": "Error",
    "meta": {
      "title": "router.errorPage",
      "icon": "vi-ci:error",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "404-demo",
        "component": "views/Error/404",
        "name": "404Demo",
        "meta": {
          "title": "404"
        }
      },
      {
        "path": "403-demo",
        "component": "views/Error/403",
        "name": "403Demo",
        "meta": {
          "title": "403"
        }
      },
      {
        "path": "500-demo",
        "component": "views/Error/500",
        "name": "500Demo",
        "meta": {
          "title": "500"
        }
      }
    ]
  },
  {
    "path": "/authorization",
    "component": "#",
    "redirect": "/authorization/user",
    "name": "Authorization",
    "meta": {
      "title": "router.authorization",
      "icon": "vi-eos-icons:role-binding",
      "alwaysShow": true
    },
    "children": [
      {
        "path": "department",
        "component": "views/Authorization/Department/Department",
        "name": "Department",
        "meta": {
          "title": "router.department"
        }
      },
      {
        "path": "user",
        "component": "views/Authorization/User/User",
        "name": "User",
        "meta": {
          "title": "router.user"
        }
      },
      {
        "path": "menu",
        "component": "views/Authorization/Menu/Menu",
        "name": "Menu",
        "meta": {
          "title": "router.menuManagement"
        }
      },
      {
        "path": "role",
        "component": "views/Authorization/Role/Role",
        "name": "Role",
        "meta": {
          "title": "router.role"
        }
      }
    ]
  }
]

启动后端测试功能

本地启动如图所示:

启动成功后日志如图

下面用postman或者其他工具测试后端接口功能状况

前后端通用业务接口返回模型设计

后端业务通用接口返回模型,此模型用于接口业务的数据返回的封装,无论业务成功与否都返回此模型

package com.finance.vue3.model;

import java.io.Serial;
import java.io.Serializable;

public class APIResponseDTO implements Serializable {

    @Serial
    private static final long serialVersionUID = 918371778765836705L;

    private int code = 0;
    private String msg = "success";
    private Object data;

    public static APIResponseDTO buildSuccess(Object data){
        APIResponseDTO result = new APIResponseDTO();
        result.setData(data);
        return result;
    }
    public static APIResponseDTO buildFailure(int code){
        APIResponseDTO result = new APIResponseDTO();
        result.setCode(code);
        return result;
    }
    public static APIResponseDTO buildFailure(int code, String msg){
        APIResponseDTO result = new APIResponseDTO();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }


}

前端通用接口设计

找到根据/type/global.d.ts(注意是项目根文件夹下的/type文件夹)文件中的declare interface IResponse  如图所示

为了和后端的业务通用接口统一,需要修改为如下所示

  declare interface IResponse<T = any> {
    code: number
    msg: string
    data: T extends any ? T : T & any
  }

这样就可以和后端的APIResponseDTO类统一了

设置vite代理使其login接口访问后端

在根目录vite.config.ts的文件里找到代理设置位置如下图

在里面添加配置项

 proxy: {
        // 选项写法
        '/api': {
          target: 'http://127.0.0.1:8000',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        },
        '/config/console':{
          target:'http://localhost:9200',
          changeOrigin:true,
          secure: false //不验证https证书
        }
      },

添加后如下图所示:

此时就可以使用和后端进行互动登录了

如图,在用户名和密码里输入admin并选择登录类型,点击登录

登录后得到如下图所示

后台日志

注销操作,点击桌面右上角的用户图标,然后选择 退出系统

注销操作的页面代码在src/components/UserInfo/src/UserInfo.vue文件中,如下图

您可能感兴趣的与本文相关的镜像

FLUX.1-dev

FLUX.1-dev

图片生成
FLUX

FLUX.1-dev 是一个由 Black Forest Labs 创立的开源 AI 图像生成模型版本,它以其高质量和类似照片的真实感而闻名,并且比其他模型更有效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值