跳至主要內容

OJ 模块开发

holic-x...大约 13 分钟项目oj-platform

04-模块开发

用户提交问卷信息查询

1.后端接口开发(todo)

扩展思考:后端接口响应慢的优化思路

​ 后端接口响应慢的优化思路:用逻辑操作替换频繁调用service请求(网络数据库交互)

​ 两端代码的分析:前者是一次性获取所有的内容,然后做逻辑处理;后者是每次遍历都访问一次数据库

image-20240424193557647

image-20240424193555867

2.前端组件开发

开发前端页面:

1)用户注册页面

2)创建题目页面(管理员)

3)题目管理页面(管理员)

  • 查看(搜索)
  • 删除
  • 修改
  • 快捷创建

4)题目列表页(用户)

5)题目详情页(在线做题页)

  • 判题状态的查看

6)题目提交列表页

组件接入

先接入可能用到的组件,再去写页面,避免因为后续依赖冲突、整合组件失败带来的返工

1)Markdown编辑器:bytemd

使用Markdown的原因:bytemd编辑器open in new window(结合其官方文档Usage引入内容)

  • 引入样式文件
  • 引入MD组件(选择Vue版本)

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

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

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

MD编辑器引入

(1)main.ts中引入样式文件

import 'bytemd/dist/index.css'

(2)在compontents中创建F引入MEditor.vue组件

<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>

(3)router/routers.ts中配置路由、启动项目查看组件效果

{
    path: "/mdEditor",
    name: "MD编辑器",
    component: () => import("../components/MdEditor.vue"),
  },

image-20240424195149948

可以设定隐藏编辑器中不需要的图标(比如GitHub图标)

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

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

​ 例如在HomeView.vue中引入MdEditor组件

image-20240424200940777

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <MdEditor></MdEditor>
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
import MdEditor from "@/components/MdEditor.vue"; // @ is an alias to /src

export default defineComponent({
  name: "HomeView",
  components: {
    MdEditor,
    HelloWorld,
  },
});
</script>

父组件控制MdEditor(提高MdEditor组件通用性)

​ MdEditor 示例代码:(让父组件去控制MdEditor内部的状态,还有一种思路是通过v-model实现),基于这种方式是的MdEditor更具备通用性。有一种思路是在父组件中用户点击提交的时候拿到子组件MdEditor的value值,这种最简单粗暴,但是相对来说那么灵活

MdEditor改造(vue父组件操作子组件的一个基础实现方式)

# 父组件控制子组件核心思路(Props传递属性、withDefaults设定默认值(当父组件不指定的时候设定默认值))
/**
 * 定义组件属性类型
 */
interface Props {
  value: string;
  handleChange: (v: string) => void;
}

/**
 * 给组件指定初始值
 */
  
const props = withDefaults(defineProps<Props>(), {
  value: () => "",
  handleChange: (v: string) => {
    console.log(v);
  },
});
# 参考改造内容
<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";

/**
 * 定义组件属性类型
 */
interface Props {
  value: string;
  handleChange: (v: string) => void;
}


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

/**
 * 给组件指定初始值
 */

const props = withDefaults(defineProps<Props>(), {
  value: () => "",
  handleChange: (v: string) => {
    console.log(v);
  },
});

</script>
<style scoped></style>

HomeView改造(注意script 加上setup属性

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <MdEditor :value="value" :handle-change="onChange"></MdEditor>
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>

<script setup lang="ts">
import { defineComponent, ref } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
import MdEditor from "@/components/MdEditor.vue"; // @ is an alias to /src

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

测试:输入内容查看联动

image-20240424202724879

2)代码编辑器:monaco-editor

微软官方编辑器open in new window官方提供的整合教程open in new window

整合步骤:vue-cli项目整合monaco-editor

【1】安装编辑器

npm install monaco-editor

【2】 vue-cli 项目(webpack 项目)整合 monaco-editor

先安装monaco-ediror-webpack-plugin:

npm i monaca-editor-webpack-plugin

# 如果安装失败(提示404)使用下述指令
npm install monaco-editor-webpack-plugin --save-dev

【3】在vue.config.js中配置webpack插件:(因为vue-cli对webpack集成做了整合,它提供了一个vue.config.js文件用于统一配置插件)

  • 全量加载方式
const { defineConfig } = require("@vue/cli-service");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");

