前端开发(基础)

news2024/11/24 11:42:47

目录

一、Web前端项目初始化

环境准备

创建项目

前端工程化配置

引入组件库

开发规范

全局通用布局

基础布局结构

全局底部栏

动态替换内容

全局顶部栏

通用路由菜单

支持多套布局

请求

请求工具库

全局自定义请求

自动生成请求代码

全局状态管理

全局权限管理

全局项目入口

通用组件 - Markdown 编辑器组件

通用组件 - 图片上传

二、前端基础页面开发

用户模块

用户登录页面

用户注册页面

用户管理页面

管理模块

应用管理

题目管理

评分结果管理

回答管理


一、Web前端项目初始化

环境准备

nodeJS 版本:v18.16.0

检测命令:

node -v

切换和管理 node 版本的工具:GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions

npm 版本:9.5.1

npm -v

创建项目

使用 Vue-CLI 脚手架快速创建 Vue3 的项目:Vue CLI

安装脚手架工具

npm install -g @vue/cli

检测是否安装成功:

vue -V

如果找不到命令,那么建议去重新到安装 npm,重新帮你配置环境变量。

创建项目:

vue create yudada-frontend

手动选择特性:

选择如下特性:

会自动生成代码并安装依赖,然后用 WebStorm 打开项目,在终端执行 npm run serve,能访问网页就成功了。

前端工程化配置

脚手架已经帮我们配置了 Prettier 代码美化、ESLint 自动校验、TypeScript 类型校验、格式化插件等,无需再自行配置。

但是需要在 webstorm 里开启代码美化插件:

在 vue 文件中执行格式化快捷键(CTRL+ALT+L),不报错,表示配置工程化成功。

如果想关闭 ESLint 校验导致的编译错误(项目无法运行),可以关闭 lintOnsave:配置参考 | Vue CLI

在vue.config.js中

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
    lintOnSave: "warning",
});

修改 .eslintrc.js 和 tsconfig.json 可以改变校验规则。

如果不使用脚手架,就需要自己整合这些工具:

  • 代码规范:Getting Started with ESLint - ESLint - Pluggable JavaScript Linter
  • 代码美化:Install · Prettier
  • 直接整合:https://github.com/prettier/eslint-plugin-prettier#recommended-configuration(包括了 https://github.com/prettier/eslint-config-prettier#installation)

引入组件库

引入 Arco Design 组件库:Arco Design Vue1808505663815548929_0.34677924671772464

参考官方文档快速上手:Arco Design Vue

注意版本号要求:vue >= 3.2.0

在WebStorm的终端执行安装:

npm install --save-dev @arco-design/web-vue

改变主入口文件 main.ts:

import { createApp } from "vue";
import App from "./App.vue";
import ArcoVue from "@arco-design/web-vue";
import { createPinia } from "pinia";
import "@arco-design/web-vue/dist/arco.css";
import router from "./router";
import "@/access";

const pinia = createPinia();

createApp(App).use(ArcoVue).use(pinia).use(router).mount("#app");

开发规范

遵循 Vue3 的组合式 API (Composition API):https://cn.vuejs.org/guide/introduction.html#composition-api

示例代码:

<template>
  <div id="xxPage">

  </div>
</template>

<script setup lang="ts">

</script>

<style scoped>
#xxPage {
}

</style>

全局通用布局

基础布局结构

在 layouts 目录下新建一个布局 BasicLayout.vue, 在 App.vue 全局页面入口文件中引入。

App.vue 代码如下:

<template>
  <div id="app">
    <BasicLayout />
  </div>
</template>

<script setup lang="ts">
import BasicLayout from "@/layouts/BasicLayout.vue";
</script>

移除页面内的默认样式:

<style>
#app {
}
</style>

选用 Arco Design 组件库的 Layout 组件:Arco Design Vue

先把【上中下】布局编排好,然后再填充内容:

BasicLayout代码如下:

<template>
  <div id="basicLayout">
    <a-layout style="min-height: 100vh">
      <a-layout-header class="header">头部</a-layout-header>
      <a-layout-content class="content">内容</a-layout-content>
      <a-layout-footer class="footer">底部</a-layout-footer>
    </a-layout>
  </div>
</template>

样式:

<style scoped>
#basicLayout {
}
</style>

全局底部栏

通常用于展示版权信息(BasicLayout代码如下):

