言辞AI答题平台前端笔记
技术选型
- vue-cli
- element plus
- eslint+prettier
- 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.json
的script
中添加 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目录,管理登录用户的权限
更新用户状态userStore.ts,新增fetchLoginUser函数,拿到当前登录用户的数据,没有的话,给用户权限默认值为未登录
对于顶部导航菜单,新增根据权限判断菜单子项的显隐
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编辑器。
- 详见毕业设计寻梦OJ笔记的第四部分
新增登录页与注册页
- 效果图:
- 效果图:
新增管理布局,用户管理页
- 注:分页组件传值为number类型,后端传值类型需一致,否则用Number()强转。
继续新增应用管理,题目管理等页面
part5
新增主页,新建卡片组件,在主页通过v-for遍历使用。
新增应用详情页,根据每个应用不同的id展示的内容不同
添加按钮,新增创建题目页,更新题目页(这两个页面复用同一个页面布局)
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: "", }); };
创建设置评分页面,将评分管理页面修改后作为组件嵌入其中。
子组件调用父组件方法
子组件在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, }; }) : []; });
答题结果页面
我的答题页面