目录
前端创建页面的开发一
创建一个路由
用acro design写
前端创建页面的开发二
题目管理页面
搜索
最終效果
题目更新页面的开发
携带参数的那种
修改路由
页码更新细节
我们先处理菜单项的权限控制和权限隐藏
在这里改
属性绑定一个函数
可以参考聚合搜索项目状态改变和url状态同步
就会触发loadData函数
当分页页号改变时 触发@page-change事件 通过改变searchParams的数值
然后
题目列表搜索页
代码
设置路由
做题页面
代码
路由
前端创建页面的开发一
创建一个路由
{path: "/add/question",name: "创建题目",component: AddQuestionView,meta: {access: ACCESS_ENUM.USER,},},
用acro design写
<template><div id="addQuestionView" style="max-width: 800px; margin: 0 auto"><h2 style="margin-bottom: 16px">创建题目</h2><a-form :model="form" label-align="left"><a-form-item label="标题"><a-input v-model="form.title" placeholder="" /></a-form-item><a-form-item label="标签"><a-input-tag v-model="form.tags" placeholder="请选择标签" allow-clear /></a-form-item><a-form-item label="题目内容"><MdEditor :value="form.content" :handle-change="onContentChange" /></a-form-item><a-form-item label="答案"><MdEditor :value="form.answer" :handle-change="onAnswerChange" /></a-form-item><a-form-item label="判题配置一"><div><a-form-item label="时间限制"><a-input-numberv-model="form.judgeConfig.timeLimit"placeholder="请输入时间限制"mode="button"min="0"size="large"/></a-form-item></div></a-form-item><a-form-item label="判题配置二"><div><a-form-item label="内存限制"><a-input-numberv-model="form.judgeConfig.memoryLimit"placeholder="请输入内存限制"mode="button"min="0"size="large"/></a-form-item></div></a-form-item><a-form-item label="判题配置三"><div><a-form-item label="堆栈限制"><a-input-numberv-model="form.judgeConfig.stackLimit"placeholder="请输入堆栈限制"mode="button"min="0"size="large"/></a-form-item></div></a-form-item><a-form-item label="测试用例配置"><!-- 使用表格布局或者 flex 布局 --><table><tbody><tr v-for="(judgeCaseItem, index) of form.judgeCase" :key="index"><td><a-form-item :label="`输入用例-${index}`" no-style><a-inputv-model="judgeCaseItem.input"placeholder="请输入测试输入用例"/></a-form-item></td><td><a-form-item :label="`输出用例-${index}`" no-style><a-inputv-model="judgeCaseItem.output"placeholder="请输入测试输出用例"/></a-form-item></td><td><a-button status="danger" @click="handleDelete(index)">删除</a-button></td></tr></tbody></table><div style="margin-top: 16px"><a-button @click="handleAdd" type="outline" status="success">新增测试用例</a-button></div></a-form-item><a-form-item style="text-align: center"><a-button type="primary" style="min-width: 200px" @click="doSubmit">提交</a-button></a-form-item></a-form></div>
</template><script setup lang="ts">
import { onMounted, ref } from "vue";
import MdEditor from "@/components/MdEditor.vue";
import { QuestionControllerService } from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import { useRoute } from "vue-router";const route = useRoute();
// 如果页面地址包含 update,视为更新页面
const updatePage = route.path.includes("update");let form = ref({title: "",tags: [],answer: "",content: "",judgeConfig: {memoryLimit: 1000,stackLimit: 1000,timeLimit: 1000,},judgeCase: [{input: "",output: "",},],
});/*** 根据题目 id 获取老的数据*/
const loadData = async () => {const id = route.query.id;if (!id) {return;}const res = await QuestionControllerService.getQuestionByIdUsingGet(id as any);if (res.code === 0) {form.value = res.data as any;// json 转 js 对象if (!form.value.judgeCase) {form.value.judgeCase = [{input: "",output: "",},];} else {form.value.judgeCase = JSON.parse(form.value.judgeCase as any);}if (!form.value.judgeConfig) {form.value.judgeConfig = {memoryLimit: 1000,stackLimit: 1000,timeLimit: 1000,};} else {form.value.judgeConfig = JSON.parse(form.value.judgeConfig as any);}if (!form.value.tags) {form.value.tags = [];} else {form.value.tags = JSON.parse(form.value.tags as any);}} else {message.error("加载失败," + res.message);}
};onMounted(() => {loadData();
});const doSubmit = async () => {console.log(form.value);// 区分更新还是创建if (updatePage) {const res = await QuestionControllerService.updateQuestionUsingPost(form.value);if (res.code === 0) {message.success("更新成功");} else {message.error("更新失败," + res.message);}} else {const res = await QuestionControllerService.addQuestionUsingPost(form.value);if (res.code === 0) {message.success("创建成功");} else {message.error("创建失败," + res.message);}}
};/*** 新增判题用例*/
const handleAdd = () => {form.value.judgeCase.push({input: "",output: "",});
};/*** 删除判题用例*/
const handleDelete = (index: number) => {form.value.judgeCase.splice(index, 1);
};const onContentChange = (value: string) => {form.value.content = value;
};const onAnswerChange = (value: string) => {form.value.answer = value;
};
</script><style scoped>
#addQuestionView {
}
</style>
前端创建页面的开发二
开发的是其他增删改查页面
题目管理页面
查看
搜索
<template><div id="manageQuestionView"><a-table:ref="tableRef":columns="columns":data="dataList":pagination="{showTotal: true,pageSize: searchParams.pageSize,current: searchParams.current,total,}"@page-change="onPageChange"><template #optional="{ record }"><a-space><a-button type="primary" @click="doUpdate(record)"> 修改</a-button><a-button status="danger" @click="doDelete(record)">删除</a-button></a-space></template></a-table></div>
</template><script setup lang="ts">
import { onMounted, ref, watchEffect } from "vue";
import {Page_Question_,Question,QuestionControllerService,
} from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import * as querystring from "querystring";
import { useRouter } from "vue-router";const tableRef = ref();const dataList = ref([]);
const total = ref(0);
const searchParams = ref({pageSize: 10,current: 1,
});const loadData = async () => {const res = await QuestionControllerService.listQuestionByPageUsingPost(searchParams.value);if (res.code === 0) {dataList.value = res.data.records;total.value = res.data.total;} else {message.error("加载失败," + res.message);}
};/*** 监听 searchParams 变量,改变时触发页面的重新加载*/
watchEffect(() => {loadData();
});/*** 页面加载时,请求数据*/
onMounted(() => {loadData();
});// {id: "1", title: "A+ D", content: "新的题目内容", tags: "["二叉树"]", answer: "新的答案", submitNum: 0,…}const columns = [{title: "题目id",dataIndex: "id",},{title: "标题",dataIndex: "title",},{title: "内容",dataIndex: "content",},{title: "标签",dataIndex: "tags",},{title: "答案",dataIndex: "answer",},{title: "提交数",dataIndex: "submitNum",},{title: "通过数",dataIndex: "acceptedNum",},{title: "判题配置",dataIndex: "judgeConfig",},{title: "判题用例",dataIndex: "judgeCase",},{title: "创建者",dataIndex: "userId",},{title: "创建时间",dataIndex: "createTime",},{title: "操作",slotName: "optional",},
];const onPageChange = (page: number) => {searchParams.value = {...searchParams.value,current: page,};
};const doDelete = async (question: Question) => {const res = await QuestionControllerService.deleteQuestionUsingPost({id: question.id,});if (res.code === 0) {message.success("删除成功");loadData();} else {message.error("删除失败");}
};const router = useRouter();const doUpdate = (question: Question) => {router.push({path: "/update/question",query: {id: question.id,},});
};
</script><style scoped>
#manageQuestionView {
}
</style>
最終效果
题目更新页面的开发
写一个动态路由
携带参数的那种
const router = useRouter();const doUpdate = (question: Question) => {router.push({path: "/update/question",query: {id: question.id,},});
};
修改路由
{path: "/update/question",name: "更新题目",component: AddQuestionView,meta: {access: ACCESS_ENUM.USER,hideInMenu: true,},},
页码更新细节
我们的管理题目页面不能分页
我们先处理菜单项的权限控制和权限隐藏
在这里改
在组件里面定义属性
属性绑定一个函数
const onPageChange = (page: number) => {searchParams.value = {...searchParams.value,current: page,};
};
可以参考聚合搜索项目状态改变和url状态同步
当我们的属性改变了
就会触发loadData函数
当分页页号改变时 触发@page-change事件 通过改变searchParams的数值
并且通过watchEffect监听searchParams的改变
然后执行loadData重新加载速度
实现页号变化时触发数据的重新加载
然后
/*** 监听 searchParams 变量,改变时触发页面的重新加载*/
watchEffect(() => {loadData();
});
题目列表搜索页
代码
<template><div id="questionsView"><a-form :model="searchParams" layout="inline"><a-form-item field="title" label="名称" style="min-width: 240px"><a-input v-model="searchParams.title" placeholder="请输入名称" /></a-form-item><a-form-item field="tags" label="标签" style="min-width: 240px"><a-input-tag v-model="searchParams.tags" placeholder="请输入标签" /></a-form-item><a-form-item><a-button type="primary" @click="doSubmit">提交</a-button></a-form-item></a-form><a-divider size="0" /><a-table:ref="tableRef":columns="columns":data="dataList":pagination="{showTotal: true,pageSize: searchParams.pageSize,current: searchParams.current,total,}"@page-change="onPageChange"><template #tags="{ record }"><a-space wrap><a-tag v-for="(tag, index) of record.tags" :key="index" color="green">{{ tag }}</a-tag></a-space></template><template #acceptedRate="{ record }">{{`${record.submitNum ? record.acceptedNum / record.submitNum : "0"}% (${record.acceptedNum}/${record.submitNum})`}}</template><template #createTime="{ record }">{{ moment(record.createTime).format("YYYY-MM-DD") }}</template><template #optional="{ record }"><a-space><a-button type="primary" @click="toQuestionPage(record)">做题</a-button></a-space></template></a-table></div>
</template><script setup lang="ts">
import { onMounted, ref, watchEffect } from "vue";
import {Page_Question_,Question,QuestionControllerService,QuestionQueryRequest,
} from "../../../generated";
import message from "@arco-design/web-vue/es/message";
import * as querystring from "querystring";
import { useRouter } from "vue-router";
import moment from "moment";const tableRef = ref();const dataList = ref([]);
const total = ref(0);
const searchParams = ref<QuestionQueryRequest>({title: "",tags: [],pageSize: 8,current: 1,
});const loadData = async () => {const res = await QuestionControllerService.listQuestionVoByPageUsingPost(searchParams.value);if (res.code === 0) {dataList.value = res.data.records;total.value = res.data.total;} else {message.error("加载失败," + res.message);}
};/*** 监听 searchParams 变量,改变时触发页面的重新加载*/
watchEffect(() => {loadData();
});/*** 页面加载时,请求数据*/
onMounted(() => {loadData();
});// {id: "1", title: "A+ D", content: "新的题目内容", tags: "["二叉树"]", answer: "新的答案", submitNum: 0,…}const columns = [{title: "题号",dataIndex: "id",},{title: "题目名称",dataIndex: "title",},{title: "标签",slotName: "tags",},{title: "通过率",slotName: "acceptedRate",},{title: "创建时间",slotName: "createTime",},{slotName: "optional",},
];const onPageChange = (page: number) => {searchParams.value = {...searchParams.value,current: page,};
};const router = useRouter();/*** 跳转到做题页面* @param question*/
const toQuestionPage = (question: Question) => {router.push({path: `/view/question/${question.id}`,});
};/*** 确认搜索,重新加载数据*/
const doSubmit = () => {// 这里需要重置搜索页号searchParams.value = {...searchParams.value,current: 1,};
};
</script><style scoped>
#questionsView {max-width: 1280px;margin: 0 auto;
}
</style>
设置路由
{path: "/questions",name: "浏览题目",component: QuestionsView,},
做题页面
代码
<template><div id="viewQuestionView"><a-row :gutter="[24, 24]"><a-col :md="12" :xs="24"><a-tabs default-active-key="question"><a-tab-pane key="question" title="题目"><a-card v-if="question" :title="question.title"><a-descriptionstitle="判题条件":column="{ xs: 1, md: 2, lg: 3 }"><a-descriptions-item label="时间限制">{{ question.judgeConfig.timeLimit ?? 0 }}</a-descriptions-item><a-descriptions-item label="内存限制">{{ question.judgeConfig.memoryLimit ?? 0 }}</a-descriptions-item><a-descriptions-item label="堆栈限制">{{ question.judgeConfig.stackLimit ?? 0 }}</a-descriptions-item></a-descriptions><MdViewer :value="question.content || ''" /><template #extra><a-space wrap><a-tagv-for="(tag, index) of question.tags":key="index"color="green">{{ tag }}</a-tag></a-space></template></a-card></a-tab-pane><a-tab-pane key="comment" title="评论" disabled> 评论区</a-tab-pane><a-tab-pane key="answer" title="答案"> 暂时无法查看答案</a-tab-pane></a-tabs></a-col><a-col :md="12" :xs="24"><a-form :model="form" layout="inline"><a-form-itemfield="language"label="编程语言"style="min-width: 240px"><a-selectv-model="form.language":style="{ width: '320px' }"placeholder="选择编程语言"><a-option>java</a-option><a-option>cpp</a-option><a-option>go</a-option><a-option>html</a-option></a-select></a-form-item></a-form><CodeEditor:value="form.code as string":language="form.language":handle-change="changeCode"/><a-divider size="0" /><a-button type="primary" style="min-width: 200px" @click="doSubmit">提交代码</a-button></a-col></a-row></div>
</template><script setup lang="ts">
import { onMounted, ref, watchEffect, withDefaults, defineProps } from "vue";
import message from "@arco-design/web-vue/es/message";
import CodeEditor from "@/components/CodeEditor.vue";
import MdViewer from "@/components/MdViewer.vue";import {QuestionControllerService,QuestionSubmitAddRequest,QuestionVO,
} from "../../../generated";interface Props {id: string;
}const props = withDefaults(defineProps<Props>(), {id: () => "",
});const question = ref<QuestionVO>();const loadData = async () => {const res = await QuestionControllerService.getQuestionVoByIdUsingGet(props.id as any);if (res.code === 0) {question.value = res.data;} else {message.error("加载失败," + res.message);}
};const form = ref<QuestionSubmitAddRequest>({language: "java",code: "",
});/*** 提交代码*/
const doSubmit = async () => {if (!question.value?.id) {return;}const res = await QuestionControllerService.doQuestionSubmitUsingPost({...form.value,questionId: question.value.id,});if (res.code === 0) {message.success("提交成功");} else {message.error("提交失败," + res.message);}
};/*** 页面加载时,请求数据*/
onMounted(() => {loadData();
});const changeCode = (value: string) => {form.value.code = value;
};
</script><style>
#viewQuestionView {max-width: 1400px;margin: 0 auto;
}#viewQuestionView .arco-space-horizontal .arco-space-item {margin-bottom: 0 !important;
}
</style>
路由
{path: "/view/question/:id",name: "在线做题",component: ViewQuestionView,props: true,meta: {access: ACCESS_ENUM.USER,hideInMenu: true,},},