1231 字
6 分钟
AI答题平台前端笔记
2025-01-03

言辞AI答题平台前端笔记#

技术选型#

  1. vue-cli
  2. element plus
  3. eslint+prettier
  4. TS

Part1#

  • 项目初始化,引入element plus组件库,开启eslint+prettier语法检查。

  • 确定布局,内容,功能。写页面时先写内容,再写功能,最后补全布局css。

  • 抽出路由为一个新文件,方便管理。

  • 定义基本布局,用户布局等多套布局。

  • 在APP.vue设定对于切换布局的判断

    • <div id="app"> <template v-if="route.path.startsWith('/user')"> <router-view /> </template> <template v-else> <BasicLayout /> </template> </div>
  • 在routes路由文件内采用子路由的形式,对于含有特定地址的路由进行布局的切换。

  • 通过meta里的字段判断路由在全局导航栏上的显隐。

Part2#

  • 安装axios请求库,安装openAPI插件,根据后端接口自动生成前端代码(详见毕业设计项目总结第一部分

    • yarn add @umijs/openapi
    • 在根目录新建相关配置,文档参考:@umijs/openapi

    • package.jsonscript 中添加 api: "openapi2ts": "openapi2ts",

  • 全局状态管理:官方文档

    • 示例userStore

    • import { defineStore } from "pinia"; import { ref } from "vue"; export const useLoginUserStore = defineStore("loginUser", () => { const loginUser = ref<API.User>({ username: "未登录", }); function setLoginUser(user: API.User) { loginUser.value = user; } return { loginUser, setLoginUser }; });

Part3#

  • 全局权限管理

    • 设置access目录,管理登录用户的权限

      • image-20250219213514058

      • image-20250219213537175

    • 更新用户状态userStore.ts,新增fetchLoginUser函数,拿到当前登录用户的数据,没有的话,给用户权限默认值为未登录

      • image-20250219213632592
    • 对于顶部导航菜单,新增根据权限判断菜单子项的显隐

      • image-20250219213853866

Part4#

  • 在登录后页面依然无权限

    • 原因:页面路由已经计算好了,哪怕登录用户发生了变化,也不会重新渲染页面。
    • 解决方法:将全局导航栏的展示在页面的菜单变量改为计算属性,用computed封装一下。
  • 如下函数无法获取res的内容

    • async function fetchLoginUser() { const res = await getLoginUserUsingGet(); if (res.data.code === 0 && res.data.data) { console.log(res); loginUser.value = res.data.data; } else { console.log(res); setTimeout(() => { loginUser.value = { id: 1, userName: "登66录", userRole: accessEnum.ADMIN, }; }, 3000); loginUser.value = { userRole: accessEnum.NOT_LOGIN }; } }
    • 原因:自己配置的axios响应拦截器返回的值直接取的data的内容,导致该处if条件判断的时候无法找到对应的字段。

    • 解决方法:修改axios全局响应拦截器的返回值,直接返回response。

  • 整合bytemd插件,实现markdown编辑器。

  • 新增登录页与注册页

    • 效果图:
      • image-20250220183215471
      • image-20250220183225429
  • 新增管理布局,用户管理页

    • 注:分页组件传值为number类型,后端传值类型需一致,否则用Number()强转。
    • image-20250223201304425
  • 继续新增应用管理,题目管理等页面

    • image-20250225130851258
    • image-20250225130910325

part5#

  • 新增主页,新建卡片组件,在主页通过v-for遍历使用。

    • image-20250225173150509
  • 新增应用详情页,根据每个应用不同的id展示的内容不同

    • image-20250225173233669
  • 添加按钮,新增创建题目页,更新题目页(这两个页面复用同一个页面布局)

    • image-20250225185347354
    • image-20250225185332447

Part6#

  • 创建题目页面的嵌套表单

    • 添加选项与删除选项的函数:

      • /** * 删除题目 * @param index */ const removeQuestion = (index: number) => { questionContent.splice(index, 1); }; /** * 添加题目 * @param index */ const addQuestion = (index: number) => { questionContent.splice(index, 0, { title: "", options: [], }); };
    • 实现题目的嵌套循环

      • <div v-for="(question, index) in questionContent" :key="index"> <el-space size="large"> <h3>题目{{ index + 1 }}</h3> <el-button size="small" @click="addQuestion(index + 1)"> 添加题目 </el-button> <el-button size="small" type="danger" @click="removeQuestion(index)" > 删除题目 </el-button> </el-space> <el-form-item :label="`题目${index + 1}标题`"> <el-input v-model="question.title" placeholder="请输入标题" /> </el-form-item>
    • 添加题目选项与删除题目选项的函数

      • /** * 删除题目选项 * @param item */ const removeQuestionOption = ( question: API.QuestionContentDTO, index: number ) => { if (!question.options) { question.options = []; } question.options.splice(index, 1); }; /** * 添加题目选项 * @param index */ const addQuestionOption = (question: API.QuestionContentDTO, index: number) => { if (!question.options) { question.options = []; } question.options.splice(index, 0, { key: "", value: "", }); };
  • 创建设置评分页面,将评分管理页面修改后作为组件嵌入其中。

    • image-20250226164542196

    • 子组件调用父组件方法

      • 子组件在props中注册父组件函数

      • interface Props { appId: number; doUpdate: (scoringResult: API.ScoringResultVO) => void; } const props = withDefaults(defineProps<Props>(), { appId: () => { return 0; }, });
      • 通过按钮点击事件触发

      • <el-button size="small" type="success" @click="doUpdate?.(scope.row)"> 修改 </el-button>
      • 父组件在子组件中把函数传递过去

      • <ScoringResultTable :appId="appId" :doUpdate="doUpdate" ref="tableRef" />
    • 父组件调用子组件方法

      • 子组件通过defineExpose将方法暴露给父组件

      • /** * 暴露给父组件 */ defineExpose({ loadData, });
      • 父组件在子组件通过ref将函数引入自己定义的ref响应式变量中,最后通过.value访问。

      • <ScoringResultTable :appId="appId" :doUpdate="doUpdate" ref="tableRef" />
      • const tableRef = ref();
  • 答题模块三个页面

    • 答题页面

      • //当前题目的序号 const current = ref(1); //当前题目 const currentQuestion = ref<API.QuestionContentDTO>({}); //当前题目选项 const questionOptions = computed(() => { return currentQuestion.value.options ? currentQuestion.value.options.map((option) => { return { label: `${option.key}.${option.value}`, value: option.key, }; }) : []; });
    • 答题结果页面

      • image-20250227115207518
    • 我的答题页面

      • image-20250227115218930
AI答题平台前端笔记
https://lym0518.cn/posts/aiansweringplatform/
作者
TeFantasy
发布于
2025-01-03
许可协议
CC BY-NC-SA 4.0