module.exports = defineConfig({
  transpileDependencies: true,
  chainWebpack(config) {
    config.plugin("monaco").use(new MonacoWebpackPlugin());
  },
});

  • 按需加载方式
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
module.exports = {
  chainWebpack: config => {
    config.plugin('monaco-editor').use(MonacoWebpackPlugin, [
      {
        // Languages are loaded on demand at runtime
        languages: ['json', 'go', 'css', 'html', 'java', 'javascript', 'less', 'markdown', 'mysql', 'php', 'python', 'scss', 'shell', 'redis', 'sql', 'typescript', 'xml'], // ['abap', 'apex', 'azcli', 'bat', 'cameligo', 'clojure', 'coffee', 'cpp', 'csharp', 'csp', 'css', 'dart', 'dockerfile', 'ecl', 'fsharp', 'go', 'graphql', 'handlebars', 'hcl', 'html', 'ini', 'java', 'javascript', 'json', 'julia', 'kotlin', 'less', 'lexon', 'lua', 'm3', 'markdown', 'mips', 'msdax', 'mysql', 'objective-c', 'pascal', 'pascaligo', 'perl', 'pgsql', 'php', 'postiats', 'powerquery', 'powershell', 'pug', 'python', 'r', 'razor', 'redis', 'redshift', 'restructuredtext', 'ruby', 'rust', 'sb', 'scala', 'scheme', 'scss', 'shell', 'solidity', 'sophia', 'sql', 'st', 'swift', 'systemverilog', 'tcl', 'twig', 'typescript', 'vb', 'xml', 'yaml'],

        features: ['format', 'find', 'contextmenu', 'gotoError', 'gotoLine', 'gotoSymbol', 'hover' , 'documentSymbols'] //['accessibilityHelp', 'anchorSelect', 'bracketMatching', 'caretOperations', 'clipboard', 'codeAction', 'codelens', 'colorPicker', 'comment', 'contextmenu', 'coreCommands', 'cursorUndo', 'dnd', 'documentSymbols', 'find', 'folding', 'fontZoom', 'format', 'gotoError', 'gotoLine', 'gotoSymbol', 'hover', 'iPadShowKeyboard', 'inPlaceReplace', 'indentation', 'inlineHints', 'inspectTokens', 'linesOperations', 'linkedEditing', 'links', 'multicursor', 'parameterHints', 'quickCommand', 'quickHelp', 'quickOutline', 'referenceSearch', 'rename', 'smartSelect', 'snippets', 'suggest', 'toggleHighContrast', 'toggleTabFocusMode', 'transpose', 'unusualLineTerminators', 'viewportSemanticTokens', 'wordHighlighter', 'wordOperations', 'wordPartOperations']
      }
    ])
  }
}

此处选择方式:

image-20240424203944063

如何使用

示例教程:Monco Editoropen in new window

image-20240424204140589

整合教程参考:Monaco Editor安装及使用open in new window

注意,monaco editor 在读写值的时候,要使用toRaw(编辑器实例)的语法来执行操作,否则会卡死。

1)在compontent新建一个CodeEditor.vue(代码编辑器),示例整合代码如下:

<template>
  <div id="code-editor" ref="codeEditorRef" style="min-height: 400px" />
  {{ value }}
  <a-button @click="fillValue">填充值</a-button>
</template>

<script setup lang="ts">
import * as monaco from "monaco-editor";
import { onMounted, ref, toRaw } from "vue";

const codeEditorRef = ref();
const codeEditor = ref();
const value = ref("hello world");
const fillValue = () => {
  if (!codeEditor.value) {
    return;
  }
  // 改变值
  toRaw(codeEditor.value).setValue("新的值");
};
onMounted(() => {
  if (!codeEditorRef.value) {
    return;
  }
  codeEditor.value = monaco.editor.create(codeEditorRef.value, {
    value: value.value,
    language: "java",
    automaticLayout: true,
    colorDecorators: true,
    minimap: {
      enabled: true,
      scale: 5,
    },
    readOnly: false,
    theme: "vs-dark",
  });
  // 编辑 监听内容变化
  codeEditor.value.onDidChangeModelContent(() => {
    console.log("目前内容为:", toRaw(codeEditor.value).getValue());
  });
});
</script>
<style scoped></style>