<a-layout-footer class="footer">
  <a href="https://www.code-nav.cn" target="_blank">
    编程导航 by 程序员鱼皮
  </a>
</a-layout-footer>

样式:

#basicLayout .footer {
  background: #efefef;
  padding: 16px;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
}

动态替换内容

使用 Vue Router,在 router包下index.ts 配置路由,能够根据访问的页面地址找到不同的文件并加载渲染。

Vue Router:介绍 | Vue Router

BasicLayout代码如下:

<a-layout-content class="content">
  <router-view />
</a-layout-content>

样式,要和底部栏保持一定的外边距,否则内容会被遮住(BasicLayout代码如下):

<style scoped>
#basicLayout .content {
  background: linear-gradient(to right, #fefefe, #fff);
  margin-bottom: 28px;
  padding: 20px;
}
</style>

全局顶部栏

基于 Arco Design 的菜单组件来创建 GlobalHeader 全局顶部栏组件:Arco Design Vue

在基础布局中引入(BasicLayout代码如下):

<a-layout-header class="header">
  <GlobalHeader />
</a-layout-header>

样式:

#basicLayout .header {
  margin-bottom: 16px;
  box-shadow: #eee 1px 1px 5px;
}

GlobalHeader 组件定制,补充网站图标:1808505663815548929_0.3092622000802707

阿里云盘:阿里云盘分享 提取码: 28gu

在根目录下的components包下创建GlobalHeader.vue(写顶部栏的样式):

模板代码:

        <a-menu-item
          key="0"
          :style="{ padding: 0, marginRight: '38px' }"
          disabled
        >
          <div class="titleBar">
            <img class="logo" src="../assets/logo.png" />
            <div class="title">鱼答答</div>
          </div>
        </a-menu-item>

样式:

<style scoped>
#globalHeader {
}

.titleBar {
  display: flex;
  align-items: center;
}

.title {
  margin-left: 16px;
  color: black;
}

.logo {
  height: 48px;
}
</style>

顶部导航栏右侧展示登录状态,左右布局:

  <a-row id="globalHeader" align="center" :wrap="false">
    <a-col flex="auto">
      <a-menu
        mode="horizontal"
      >
        。。。
      </a-menu>
    </a-col>
    <a-col flex="100px">
      <div>
        <a-button type="primary" href="/user/login">登录</a-button>
      </div>
    </a-col>
  </a-row>

通用路由菜单

目标:根据路由配置信息,自动生成菜单内容。实现更通用、更自动的菜单配置。

通用路由菜单组件实现步骤:

  1. 提取通用路由文件
  2. 菜单组件读取路由,动态渲染菜单项
  3. 绑定跳转事件
  4. 同步路由的更新到菜单项高亮
  5. 按需补充更多能力

依次实现:

1)提取通用路由文件

把 router/index.ts 中的路由变量定义为单独的文件 routes.ts,代码如下:

export const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "home",
    component: HomeView,
  },
 {
    path: "/about",
    name: "about",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
  },
];

然后在 router/index.ts 中引入 routes。

import { routes } from "@/router/routes";

2)菜单组件读取路由,动态渲染菜单项

GlobalHeader.vue中:

<script setup lang="ts">
import { routes } from "@/router/routes";
</script>

模板中根据路由数组渲染菜单:

<a-menu-item v-for="item in visibleRoutes" :key="item.path">
          {{ item.name }}
        </a-menu-item>
      </a-menu>

3)绑定跳转事件

4)同步路由的更新到菜单项高亮

同步高亮原理:首先点击菜单项 => 触发点击事件并跳转更新路由 => 更新路由后,同步去更新菜单栏的高亮状态。1808505663815548929_0.016312673248611853

使用 Vue Router 的 afterEach 路由钩子实现:

const router = useRouter();
// 当前选中的菜单项
const selectedKeys = ref(["/"]);
// 路由跳转时,自动更新选中的菜单项
router.afterEach((to, from, failure) => {
  selectedKeys.value = [to.path];
});

模板引入变量:

<a-menu
        mode="horizontal"
        :selected-keys="selectedKeys"
        @menu-item-click="doMenuClick"
      >
</a-menu>

还可以给路由菜单组件增加更多能力。

5)按需补充更多能力(可以参考网上的框架),比如根据配置控制菜单的显隐。

利用 routes 配置的 meta 属性实现。routes.ts 中给路由配置新增一个标志位 hideInMenu,用于判断路由是否显隐:

然后根据该标志位过滤路由数组,仅保留需要展示的元素。

不要用 v-for + v-if 去条件渲染元素,这样会先循环所有的元素,导致性能的浪费。

支持多套布局

需求:不是所有页面都能统一布局,比如用户登录注册页不需要导航栏,因此模板需要多套布局能力。

新建布局 UserLayout,代码如下:

<template>
  <div id="userLayout">
    <a-layout style="height: 100vh">
      <a-layout-header class="header">
        <a-space>
          <img class="logo" src="../assets/logo.png" />
          <div>鱼答答 AI 答题应用平台</div>
        </a-space>
      </a-layout-header>
      <a-layout-content class="content">
        <router-view />
      </a-layout-content>
      <a-layout-footer class="footer">
        <a href="https://www.code-nav.cn" target="_blank">
          编程导航 by 程序员鱼皮
        </a>
      </a-layout-footer>
    </a-layout>
  </div>
</template>

<script setup lang="ts"></script>

<style scoped>
#userLayout {
  text-align: center;
  background: url("https://gw.alipayobjects.com/zos/rmsportal/FfdJeJRQWjEeGTpqgBKj.png")
    0% 0% / 100% 100%;
}

#userLayout .logo {
  width: 48px;
  height: 48px;
}

#userLayout .header {
  margin-top: 16px;
  font-size: 21px;
}

#userLayout .content {
  margin-bottom: 16px;
  padding: 20px;
}

.footer {
  padding: 16px;
  text-align: center;
  background: #efefef;
}
</style>

实现多套布局的思路:

1)使用 vue-router 自带的子路由机制,天然实现布局和嵌套路由,不同的父路由指定不同的 Layout 即可。

比如用户登录注册相关的路由配置:

需要新建 UserLayout、UserLoginView、UserRegisterView 页面,并且在 routes 中引入。

3)在 App.vue 根页面文件,根据路由的路径选择是否使用全局基础布局。

注意,当前这种 app.vue 中通过 if else 区分布局的方式,不是最优雅的。可以通过配置路由的 meta.layout 参数决定是否开启全局基础布局;或者给需要全局基础布局的页面指定 component: BasicLayout。

请求

引入 Axios 请求库 + ⭐️ OpenAPI 前端代码生成

请求工具库

安装请求工具类 Axios

官方文档:https://axioshttp.com/docs/intro

终端:

npm install axios

全局自定义请求

需要自定义全局请求地址等,参考 Axios 官方文档,编写请求配置文件 request.ts。包括全局接口请求地址、超时时间、自定义请求响应拦截器等。

比如可以在全局响应拦截器中,读取出结果中的 data,并校验 code 是否合法,如果是未登录状态,则自动登录。

示例代码如下,其中 withCredentials: true 一定要写,否则无法在发请求时携带 Cookie,就无法完成登录。

request.ts代码如下:

import axios from "axios";
import { Message } from "@arco-design/web-vue";

const myAxios = axios.create({
  baseURL: "http://localhost:8101",
  timeout: 10000,
  withCredentials: true,
});

// 全局请求拦截器
myAxios.interceptors.request.use(
  function (config) {
    // Do something before request is sent
    return config;
  },
  function (error) {
    // Do something with request error
    return Promise.reject(error);
  }
);

// 全局响应拦截器
myAxios.interceptors.response.use(
  function (response) {
    console.log(response);
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    const { data } = response;

    // 未登录
    if (data.code === 40100) {
      // 不是获取用户信息的请求,并且用户目前不是已经在用户登录页面,则跳转到登录页面
      if (
        !response.request.responseURL.includes("user/get/login") &&
        !window.location.pathname.includes("/user/login")
      ) {
        Message.warning("请先登录");
        window.location.href = `/user/login?redirect=${window.location.href}`;
      }
    }

    return response;
  },
  function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  }
);

export default myAxios;

自动生成请求代码

传统情况下,每个请求都要单独编写代码,很麻烦。

推荐使用 OpenAPI 工具,直接自动生成即可:@umijs/openapi - npm

按照官方文档的步骤,先安装:

npm i --save-dev @umijs/openapi

在项目根目录新建 openapi.config.ts,根据自己的需要定制生成的代码:

const { generateService } = require("@umijs/openapi");