2)配置路由router/routes.ts:测试CodeEditor


  {
    path: "/codeEditor",
    name: "Code编辑器",
    component: () => import("../components/CodeEditor.vue"),
  },

3)启动项目测试(如果出错参考下述整合报错问题解决)

整合报错问题解决:

Module build failed (from ./node_modules/babel-loader/lib/index.js): SyntaxError: E:\workspace\Git\github\PROJECT\noob\oj-platform\oj-platform-frontend\node_modules\monaco-editor\esm\vs\language\typescript\tsMode.js: Static class blocks are not enabled. Please add `@babel/plugin-transform-class-static-block` to your configuration.

image-20240424205446398

(1)提示配置确认插件,根据提示安装插件

npm install @babel/plugin-transform-class-static-block

(2)进入 Babelopen in new window 配置文件(.babelrcbabel.config.js),加入下方代码:

plugins: ["@babel/plugin-transform-class-static-block"],

image-20240424210104070

(3)重启项目确认

image-20240424210248368

​ 这里有个点:编辑器输入内容,但是不会输出(这个组件不会像MdEditor那样自动设置联动更新结果),需要手动调整结果值

父组件控制CodeEditor

​ 和上面的Md 编辑器一样,让组件更具备通用性。接受父组件的传值,把显示的输入交给父组件去控制,从而能够让父组件实时得到用户输入的代码:

/**
 * 定义组件属性类型
 */
interface Props {
  value: string;
  handleChange: (v: string) => void;
}
  
/**
 * 给组件指定初始值
 */
const props = withDefaults(defineProps<Props>(), {
  value: () => "",
  handleChange: (v: string) => {
    console.log(v);
  },
});

参考CodeEditor实现:

<template>
  <div
    id="code-editor"
    ref="codeEditorRef"
    style="min-height: 400px; height: 60vh"
  />
  <!--  <a-button @click="fillValue">填充值</a-button>-->
</template>

<script setup lang="ts">
import * as monaco from "monaco-editor";
import { onMounted, ref, toRaw, withDefaults, defineProps, watch } from "vue";

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

/**
 * 给组件指定初始值
 */
const props = withDefaults(defineProps<Props>(), {
  value: () => "",
  language: () => "java",
  handleChange: (v: string) => {
    console.log(v);
  },
});

const codeEditorRef = ref();
const codeEditor = ref();

// const fillValue = () => {
//   if (!codeEditor.value) {
//     return;
//   }
//   // 改变值
//   toRaw(codeEditor.value).setValue("新的值");
// };

watch(
  () => props.language,
  () => {
    if (codeEditor.value) {
      monaco.editor.setModelLanguage(
        toRaw(codeEditor.value).getModel(),
        props.language
      );
    }
  }
);

onMounted(() => {
  if (!codeEditorRef.value) {
    return;
  }
  // Hover on each property to see its docs!
  codeEditor.value = monaco.editor.create(codeEditorRef.value, {
    value: props.value,
    language: props.language,
    automaticLayout: true,
    colorDecorators: true,
    minimap: {
      enabled: true,
    },
    readOnly: false,
    theme: "vs-dark",
    // lineNumbers: "off",
    // roundedSelection: false,
    // scrollBeyondLastLine: false,
  });

  // 编辑 监听内容变化
  codeEditor.value.onDidChangeModelContent(() => {
    props.handleChange(toRaw(codeEditor.value).getValue());
  });
});
</script>

<style scoped></style>

父组件引入配置实现(view下创建一个ExampleView.vue示例,测试组件的应用)

 {
    path: "/exampleEditor",
    name: "编辑器示例",
    component: () => import("../views/ExampleView.vue"),
  },
  • ExampleView.vue示例
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <MdEditor :value="mdValue" :handle-change="mdOnChange"></MdEditor>
    <CodeEditor :value="codeValue" :handle-change="codeOnChange"></CodeEditor>
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>

<script setup lang="ts">
import { defineComponent, ref } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
import MdEditor from "@/components/MdEditor.vue";
import CodeEditor from "@/components/CodeEditor.vue"; // @ is an alias to /src

const mdValue = ref("");
const mdOnChange = (v: string) => {
  mdValue.value = v;
};

const codeValue = ref("");
const codeOnChange = (v: string) => {
  codeValue.value = v;
};

</script>