generateService({
  requestLibPath: "import request from '@/request'",
  schemaPath: "http://localhost:8101/api/v2/api-docs",
  serversPath: "./src",
});

在 package.json 的 script 中添加 "openapi": "ts-node openapi.config.ts"

如果 ts-node 无法运行,改为 node

执行即可生成请求代码。

全局状态管理

什么是全局状态管理?

所有页面全局共享的变量,而不是局限在某一个页面中。

适合作为全局状态的数据:已登录用户信息(每个页面几乎都要用)

Pinia 是一个主流的状态管理库,使用更简单,入门文档:开始 | Pinia

1)先按照文档引入 Pinia

2)定义状态。在 store 目录下定义 user 模块,定义了用户的存储、远程获取、修改逻辑:

import { defineStore } from "pinia";
import { ref } from "vue";
import { getLoginUserUsingGet } from "@/api/userController";
import ACCESS_ENUM from "@/access/accessEnum";

/**
 * 登录用户信息全局状态
 */
export const useLoginUserStore = defineStore("loginUser", () => {
  const loginUser = ref<API.LoginUserVO>({
    userName: "未登录",
  });

  function setLoginUser(newLoginUser: API.LoginUserVO) {
    loginUser.value = newLoginUser;
  }

  async function fetchLoginUser() {
    const res = await getLoginUserUsingGet();
    if (res.data.code === 0 && res.data.data) {
      loginUser.value = res.data.data;
    } else {
      loginUser.value = { userRole: ACCESS_ENUM.NOT_LOGIN };
    }
  }

  return { loginUser, setLoginUser, fetchLoginUser };
});

3)使用状态。直接使用 store 中导出的状态变量和函数。

可以在首次进入到页面时,尝试获取登录用户信息。修改 App.vue,编写远程获取数据代码:

在任何页面中都可以使用数据,比如在页面中直接展示:

顶部导航栏右侧展示登录状态:

全局权限管理

需求:能够灵活配置每个页面所需要的用户权限,由全局权限管理系统自动校验和拦截,而不需要在每个页面中编写权限校验代码,提高开发效率。

实现方案:

  1. 在路由配置文件, 定义某个路由的访问权限
  2. 使用全局路由监听器,每次访问页面时,根据用户要访问页面的路由权限信息,判断用户是否有对应的访问权限,并进行相应的拦截处理。

新建 NoAuth 无权限页面,内容随便写。

新建 access 目录,所有权限管理相关的代码都放在该目录下,模块化。只要不引入,就不会生效。

1)定义权限枚举文件 accessEnum.ts:

/**
 * 权限定义
 */
const ACCESS_ENUM = {
  NOT_LOGIN: "notLogin",
  USER: "user",
  ADMIN: "admin",
};

export default ACCESS_ENUM;

2)routes.ts 中新增一个测试路由:

3)编写通用的权限校验方法。1808505663815548929_0.7997078501634929

为什么?因为菜单组件中要判断权限、权限拦截也要用到权限判断功能,所以抽离成公共模块。

checkAccess.ts 文件:

import ACCESS_ENUM from "@/access/accessEnum";

/**
 * 检查权限(判断当前登录用户是否具有某个权限)
 * @param loginUser 当前登录用户
 * @param needAccess 需要有的权限
 * @return boolean 有无权限
 */
const checkAccess = (
  loginUser: API.LoginUserVO,
  needAccess = ACCESS_ENUM.NOT_LOGIN
) => {
  // 获取当前登录用户具有的权限(如果没有 loginUser,则表示未登录)
  const loginUserAccess = loginUser?.userRole ?? ACCESS_ENUM.NOT_LOGIN;
  if (needAccess === ACCESS_ENUM.NOT_LOGIN) {
    return true;
  }
  // 如果用户要登录才能访问
  if (needAccess === ACCESS_ENUM.USER) {
    // 如果用户没登录,那么表示无权限
    if (loginUserAccess === ACCESS_ENUM.NOT_LOGIN) {
      return false;
    }
  }
  // 如果管理员才能访问
  if (needAccess === ACCESS_ENUM.ADMIN) {
    // 如果不是管理员,表示无权限
    if (loginUserAccess !== ACCESS_ENUM.ADMIN) {
      return false;
    }
  }
  return true;
};

export default checkAccess;

4)编写全局权限校验核心文件。