​ 测试效果如下所示:

image-20240424212809364

to:项目扩展

用diff editor对比用户代码和标准答案的区别

前端模板生成小技巧

image-20240424214706255

​ 新建一个vue文件,可以指定自定义的vue模板生成(便于快速开发)

image-20240424214254469

配置模板内容:

image-20240424214250578

指定模板生效的作用域:(例如此处指定为vue文件)

image-20240424214445674

使用:新建一个Vue文件,然后清空文件内容,输入myvuepage关键字按下回车生成模板

image-20240424214935373

3.页面开发

​ 后端接口编写完成,随后执行指令生成相应的service(此处注意一个小坑,每次生成新的服务,默认OpenAPI.ts中的WITH_CREDENTIALS(请求携带cookies)配置都是false,因此要手动设定其为true)

export const OpenAPI: OpenAPIConfig = {
  BASE: "http://localhost:8101",
  VERSION: "1.0",
  WITH_CREDENTIALS: true, // 设置请求携带cookies
  CREDENTIALS: "include",
  TOKEN: undefined,
  USERNAME: undefined,
  PASSWORD: undefined,
  HEADERS: undefined,
  ENCODE_PATH: undefined,
};

​ 为了解决这个问题,可以在axios.ts中进行全局配置,避免每次生成新的接口文档都受到影响(todo)

# vue生成openapi文档指令
openapi --input http://localhost:8101/api/v2/api-docs --output ./generated --client axios

​ 如果生成新的接口服务启动后项目报错,则去检查OpenAPI的配置信息(后端接口路径、请求携带cookies配置)

创建题目

​ 参考表单组件open in new window嵌套表单open in new window动态增减表单open in new window

​ 此处需注意自定义的代码编辑器组件不会被组件库识别,需要手动去指定 value 和 handleChange 函数

参考用户输入值:

{
  "answer": "暴力破解",
  "content": "题目内容",
  "judgeCase": [
    {
        "input": "1 2",
        "output": "3 4"
    }
  ],
  "judgeConfig": {
      "memoryLimit": 1000,
      "stackLimit": 1000,
      "timeLimit": 1000
  },
  "tags": [
      "栈", "简单"
      ],
  "title": "A + B"
}

构建步骤

1)新建views/question/AddQuestionView.vue

<template>
  <div id="addQuestionView">
    <h2>创建题目</h2>
    <a-form :model="form" label-align="left">
      <a-form-item field="title" label="标题">
        <a-input v-model="form.title" placeholder="请输入标题" />
      </a-form-item>
      <a-form-item field="tags" label="标签">
        <a-input-tag v-model="form.tags" placeholder="请选择标签" allow-clear />
      </a-form-item>
      <a-form-item field="content" label="题目内容">
        <MdEditor :value="form.content" :handle-change="onContentChange" />
      </a-form-item>
      <a-form-item field="answer" label="答案">
        <MdEditor :value="form.answer" :handle-change="onAnswerChange" />
      </a-form-item>
      <a-form-item label="判题配置" :content-flex="false" :merge-props="false">
        <a-space direction="vertical" style="min-width: 480px">
          <a-form-item field="judgeConfig.timeLimit" label="时间限制">
            <a-input-number
              v-model="form.judgeConfig.timeLimit"
              placeholder="请输入时间限制"
              mode="button"
              min="0"
              size="large"
            />
          </a-form-item>
          <a-form-item field="judgeConfig.memoryLimit" label="内存限制">
            <a-input-number
              v-model="form.judgeConfig.memoryLimit"
              placeholder="请输入内存限制"
              mode="button"
              min="0"
              size="large"
            />
          </a-form-item>
          <a-form-item field="judgeConfig.stackLimit" label="堆栈限制">
            <a-input-number
              v-model="form.judgeConfig.stackLimit"
              placeholder="请输入堆栈限制"
              mode="button"
              min="0"
              size="large"
            />
          </a-form-item>
        </a-space>
      </a-form-item>
      <a-form-item
        label="测试用例配置"
        :content-flex="false"
        :merge-props="false"
      >
        <a-form-item
          v-for="(judgeCaseItem, index) of form.judgeCase"
          :key="index"
          no-style
        >
          <a-space direction="vertical" style="min-width: 640px">
            <a-form-item
              :field="`form.judgeCase[${index}].input`"
              :label="`输入用例-${index}`"
              :key="index"
            >
              <a-input
                v-model="judgeCaseItem.input"
                placeholder="请输入测试输入用例"
              />
            </a-form-item>
            <a-form-item
              :field="`form.judgeCase[${index}].output`"
              :label="`输出用例-${index}`"
              :key="index"
            >
              <a-input
                v-model="judgeCaseItem.output"
                placeholder="请输入测试输出用例"
              />
            </a-form-item>
            <a-button status="danger" @click="handleDelete(index)">
              删除
            </a-button>
          </a-space>
        </a-form-item>
        <div style="margin-top: 32px">
          <a-button @click="handleAdd" type="outline" status="success"
            >新增测试用例
          </a-button>
        </div>
      </a-form-item>
      <div style="margin-top: 16px" />
      <a-form-item>
        <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>

2)前面的接口实现都是比较简单的CRUD,现在要根据业务逻辑去处理请求(确认请求参数并处理)

image-20240424221739629

构建步骤

​ 涉及到JSON转化,需要引入gson组件(修改pom.xml)

		<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.9.1</version>
        </dependency>

1)修改QuestionAddRequest请求参数:将tags、judgeCase、judgeConfig与前端定义的数据类型进行匹配(其中JudgeCase、JudgeConfig存储为json数据,可定义对应实体进行匹配)

@Data
public class QuestionAddRequest implements Serializable {

    /**
     * 标题
     */
    private String title;

    /**
     * 内容
     */
    private String content;

    /**
     * 标签列表
     */
    private List<String> tags;

    /**
     * 题目答案
     */
    private String answer;

    /**
     * 判题用例
     */
    private List<JudgeCase> judgeCase;

    /**
     * 判题配置
     */
    private JudgeConfig judgeConfig;


    private static final long serialVersionUID = 1L;
}
public class JudgeCase {

    /**
     * 输入用例
     */
    private String input;

    /**
     * 输出用例
     */
    private String output;
}
public class JudgeConfig {

    /**
     * 时间限制(ms)
     */
    private Long timeLimit;

    /**
     * 内存限制(KB)
     */
    private Long memoryLimit;

    /**
     * 堆栈限制(KB)
     */
    private Long stackLimit;
}

2)修改QuestionController的add方法

@RestController
@RequestMapping("/question")
@Slf4j
public class QuestionController {

    @Resource
    private QuestionService questionService;

    @Resource
    private UserService userService;

    private final static Gson GSON = new Gson();

    /**
     * 创建
     * @param questionAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Long> addQuestion(@RequestBody QuestionAddRequest questionAddRequest, HttpServletRequest request) {
        if (questionAddRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Question question = new Question();
        BeanUtils.copyProperties(questionAddRequest, question);

        // 校验问题信息
        questionService.validQuestion(question, true);

        User loginUser = userService.getLoginUser(request);
        question.setCreater(loginUser.getId());
        question.setUpdater(loginUser.getId());
        question.setCreateTime(new Date());
        question.setUpdateTime(new Date());

        // 设置标签
        List<String> tags = questionAddRequest.getTags();
        if (tags != null) {
            question.setTags(GSON.toJson(tags));
        }

        // 设置测试用例
        List<JudgeCase> judgeCase = questionAddRequest.getJudgeCase();
        if (judgeCase != null) {
            question.setJudgeCase(GSON.toJson(judgeCase));
        }

        // 设置题目配置
        JudgeConfig judgeConfig = questionAddRequest.getJudgeConfig();
        if (judgeConfig != null) {
            question.setJudgeConfig(GSON.toJson(judgeConfig));
        }

        question.setFavourNum(0);
        question.setThumbNum(0);

        // 新增
        boolean result = questionService.save(question);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        long newQuestionId = question.getId();
        return ResultUtils.success(newQuestionId);
    }
}

3)后端修改完成需要重启,然后前端生成API接口(如果请求参数和路径不变则可以不用重新生成)

​ 其中标签输入规则,设定输入标签然后点击enter键则可创建标签

image-20240424231233649

表单组件默认有初始化值,这块的内容可以定位具体的代码实现进行调整,后续业务逻辑还要继续优化这块的实现

image-20240424231344248

题目管理

构建步骤

1)创建views/question/ManageQuestion.vue文件


提交题目

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3