逻辑如下:

  1. 首先判断页面是否需要登录权限,如果不需要,直接放行。
  2. 如果页面需要登录权限
    1. 如果用户未登录,则跳转到登录页面。
    2. 如果已登录,判断登录用户的权限是否符合要求,否则跳转到 401 无权限页面。

access包下的index.ts实现代码如下:

import router from "@/router";
import { useLoginUserStore } from "@/store/userStore";
import ACCESS_ENUM from "@/access/accessEnum";
import checkAccess from "@/access/checkAccess";

// 进入页面前,进行权限校验
router.beforeEach(async (to, from, next) => {
  // 获取当前登录用户
  const loginUserStore = useLoginUserStore();
  let loginUser = loginUserStore.loginUser;

  // 如果之前没有尝试获取过登录用户信息,才自动登录
  if (!loginUser || !loginUser.userRole) {
    // 加 await 是为了等待用户登录成功并获取到值后,再执行后续操作
    await loginUserStore.fetchLoginUser();
    loginUser = loginUserStore.loginUser;
  }

  // 当前页面需要的权限
  const needAccess = (to.meta?.access as string) ?? ACCESS_ENUM.NOT_LOGIN;
  // 要跳转的页面必须登录
  if (needAccess !== ACCESS_ENUM.NOT_LOGIN) {
    // 如果没登录,跳转到登录页面
    if (
      !loginUser ||
      !loginUser.userRole ||
      loginUser.userRole === ACCESS_ENUM.NOT_LOGIN
    ) {
      next(`/user/login?redirect=${to.fullPath}`);
    }
    // 如果已经登录了,判断权限是否足够,如果不足,跳转到无权限页面
    if (!checkAccess(loginUser, needAccess)) {
      next("/noAuth");
      return;
    }
  }
  next();
});

在 main.ts 中引入,即可生效权限校验:

import "@/access";

注意,必须保证 pinia 初始化在这段代码执行前,所以需要将 useLoginUserStore() 函数放到 router.beforeEach 参数里。

参考:Using a store outside of a component | Pinia

5)支持全局自动登录。如果是 首次 进入页面,状态为未登陆,则自动登录。

如何区别是否为首次进入页面(还没尝试过获取登录用户)呢?

默认的 loginUser 是没有 userRole 的,只要获取过,哪怕未登录,也可以给设置一个 userRole 为 "notLogin"。

修改 user 状态管理代码:

在 access/index.ts 开头补充自动登录逻辑

之后记得移除 App.vue 中的登录逻辑。

附加需求:根据权限控制菜单显隐。

需求:只有具有权限的菜单,才对用户可见

原理:类似上面的控制路由显示隐藏,只要判断用户没有这个权限,就直接过滤掉。

修改 GlobalHeader 全局导航栏(通用菜单)组件,补充根据权限来过滤菜单的逻辑。

全局项目入口

app.vue 中预留一个可以编写全局初始化逻辑的代码:

通用组件 - Markdown 编辑器组件

为什么用 Markdown?

一套通用的文本编辑语法,可以在各大网站上统一标准、渲染出统一的样式,比较简单易学。

推荐的 Md 编辑器:GitHub - bytedance/bytemd: ByteMD v1 repository

阅读官方文档,下载编辑器主体、以及 gfm(表格支持)插件、highlight 代码高亮插件

npm i @bytemd/vue-next
npm i @bytemd/plugin-highlight @bytemd/plugin-gfm

新建 MdEditor 组件,编写代码:

<template>
  <Editor :value="value" :plugins="plugins" @change="handleChange" />
</template>

<script setup lang="ts">
import gfm from "@bytemd/plugin-gfm";
import highlight from "@bytemd/plugin-highlight";
import { Editor, Viewer } from "@bytemd/vue-next";
import { ref } from "vue";

const plugins = [
  gfm(),
  highlight(),
  // Add more plugins here
];

const value = ref("");

const handleChange = (v: string) => {
  value.value = v;
};
</script>

<style scoped></style>

隐藏编辑器中不需要的操作图标(比如 GitHub 图标):

.bytemd-toolbar-icon.bytemd-tippy.bytemd-tippy-right:last-child {
    display: none;
}

要把 MdEditor 当前输入的值暴露给父组件,便于父组件去使用,同时也是提高组件的通用性,需要定义属性,把 value 和 handleChange 事件交给父组件去管理:

MdEditor 示例代码:

有编辑器就有浏览器,MdViewer 示例代码如下:

通用组件 - 图片上传

先复制现成的组件代码:Arco Design Vue

然后进行修改,先重构为 setup 组合式 API 写法,自定义请求,跑通流程。

然后再改为受控组件,接受外层传来的 url 和 onChange 函数,之后的表单页面就可以引用了。

PictureUploader.vue 代码如下:

<template>
  <a-space direction="vertical" :style="{ width: '100%' }">
    <a-upload
      :fileList="file ? [file] : []"
      :show-file-list="false"
      :custom-request="customRequest"
    >
      <template #upload-button>
        <div
          :class="`arco-upload-list-item${
            file && file.status === 'error'
              ? ' arco-upload-list-item-error'
              : ''
          }`"
        >
          <div
            class="arco-upload-list-picture custom-upload-avatar"
            v-if="file && file.url"
          >
            <img :src="file.url" />
            <div class="arco-upload-list-picture-mask">
              <IconEdit />
            </div>
            <a-progress
              v-if="file.status === 'uploading' && file.percent < 100"
              :percent="file.percent"
              type="circle"
              size="mini"
              :style="{
                position: 'absolute',
                left: '50%',
                top: '50%',
                transform: 'translateX(-50%) translateY(-50%)',
              }"
            />
          </div>
          <div class="arco-upload-picture-card" v-else>
            <div class="arco-upload-picture-card-text">
              <IconPlus />
              <div style="margin-top: 10px; font-weight: 600">上传</div>
            </div>
          </div>
        </div>
      </template>
    </a-upload>
  </a-space>
</template>

<script setup lang="ts">
import { IconEdit, IconPlus } from "@arco-design/web-vue/es/icon";
import { ref, withDefaults, defineProps } from "vue";
import { uploadFileUsingPost } from "@/api/fileController";
import { Message } from "@arco-design/web-vue";

/**
 * 定义组件属性类型
 */
interface Props {
  biz: string;
  onChange?: (url: string) => void;
  value?: string;
}

/**
 * 给组件指定初始值
 */
const props = withDefaults(defineProps<Props>(), {
  value: () => "",
});

const file = ref();
if (props.value) {
  file.value = {
    url: props.value,
    percent: 100,
    status: "done",
  };
}

// 自定义请求
const customRequest = async (option: any) => {
  const { onError, onSuccess, fileItem } = option;

  const res: any = await uploadFileUsingPost(
    { biz: props.biz },
    {},
    fileItem.file
  );
  if (res.data.code === 0 && res.data.data) {
    const url = res.data.data;
    file.value = {
      name: fileItem.name,
      file: fileItem.file,
      url,
    };
    props.onChange?.(url);
    onSuccess();
    console.log(file.value);
  } else {
    Message.error("上传失败," + res.data.message || "");
    onError(new Error(res.data.message));
  }
};
</script>

二、前端基础页面开发

用户模块

各项目通用

用户模块路由配置:

用户登录页面

使用表单组件

<template>
  <div id="userLoginPage">
    <h2 style="margin-bottom: 16px">用户登录</h2>
    <a-form
      :model="form"
      :style="{ width: '480px', margin: '0 auto' }"
      label-align="left"
      auto-label-width
      @submit="handleSubmit"
    >
      <a-form-item field="userAccount" label="账号">
        <a-input v-model="form.userAccount" placeholder="请输入账号" />
      </a-form-item>
      <a-form-item field="userPassword" tooltip="密码不小于 8 位" label="密码">
        <a-input-password
          v-model="form.userPassword"
          placeholder="请输入密码"
        />
      </a-form-item>
      <a-form-item>
        <div
          style="
            display: flex;
            width: 100%;
            align-items: center;
            justify-content: space-between;
          "
        >
          <a-button type="primary" html-type="submit" style="width: 120px">
            登录
          </a-button>
          <a-link href="/user/register">新用户注册</a-link>
        </div>
      </a-form-item>
    </a-form>
  </div>
</template>

<script setup lang="ts">
import { reactive } from "vue";
import API from "@/api";
import { userLoginUsingPost } from "@/api/userController";
import { useLoginUserStore } from "@/store/userStore";
import message from "@arco-design/web-vue/es/message";
import { useRouter } from "vue-router";

const loginUserStore = useLoginUserStore();
const router = useRouter();

const form = reactive({
  userAccount: "",
  userPassword: "",
} as API.UserLoginRequest);

/**
 * 提交
 */
const handleSubmit = async () => {
  const res = await userLoginUsingPost(form);
  if (res.data.code === 0) {
    await loginUserStore.fetchLoginUser();
    message.success("登录成功");
    router.push({
      path: "/",
      replace: true,
    });
  } else {
    message.error("登录失败," + res.data.message);
  }
};
</script>

用户注册页面

参考用户登录页面,同样使用表单组件。1808505663815548929_0.27247751987834223

需要让两个页面之间能够相互跳转。

用户管理页面

需求:管理用户 - 增删改查(仅管理员可用)P11808505663815548929_0.04351610257466798

新增路由:

编写页面:上方搜索栏,下方表格。

1)先开发表格。

使用表格组件:

定义表格列:

自定义渲染表格列:

然后需要在 columns 中补充插槽配置:

搜索条件:

数据加载:

2)再开发搜索表单

创建和更新功能其实就是表单页面。

管理模块

应用管理

题目管理

评分结果管理

回答管理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电力调度台如何助力电力指挥中心更智慧

在现代电力系统的复杂运行环境中&#xff0c;电力调度台正逐渐成为电力指挥中心实现智慧化管理的关键力量。 电力调度台具备强大的信息集成与处理能力。它能够将来自不同监测系统、传感器和数据源的海量数据汇聚一处&#xff0c;包括电力设备的实时运行状态、电力负荷的动态变化…

应急靶场(4):Windows Server 2019 - Web3

目录 一、攻击者的两个IP地址 二、隐藏用户名称 三、黑客遗留下的flag【3个】 下载好靶场&#xff08;前来挑战&#xff01;应急响应靶机训练-Web3&#xff09;并搭建好环境&#xff0c;使用帐号密码&#xff08;administrator / xj123456&#xff09;登录靶机。 一、攻击者的两…

张幼玲:心中有火,眼里有光照医路

在我们的传统社会中&#xff0c;男科医生这一职业往往被人们带着异样的眼光看待。然而&#xff0c;张幼玲却选择了这一领域&#xff0c;成为了一名专业男科医生。他以其丰富的临床经验、高超的医术和对患者的关爱&#xff0c;赢得了患者和社会的广泛赞誉。 张幼玲出生于一个中医…

ASP.NET Core----基础学习06----将所有数据在页面中显示 布局页面的使用

文章目录 1. 将数据以list的形式展示在页面中2. 布局页面的使用3. 自定义设置视图文件是否需要加载的JS 1. 将数据以list的形式展示在页面中 step1:在接口文件中添加新的方法GetAllStudents&#xff08;&#xff09; step2:在mock的数据中添加方法GetAllStudents&#xff08;&a…

7/13 - 7/15

vo.setId(rs.getLong("id"))什么意思&#xff1f; vo.setId(rs.getLong("id")); 这行代码是在Java中使用ResultSet对象&#xff08;通常用于从数据库中检索数据&#xff09;获取一个名为"id"的列&#xff0c;并将其作为long类型设置为一个对象…

Billu_b0x靶机

信息收集 使用arp-scan 生成网络接口地址来查看ip 输入命令&#xff1a; arp-scan -l 可以查看到我们的目标ip为192.168.187.153 nmap扫描端口开放 输入命令&#xff1a; nmap -min-rate 10000 -p- 192.168.187.153 可以看到开放2个端口 nmap扫描端口信息 输入命令&…

工作中项目git如何管理,冲突,push不上去如何解决

主要涉及的知识点 现在公司中一般的git仓库的管理方式是怎么样的代码为什么push不上线上仓库如何解决代码冲突 分支管理方式 git checkout -b 分支名字 是创建并切换到分支 git push origin 分支名字 推到远程仓库分支上 主流的git管理方式 共用一个仓库&#xff0c;不同…

Golang | Leetcode Golang题解之第237题删除链表中的节点

题目&#xff1a; 题解&#xff1a; func deleteNode(node *ListNode) {node.Val node.Next.Valnode.Next node.Next.Next }

解决宝塔Spring Boot项目获取不到环境变量的问题

问题描述 在使用宝塔面板管理Spring Boot项目时&#xff0c;可能会遇到代码无法获取 /etc/profile 文件中设置的Linux环境变量的问题。虽然在SSH终端中可以正常获取&#xff0c;但在通过宝塔面板启动的Spring Boot项目中&#xff0c;环境变量却无法被读取。 解决方案&#xf…

TS 入门(三):Typescript函数与对象类型

目录 前言回顾1. 函数类型a. 基本函数类型b. 可选参数和默认参数c. 剩余参数 2. 对象类型a. 基本对象类型b. 可选属性和只读属性 3. 类型别名和接口a. 类型别名b. 接口扩展 4. 类型推断和上下文类型a. 类型推断b. 上下文类型 扩展知识点&#xff1a;函数重载结语 前言 在前两章…

实验06 持续集成测试

知识点 集成测试定义 集成测试是将多个单元组合起来形成更大的单元&#xff0c;并测试它们是否能协同工作形成子系统。一种旨在暴露单元接口之间、组件/系统间交互或协同工作时所存在的缺陷的测试。 集成测试关注的问题 模块间数据传递是否正确。一个模块的功能是否影响另一…

[iOS]内存分区

[iOS]内存分区 文章目录 [iOS]内存分区五大分区栈区堆区全局区常量区代码区验证内存使用注意事项总结 函数栈堆栈溢出栈的作用 参考博客 在iOS中&#xff0c;内存主要分为栈区、堆区、全局区、常量区、代码区五大区域 还记得OC是C的超类 所以C的内存分区也是一样的 iOS系统中&a…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑频率不同响应阶段的惯量评估优化策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

嵌入式人工智能(6-树莓派4B按键输入控制LED)

1、按键 按键的原理都是一样&#xff0c;通过按键开关的按下导通&#xff0c;抬起断开的情况&#xff0c;GPIO引脚来检测其是否有电流流入。GPIO有input()方法&#xff0c;对于GPIO引脚检测电流&#xff0c;不能让其引脚悬空&#xff0c;否则引脚会受周边环境电磁干扰产生微弱…

《昇思25天学习打卡营第15天|计算机视觉-SSD目标检测》

FCN图像语义分割&ResNet50迁移学习&ResNet50图像分类&ShuffleNet图像分类&SSD目标检测 SSD目标检测 模型简介 SSD&#xff0c;全称Single Shot MultiBox Detector&#xff0c;是Wei Liu在ECCV 2016上提出的一种目标检测算法。使用Nvidia Titan X在VOC 2007测…

重塑水利未来:智慧水利解决方案的探索与实践,从物联网、大数据到人工智能,科技如何赋能水利行业,实现智慧化管理与决策

本文关键词&#xff1a;智慧水利、智慧水利工程、智慧水利发展前景、智慧水利技术、智慧水利信息化系统、智慧水利解决方案、数字水利和智慧水利、数字水利工程、数字水利建设、数字水利概念、人水和协、智慧水库、智慧水库管理平台、智慧水库建设方案、智慧水库解决方案、智慧…

达梦数据库的系统视图v$systeminfo

达梦数据库的系统视图v$systeminfo 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$SYSTEMINFO 是一个系统视图&#xff0c;用于显示数据库实例的系统信息。这个视图提供了关于数据库实例的运行状态、配置参数、资源使用情况等重要信息&#xff0c;对于数据…

linux端口,进程管理,主机状态监控

linux端口&#xff0c;进程管理&#xff0c;主机状态监控 一、端口 1、什么是端口?2、端口的划分2、查看端口占用 二、进程 1、什么是进程2、查看进程信息2、关闭进程 三、主机状态监控 1、查看资源占用2、磁盘信息监控3、查看网络情况 四、命令总结 一、端口 1、什么是端口…

【089】基于SpringBoot+Vue+小程序实现的在线点餐小程序

系统介绍 基于SpringBootVue小程序实现的在线点餐小程序 基于SpringBootVue小程序实现的在线点餐小程序采用前后端分离的架构方式&#xff0c;系统分为管理员、员工、用户三种角色&#xff0c;实现了用户点餐、订单生成、模拟支付、菜单管理、账号管理、角色管理、分类管理、菜…

什么是边缘计算技术和边缘计算平台?

随着物联网、5G技术和人工智能的不断发展&#xff0c;数据的规模和种类也在快速增加。在这种背景下&#xff0c;传统的云计算模式面临着一些问题&#xff0c;例如延迟高、网络拥塞等&#xff0c;这些问题限制了数据的处理速度和效率&#xff0c;降低了用户的使用体验。为了解决…