Compare commits

...

17 Commits

2
.gitignore vendored

@ -21,3 +21,5 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
**/.idea

@ -1,13 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义XML文档的版本和编码 -->
<project version="4">
<!-- 定义项目版本 -->
<component name="CompilerConfiguration">
<!-- 定义编译器配置 -->
<annotationProcessing>
<!-- 定义注解处理 -->
<profile name="Maven default annotation processors profile" enabled="true">
<!-- 定义Maven默认注解处理器配置 -->
<sourceOutputDir name="target/generated-sources/annotations" />
<!-- 定义源代码输出目录 -->
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<!-- 定义测试代码输出目录 -->
<outputRelativeToContentRoot value="true" />
<!-- 定义输出目录相对于内容根目录 -->
<module name="exam" />
<!-- 定义模块名称 -->
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<!-- 定义Javac设置 -->
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<!-- 定义附加选项覆盖 -->
<module name="exam" options="-parameters" />
<!-- 定义模块名称和选项 -->
</option>
</component>
</project>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义XML文档的版本和编码 -->
<project version="4">
<!-- 定义项目版本 -->
<component name="Encoding">
<!-- 定义组件名称 -->
<file url="file://$PROJECT_DIR$/backend/src/main/java" charset="UTF-8" />
<!-- 定义文件路径和字符编码 -->
</component>
</project>

@ -1,6 +1,69 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AliAccessStaticViaInstance" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliArrayNamingShouldHaveBracket" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliDeprecation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliEqualsAvoidNull" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliLongLiteralsEndingWithLowercaseL" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliMissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AliWrapperTypeEquality" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAbstractClassShouldStartWithAbstractNaming" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAbstractMethodOrInterfaceMethodMustUseJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidApacheBeanUtilsCopy" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidCallStaticSimpleDateFormat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidCommentBehindStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidComplexCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidConcurrentCompetitionRandom" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidDoubleOrFloatEqualCompare" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidManuallyCreateThread" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidMissUseOfMathRandom" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidNegationOperator" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidNewDateGetTime" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidPatternCompileInMethod" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidReturnInFinally" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidStartWithDollarAndUnderLineNaming" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaAvoidUseTimer" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaBigDecimalAvoidDoubleConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaBooleanPropertyShouldNotStartWithIs" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaClassCastExceptionWithSubListToArrayList" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaClassCastExceptionWithToArray" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaClassMustHaveAuthor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaClassNamingShouldBeCamel" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaCollectionInitShouldAssignCapacity" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaCommentsMustBeJavadocFormat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaConcurrentExceptionWithModifyOriginSubList" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaConstantFieldShouldBeUpperCase" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaCountDownShouldInFinally" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaDontModifyInForeachCircle" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaEnumConstantsMustHaveComment" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaExceptionClassShouldEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaIbatisMethodQueryForList" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaLockShouldWithTryFinally" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaLowerCamelCaseVariableNaming" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaMethodReturnWrapperType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaMethodTooLong" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaPackageNaming" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaPojoMustOverrideToString" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaPojoMustUsePrimitiveField" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaPojoNoDefaultValue" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaRemoveCommentedCode" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaServiceOrDaoClassShouldEndWithImpl" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaSneakyThrowsWithoutExceptionType" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaStringConcat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaSwitchExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaTestClassShouldEndWithTestNaming" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaThreadLocalShouldRemove" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaThreadPoolCreation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaThreadShouldSetName" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaTransactionMustHaveRollback" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaUndefineMagicConstant" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaUnsupportedExceptionWithModifyAsList" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaUseQuietReferenceNotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaUseRightCaseForDateFormat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MapOrSetKeyShouldOverrideHashCodeEquals" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义项目版本 -->
<project version="4">
<!-- 定义远程仓库配置 -->
<component name="RemoteRepositoriesConfiguration">
<!-- 定义第一个远程仓库 -->
<remote-repository>
<!-- 定义远程仓库ID -->
<option name="id" value="central" />
<!-- 定义远程仓库名称 -->
<option name="name" value="Central Repository" />
<!-- 定义远程仓库URL -->
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<!-- 定义第二个远程仓库 -->
<remote-repository>
<!-- 定义远程仓库ID -->
<option name="id" value="central" />
<!-- 定义远程仓库名称 -->
<option name="name" value="Maven Central repository" />
<!-- 定义远程仓库URL -->
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<!-- 定义第三个远程仓库 -->
<remote-repository>
<!-- 定义远程仓库ID -->
<option name="id" value="jboss.community" />
<!-- 定义远程仓库名称 -->
<option name="name" value="JBoss Community repository" />
<!-- 定义远程仓库URL -->
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

@ -1,14 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 项目版本 -->
<project version="4">
<!-- 外部存储配置管理器 -->
<component name="ExternalStorageConfigurationManager" enabled="true" />
<!-- Maven项目管理器 -->
<component name="MavenProjectsManager">
<!-- 原始文件列表 -->
<option name="originalFiles">
<list>
<!-- 后端项目的pom.xml文件 -->
<option value="$PROJECT_DIR$/backend/pom.xml" />
</list>
</option>
</component>
<!-- 项目根管理器 -->
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8 (2)" project-jdk-type="JavaSDK">
<!-- 输出目录 -->
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -1,8 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义XML文档的版本和编码 -->
<project version="4">
<!-- 定义项目的版本 -->
<component name="ProjectModuleManager">
<!-- 定义项目的模块管理器 -->
<modules>
<!-- 定义项目的模块 -->
<module fileurl="file://$PROJECT_DIR$/.idea/spring-boot-online-exam-master.iml" filepath="$PROJECT_DIR$/.idea/spring-boot-online-exam-master.iml" />
<!-- 定义项目的模块文件路径 -->
</modules>
</component>
</project>
</project>
<!-- 定义项目的根元素 -->

@ -1,8 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义XML文档的版本和编码 -->
<project version="4">
<!-- 定义一个项目版本为4 -->
<component name="DdlMappings">
<!-- 定义一个组件名称为DdlMappings -->
<mapping uuid="3f2269d9-40c7-4022-bc5b-913dfbbc6cdc" name="@localhost Mapping">
<!-- 定义一个映射uuid为3f2269d9-40c7-4022-bc5b-913dfbbc6cdc名称为@localhost Mapping -->
<data-sources db="52ce0e2c-1ca8-4087-a535-1632d84f6e81" ddl="7ee42c98-c0b3-46e7-9153-4802360edee6" />
<!-- 定义数据源数据库为52ce0e2c-1ca8-4087-a535-1632d84f6e81DDL为7ee42c98-c0b3-46e7-9153-4802360edee6 -->
</mapping>
</component>
</project>

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义一个项目版本号为4 -->
<project version="4">
<!-- 定义一个组件名称为SqlDialectMappings -->
<component name="SqlDialectMappings">
<!-- 定义一个文件URL为PROJECT方言为MySQL -->
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<!-- 定义项目版本 -->
<component name="Palette2">
<!-- 定义调色板组件 -->
<group name="Swing">
<!-- 定义Swing组件组 -->
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义水平间距组件 -->
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
<!-- 定义默认约束 -->
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义垂直间距组件 -->
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义面板组件 -->
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<!-- 定义滚动面板组件 -->
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义按钮组件 -->
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<!-- 定义默认约束 -->
<initial-values>
<!-- 定义初始值 -->
<property name="text" value="Button" />
<!-- 定义按钮文本 -->
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义单选按钮组件 -->
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<!-- 定义默认约束 -->
<initial-values>
<!-- 定义初始值 -->
<property name="text" value="RadioButton" />
<!-- 定义单选按钮文本 -->
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义复选框组件 -->
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<!-- 定义默认约束 -->
<initial-values>
<!-- 定义初始值 -->
<property name="text" value="CheckBox" />
<!-- 定义复选框文本 -->
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义标签组件 -->
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<!-- 定义默认约束 -->
<initial-values>
<!-- 定义初始值 -->
<property name="text" value="Label" />
<!-- 定义标签文本 -->
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义文本框组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<!-- 定义默认约束 -->
<preferred-size width="150" height="-1" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义密码框组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<!-- 定义默认约束 -->
<preferred-size width="150" height="-1" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义格式化文本框组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<!-- 定义默认约束 -->
<preferred-size width="150" height="-1" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义文本区域组件 -->
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="150" height="50" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义文本面板组件 -->
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="150" height="50" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义编辑面板组件 -->
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="150" height="50" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义组合框组件 -->
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义表格组件 -->
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="150" height="50" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义列表组件 -->
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="150" height="50" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义树组件 -->
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="150" height="50" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义选项卡面板组件 -->
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="200" height="200" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义分割面板组件 -->
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<!-- 定义默认约束 -->
<preferred-size width="200" height="200" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<!-- 定义微调器组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义滑块组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义分隔符组件 -->
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义进度条组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义工具栏组件 -->
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<!-- 定义默认约束 -->
<preferred-size width="-1" height="20" />
<!-- 定义首选大小 -->
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<!-- 定义工具栏分隔符组件 -->
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
<!-- 定义默认约束 -->
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<!-- 定义滚动条组件 -->
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
<!-- 定义默认约束 -->
</item>
</group>
</component>
</project>

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -1,108 +1,18 @@
# spring-boot-online-exam
> 在线Demo预览http://129.211.88.191 账户分别是admin、teacher、student密码是admin123。视频讲解代码https://www.bilibili.com/video/BV1FP4y1L7xt/
> 好消息一个小伙伴做了Python实现欢迎大家starhttps://github.com/xingxingzaixian/django-drf-online-exam
## 1.快速体验
### 1.1 事先准备
> clone代码并进入代码路径
```shell
git clone git@github.com:lsgwr/spring-boot-online-exam.git
cd spring-boot-online-exam
```
下面按照Linux和windows说明快速搭建的方法
### 1.2 Linux
执行代码下的脚本start.sh即可
然后访问 http://ip:80 即可访问自己搭建的考试系统
### 1.3 windows
+ 1.安装JDK推荐JDK8
+ 2.从官方仓库下载发布的jar包建议选择最新版https://github.com/lsgwr/spring-boot-online-exam/releases
+ 3.安装MySQL创建数据库exam并初始化密码为aA111111导入doc/sql/exam.sql文件来创建数据库
+ 4.启动jar包`java -jar exam.jar`
+ 5.访问http://ip:9527 即可访问自己搭建的考试系统
## 2.介绍
基于springboot的在线考试系统
### 2.1 功能简介
+ 支持单选题、多选题、判断题
+ 支持学生(student)、教师(teacher)、管理员(admin)三种角色
+ 学生:参加考试和查看我的考试
+ 教师:学生的所有权限+创建/编辑题目+创建/编辑考试
+ 管理员:教师的所有权限+管理用户
### 2.3 软件架构
> 前后端分离,前段组件化,方便二次开发;后端
+ 后端采用SpringBoot+JPA++Swagger2+JWT校验,根据不同用户的权限返回给用户不同的数据
+ 后端采用Vue+AntDesign,组件化拆分,封装了很多年公共组件,方便维护和二次开发
### 2.3 使用教程
+ 1.下载代码
```shell
git clone https://github.com/19920625lsg/spring-boot-online-exam.git
```
+ 2.初始化数据库
> 安装mysql的步骤这里省略网上的教程很多。安装好mysql后新建exam数据库密码和`spring-boot-online-exam/backend/exam/src/main/resources/application.yml`的`password: xxxxxx`保持一致,然后导入`spring-boot-online-exam/doc/sql/exam.sql`
+ 3.启动后端
> 打开`spring-boot-online-exam/backend/exam`这个Maven项目可以在IDE里启动或者执行`mvn install`生成jar包启动
+ 4.启动前端
+ 进入到前端代码路径 `cd spring-boot-online-exam/frontend/exam/`
+ 安装依赖 `npm install`
+ 启动前端 `npm run serve`
+ 5.部署完毕,查看效果
> 打开 http://localhost:8000 或者 http://本机ip:8000 即可查看演示效果
## 3.功能图示
+ 1.管理题目
+ 1.1 题目列表
> ![题目查看](doc/images/question_list.png)
+ 1.2 题目创建
> ![题目创建](doc/images/question_create.png)
+ 1.3 题目更新
> ![题目更新](doc/images/question_update.png)
+ 2.考试管理
+ 2.1 考试列表
> ![考试查看](doc/images/exam_list.png)
+ 2.2 考试创建
> ![考试创建](doc/images/exam_create.png)
+ 2.3 考试更新(`还有点小bug开发中`)
> ![考试更新](doc/images/exam_update.png)
+ 3.我的考试
+ 3.1 参加考试
> 在"考试列表"模块点击自己想参加的考试卡片即可
> ![参加考试1](doc/images/exam_join.png)
> ![参加考试2](doc/images/exam_join2.png)
+ 3.2 考试记录查看
> ![考试记录查看](doc/images/exam_detail.png)
## 4.参与贡献
1. Fork 本仓库
2. 新建 exam_xxx 分支
3. 提交代码
4. 新建 Pull Request
## 5.Todo
+ `√`0.修复issue提地bug题目创建失败
+ `√`1.考试详情编辑
+ 2.支持题目和考试的删除`删除的话比较麻烦先不做了最好是弄个visible字段不实际删除要不后面有些关联数据找不到就不好了`
> 如果题目有关联的考试则必须先删除对应的考试,反过来删除考试则不用删除题目
+ 3.图片改成base64存到数据库中
+ 4.题干和选项支持富文本
+ 5.支持批量导入题目
+ 6.新增用户管理、学科管理功能
+ 7.老师能考到所有学生的成绩以及考试的统计信息
+ 8.更多的数据分析功能
+ 9.支持容器化一键式部署(编好Dockerfile)
+ 10.支持移动端最好用uniapp做
+ ......抓紧做吧,争取每周末做一点......
ResultVO<RecordDetailVo> getExamRecordDetail(@PathVariable String recordId) {
// 定义返回结果
ResultVO<RecordDetailVo> resultVO;
try {
// 调用examService获取考试记录详情
RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId);
// 返回成功结果
resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回失败结果
resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null);
}
// 返回结果
return resultVO;
}
}

@ -16,15 +16,22 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Slf4j
public class CORSConf {
// 创建一个 WebMvcConfigurer Bean
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
// 重写 addCorsMappings 方法
@Override
public void addCorsMappings(CorsRegistry registry) {
// 记录初始化 CORSConfiguration 配置
log.info("初始化 CORSConfiguration 配置");
// 添加映射
registry.addMapping("/**")
// 允许所有请求头
.allowedHeaders("*")
// 允许所有请求方法
.allowedMethods("*")
// 允许所有请求来源
.allowedOrigins("*");
}
};

@ -15,13 +15,15 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
// 注入LoginInterceptor
@Autowired
private LoginInterceptor loginInterceptor;
// 添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截user下的api
registry.addInterceptor(loginInterceptor).addPathPatterns("/api/**");
}
}
}

@ -9,10 +9,14 @@ import org.springframework.http.HttpStatus;
@Configuration
public class ServletConfig {
// 创建一个WebServerFactoryCustomizer bean用于自定义WebServerFactory
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
// 返回一个lambda表达式用于自定义WebServerFactory
return factory -> {
// 创建一个ErrorPage对象用于处理404错误
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/");
// 将ErrorPage对象添加到WebServerFactory中
factory.addErrorPages(error404Page);
};
}

@ -29,32 +29,48 @@ public class Swagger2Config {
@Bean
public Docket api() {
// 创建一个参数构建器
ParameterBuilder ticketPar = new ParameterBuilder();
// 创建一个参数集合
List<Parameter> pars = new ArrayList<>();
// 设置参数名
ticketPar.name("Access-Token").description("Rest接口权限认证token,无需鉴权可为空")
// 设置参数类型
.modelRef(new ModelRef("string")).parameterType("header")
//header中的ticket参数非必填传空也可以
// header中的ticket参数非必填传空也可以
.required(false).build();
//根据每个方法名也知道当前方法在设置什么参数
// 根据每个方法名也知道当前方法在设置什么参数
pars.add(ticketPar.build());
return new Docket(DocumentationType.SWAGGER_2)
// 设置文档信息
.apiInfo(apiInfo())
// 选择要扫描的接口
.select()
// 自行修改为自己的包路径
.apis(RequestHandlerSelectors.basePackage("lsgwr"))
// 选择所有的路径
.paths(PathSelectors.any())
// 构建Docket
.build()
// 设置全局参数
.globalOperationParameters(pars);
}
private ApiInfo apiInfo() {
// 创建ApiInfo对象
return new ApiInfoBuilder()
// 设置API标题
.title("online exam by springboot")
// 设置API描述
.description("在线考试系统 by 梁山广 at 2021")
// 设置API服务条款URL
.termsOfServiceUrl("https://github.com/19920625lsg/spring-boot-online-exam")
// 设置API版本
.version("2.0")
// 设置API联系人信息
.contact(new Contact("liangshanguang", "https://github.com/lsgwr/spring-boot-online-exam", "liangshanguang2@gmail.com"))
// 构建ApiInfo对象
.build();
}
}

@ -1,6 +1,9 @@
/**
* @Description : REST,HTTP,,
*/
/***********************************************************
* @Description :
* @author : 广(Laing Shan Guang)
* @date : 2019-05-28 08:04
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.controller;
import lsgwr.exam.entity.Exam;
@ -26,180 +29,207 @@ public class ExamController {
private ExamService examService;
/**
* @Description: 使examService.getQuestionAll(),ResultVO,
* @Description: 使examService.getQuestionAll(),
*
* ResultVO,
*
* &#064;ApiOperationAPI,SwaggerUI
*/
@GetMapping("/question/all")
@ApiOperation("获取所有问题的列表")
ResultVO<List<QuestionVo>> getQuestionAll() {
// 获取全部问题列表。
ResultVO<List<QuestionVo>> getQuestionAll() {
// 定义返回值。
ResultVO<List<QuestionVo>> resultVO;
try {
// 调用examService获取全部问题列表。
List<QuestionVo> questionAll = examService.getQuestionAll();
// 返回成功结果。
resultVO = new ResultVO<>(0, "获取全部问题列表成功", questionAll);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回失败结果。
resultVO = new ResultVO<>(-1, "获取全部问题列表失败", null);
}
// 返回结果。
return resultVO;
}
/**
* @Description: QuestionVo使examService.updateQuestion()
* @param questionVo QuestionVo
*/
@PostMapping("/question/update")
@ApiOperation("更新问题")
ResultVO<QuestionVo> questionUpdate(@RequestBody QuestionVo questionVo) {
// 完成问题的更新
// 完成问题的更新。
System.out.println(questionVo);
try {
// 调用examService的updateQuestion方法更新问题。
QuestionVo questionVoResult = examService.updateQuestion(questionVo);
// 返回更新成功的结果。
return new ResultVO<>(0, "更新问题成功", questionVoResult);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回更新失败的结果。
return new ResultVO<>(-1, "更新问题失败", null);
}
}
/**
*
* @Description: QuestionCreateSimplifyVoQuestionCreateVoID使examService.questionCreate()
* @param questionCreateSimplifyVo QuestionCreateSimplifyVo
* @Description: QuestionCreateSimplifyVo
* QuestionCreateVoID
*
* 使examService.questionCreate()
*
* @param questionCreateSimplifyVo
*
* QuestionCreateSimplifyVo
*
* @param request HttpServletRequestID
*
* @return ResultVO<String>
*
*
*/
@PostMapping("/question/create")
@ApiOperation("创建问题")
ResultVO<String> questionCreate(@RequestBody QuestionCreateSimplifyVo questionCreateSimplifyVo, HttpServletRequest request) {
// 创建一个QuestionCreateVo对象。
QuestionCreateVo questionCreateVo = new QuestionCreateVo();
// 把能拷贝过来的属性都拷贝过来
// 把能拷贝过来的属性都拷贝过来
BeanUtils.copyProperties(questionCreateSimplifyVo, questionCreateVo);
// 设置创建者信息
// 设置创建者信息
String userId = (String) request.getAttribute("user_id");
questionCreateVo.setQuestionCreatorId(userId);
System.out.println(questionCreateVo);
try {
// 调用examService的questionCreate方法创建问题。
examService.questionCreate(questionCreateVo);
// 返回问题创建成功的ResultVO。
return new ResultVO<>(0, "问题创建成功", null);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回创建问题失败的ResultVO。
return new ResultVO<>(-1, "创建问题失败", null);
}
}
/**
* @Description: ,使examService.getSelections()ResultVO
* @Description: ,
*
* 使examService.getSelections()ResultVO
*
* @return ResultVO<QuestionAllVo>
*/
@GetMapping("/question/selection")
@ApiOperation("获取问题分类的相关选项")
ResultVO<QuestionSelectionVo> getSelections() {
// 获取问题分类选项。
ResultVO<QuestionSelectionVo> getSelections() {
// 调用examService的getSelections方法获取问题分类选项。
QuestionSelectionVo questionSelectionVo = examService.getSelections();
// 如果获取成功。
if (questionSelectionVo != null) {
// 返回成功的结果。
return new ResultVO<>(0, "获取问题分类选项成功", questionSelectionVo);
} else {
// 否则返回失败的结果。
return new ResultVO<>(-1, "获取问题分类选项失败", null);
}
}
/**
* @Description: 使examService.getQuestionDetail(id),ResultVO,
* @param id id
* @return ResultVO<QuestionDetailVo>
*/
@GetMapping("/question/detail/{id}")
@ApiOperation("根据问题的id获取问题的详细信息")
ResultVO<QuestionDetailVo> getQuestionDetail(@PathVariable String id) {
// 根据问题id获取问题的详细信息
// 根据问题id获取问题的详细信息。
System.out.println(id);
ResultVO<QuestionDetailVo> resultVO;
try {
// 调用examService的getQuestionDetail方法根据问题id获取问题的详细信息。
QuestionDetailVo questionDetailVo = examService.getQuestionDetail(id);
// 如果获取成功则返回ResultVO对象状态码为0提示信息为"获取问题详情成功"数据为questionDetailVo。
resultVO = new ResultVO<>(0, "获取问题详情成功", questionDetailVo);
} catch (Exception e) {
// 如果获取失败则打印异常信息并返回ResultVO对象状态码为-1提示信息为"获取问题详情失败"数据为null。
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取问题详情失败", null);
}
// 返回ResultVO对象。
return resultVO;
}
/**
* @Description: 使examService.getExamAll(),ResultVO,
* @return ResultVO<QuestionDetailVo>
*/
@GetMapping("/all")
@ApiOperation("获取全部考试的列表")
ResultVO<List<ExamVo>> getExamAll() {
// 需要拼接前端需要的考试列表对象
// 需要拼接前端需要的考试列表对象。
ResultVO<List<ExamVo>> resultVO;
try {
// 调用examService的getExamAll方法获取全部考试的列表。
List<ExamVo> examVos = examService.getExamAll();
// 将获取到的考试列表封装到ResultVO对象中并返回。
resultVO = new ResultVO<>(0, "获取全部考试的列表成功", examVos);
} catch (Exception e) {
// 捕获异常,并打印异常信息。
e.printStackTrace();
// 将异常信息封装到ResultVO对象中并返回。
resultVO = new ResultVO<>(-1, "获取全部考试的列表失败", null);
}
return resultVO;
}
/**
* @Description: 使examService.getExamQuestionTypeList(),ResultVO,
* @return ResultVO<ExamQuestionTypeVo>
*/
@GetMapping("/question/type/list")
@ApiOperation("获取问题列表,按照单选、多选和判断题分类返回")
ResultVO<ExamQuestionTypeVo> getExamQuestionTypeList() {
// 获取问题的分类列表
// 获取问题的分类列表。
ResultVO<ExamQuestionTypeVo> resultVO;
try {
// 调用examService的getExamQuestionType方法获取问题分类列表。
ExamQuestionTypeVo examQuestionTypeVo = examService.getExamQuestionType();
// 如果获取成功,则返回成功的结果。
resultVO = new ResultVO<>(0, "获取问题列表成功", examQuestionTypeVo);
} catch (Exception e) {
// 如果获取失败,则打印异常信息,并返回失败的结果。
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取问题列表失败", null);
}
// 返回结果。
return resultVO;
}
/**
* @Description: 使examService.createExam(),ResultVO,
* @param examCreateVo
* @param request id
* @return ResultVO<Exam>
*/
@PostMapping("/create")
@ApiOperation("创建考试")
ResultVO<Exam> createExam(@RequestBody ExamCreateVo examCreateVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
// 从前端传参数过来,在这里完成考试的入库。
ResultVO<Exam> resultVO;
// 获取当前用户的id。
String userId = (String) request.getAttribute("user_id");
try {
// 调用examService的create方法将examCreateVo和userId作为参数传入创建考试。
Exam exam = examService.create(examCreateVo, userId);
// 创建一个ResultVO对象将创建成功的考试信息返回。
resultVO = new ResultVO<>(0, "创建考试成功", exam);
} catch (Exception e) {
// 捕获异常打印异常信息并创建一个ResultVO对象将创建失败的考试信息返回。
e.printStackTrace();
resultVO = new ResultVO<>(-1, "创建考试失败", null);
}
// 返回ResultVO对象。
return resultVO;
}
/**
* @Description: 使examService.updateExam(),ResultVO,
* @param examVo
* @param request id
* @return
*/
@PostMapping("/update")
@ApiOperation("更新考试")
ResultVO<Exam> updateExam(@RequestBody ExamVo examVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
// 从前端传参数过来,在这里完成考试的入库。
ResultVO<Exam> resultVO;
// 获取当前用户id。
String userId = (String) request.getAttribute("user_id");
try {
// 调用service层更新考试。
Exam exam = examService.update(examVo, userId);
// 返回更新成功的resultVO。
resultVO = new ResultVO<>(0, "更新考试成功", exam);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回更新失败的resultVO。
resultVO = new ResultVO<>(-1, "更新考试失败", null);
}
return resultVO;
@ -207,32 +237,41 @@ public class ExamController {
/**
* @Description:
* @return ResultVO<List<ExamCardVo>>
*
* @return ResultVO<List<ExamCardVo>>
*
*
*/
@GetMapping("/card/list")
@ApiOperation("获取考试列表,适配前端卡片列表")
ResultVO<List<ExamCardVo>> getExamCardList() {
// 获取考试列表卡片
// 获取考试列表卡片
ResultVO<List<ExamCardVo>> resultVO;
try {
// 调用examService的getExamCardList方法获取考试列表卡片。
List<ExamCardVo> examCardVoList = examService.getExamCardList();
// 如果获取成功,则返回成功的结果。
resultVO = new ResultVO<>(0, "获取考试列表卡片成功", examCardVoList);
} catch (Exception e) {
// 如果获取失败,则打印异常信息,并返回失败的结果。
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取考试列表卡片失败", null);
}
// 返回结果
return resultVO;
}
/**
* @Description: id
*
* @param id id
*
* @return ResultVO<ExamDetailVo>
*/
@GetMapping("/detail/{id}")
@ApiOperation("根据考试的id获取考试详情")
ResultVO<ExamDetailVo> getExamDetail(@PathVariable String id) {
// 根据id获取考试详情
// 根据id获取考试详情
ResultVO<ExamDetailVo> resultVO;
try {
ExamDetailVo examDetail = examService.getExamDetail(id);
@ -244,42 +283,56 @@ public class ExamController {
}
/**
* @Description: 使examService.finishExam,ResultVO,
* @Description: 使examService.finishExam,
*
* ResultVO,
* @param examId id
*
* @param answersMap
*
* @param request id
* @return
*/
@PostMapping("/finish/{examId}")
@ApiOperation("根据用户提交的答案对指定id的考试判分")
// 完成考试
ResultVO<ExamRecord> finishExam(@PathVariable String examId, @RequestBody HashMap<String, List<String>> answersMap, HttpServletRequest request) {
// 定义返回结果。
ResultVO<ExamRecord> resultVO;
try {
// 拦截器里设置上的用户id
// 拦截器里设置上的用户id
String userId = (String) request.getAttribute("user_id");
// 下面根据用户提交的信息进行判分,返回用户的得分情况
// 下面根据用户提交的信息进行判分,返回用户的得分情况
ExamRecord examRecord = examService.judge(userId, examId, answersMap);
// 返回结果。
resultVO = new ResultVO<>(0, "考卷提交成功", examRecord);
} catch (Exception e) {
e.printStackTrace();
// 返回错误结果。
resultVO = new ResultVO<>(-1, "考卷提交失败", null);
}
return resultVO;
}
/**
* @Description: 使examService.getExamRecordList,ResultVO,
* @Description: 使examService.getExamRecordList,
* ResultVO,
*
* @param request id
* @return ResultVO<List<ExamRecordVo>> ResultVOResultVO
*
* @return ResultVO<List<ExamRecordVo>>
*
* ResultVOResultVO
*/
@GetMapping("/record/list")
@ApiOperation("获取当前用户的考试记录")
// 获取考试记录列表。
ResultVO<List<ExamRecordVo>> getExamRecordList(HttpServletRequest request) {
ResultVO<List<ExamRecordVo>> resultVO;
try {
// 拦截器里设置上的用户id
// 拦截器里设置上的用户id
String userId = (String) request.getAttribute("user_id");
// 下面根据用户账号拿到他(她所有的考试信息)注意要用VO封装下
// 下面根据用户账号拿到他(她所有的考试信息)注意要用VO封装下
List<ExamRecordVo> examRecordVoList = examService.getExamRecordList(userId);
resultVO = new ResultVO<>(0, "获取考试记录成功", examRecordVoList);
} catch (Exception e) {
@ -290,21 +343,34 @@ public class ExamController {
}
/**
* @Description: 使examService.getExamRecordDetail,ResultVO,id
* @Description: 使examService.getExamRecordDetail,
*
* ResultVO,id
*
* @param recordId id
* @return ResultVO<RecordDetailVo> ResultVOResultVO
*
* @return ResultVO<RecordDetailVo>
* ResultVO
*
* ResultVO
*/
@GetMapping("/record/detail/{recordId}")
@ApiOperation("根据考试记录id获取考试记录详情")
ResultVO<RecordDetailVo> getExamRecordDetail(@PathVariable String recordId) {
// 定义返回结果。
ResultVO<RecordDetailVo> resultVO;
try {
// 调用examService获取考试记录详情。
RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId);
// 返回成功结果。
resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回失败结果。
resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null);
}
// 返回结果。
return resultVO;
}
}

@ -15,13 +15,22 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/***********************************************************
*
* @note : ,SwaggerSwaggerUI
*
*
*
*
* @author : 广
* * AjaxResponseEntity
* *
* * 1. - `MultipartFile`
* * 2. - `MultipartFile []`
*
* * Ajax
*
* ResponseEntity
* *
*
* * 1.
* - `MultipartFile`
* * 2.
* - `MultipartFile []`
* * 3. - `@ModelAttribute`
* @version : V1.0 at 2018/7/16 20:43
***********************************************************/
@ -32,47 +41,63 @@ import java.io.IOException;
public class UploadDownloadController {
// @Autowired
// AITestConfig aiTestConfig;
//
// @PostMapping("/upload/single")
// @ApiOperation("单文件上传")
// public String uploadFile(@RequestParam("file") MultipartFile uploadfile) {
// return FileTransUtil.uploadFile(uploadfile, "/root/" + File.separator + uploadfile.getOriginalFilename());
// }
@ApiOperation("单文件上传,支持同时传入参数")
@PostMapping("/api/upload/singleAndparas")
public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) {
// 单文件上传。
public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) {
// 调用FileTransUtil工具类中的uploadFile方法将上传的文件和目录作为参数传入。
return FileTransUtil.uploadFile(uploadfile, dir);
}
// 单文件上传支持同时传入参数Model。
@ApiOperation("单文件上传,支持同时传入参数,Model")
@PostMapping("/upload/single/model")
public String singleUploadFileModel(@ModelAttribute("model") UploadModel2 model) {
// 调用FileTransUtil工具类中的uploadFile方法将上传的文件和目录作为参数传入。
return FileTransUtil.uploadFile(model.getFile(), model.getDir());
}
// 多文件上传,支持同时传入参数。
@ApiOperation("多文件上传,支持同时传入参数")
@PostMapping("upload/multiAndparas")
public String uploadFileMulti(@RequestParam("dir") String dir, @RequestParam("files") MultipartFile[] uploadfiles) {
// 调用FileTransUtil工具类中的uploadFiles方法将上传的文件数组和目录作为参数传入。
return FileTransUtil.uploadFiles(uploadfiles, dir);
}
// 多文件上传,支持同时传入参数。
@ApiOperation("多文件上传,支持同时传入参数")
@PostMapping(value = "/upload/multi/model")
public String multiUploadFileModel(@ModelAttribute(("model")) UploadModel model) {
// 调用FileTransUtil工具类中的uploadFiles方法将上传的文件数组和目录作为参数传入。
return FileTransUtil.uploadFiles(model.getFiles(), model.getDir());
}
// Get下载文件
@ApiOperation("Get下载文件")
@GetMapping(value = "/download/get")
public ResponseEntity<InputStreamResource> downloadFileGet(@RequestParam String filePath) throws IOException {
// 调用FileTransUtil工具类中的downloadFile方法将文件路径作为参数传入。
return FileTransUtil.downloadFile(filePath);
}
// Post下载文件
@ApiOperation("Post下载文件")
@PostMapping(value = "/download/post")
public ResponseEntity<InputStreamResource> downloadFilePost(@RequestBody DownloadQo downloadQo) throws IOException {
// 调用FileTransUtil工具类中的downloadFile方法将文件路径作为参数传入。
return FileTransUtil.downloadFile(downloadQo.getPath());
}
}

@ -62,18 +62,27 @@ public class UserController {
@GetMapping("/user-info")
@ApiOperation("获取用户信息")
// 根据请求获取用户信息
ResultVO<UserVo> getUserInfo(HttpServletRequest request) {
// 从请求中获取用户ID
String userId = (String) request.getAttribute("user_id");
// 根据用户ID获取用户信息
UserVo userVo = userService.getUserInfo(userId);
// 返回用户信息
return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userVo);
}
@GetMapping("/info")
@ApiOperation("获取用户的详细信息,包括个人信息页面和操作权限")
// 获取用户信息的接口
ResultVO<UserInfoVo> getInfo(HttpServletRequest request) {
// 打印进入接口的日志
System.out.println("进入/user/info的获取用户信息的接口");
// 获取用户ID
String userId = (String) request.getAttribute("user_id");
// 调用userService获取用户信息
UserInfoVo userInfoVo = userService.getInfo(userId);
// 返回结果
return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userInfoVo);
}
@ -87,4 +96,4 @@ public class UserController {
System.out.println("用户名:" + username);
return "用户id" + userId + "\n用户名" + username;
}
}
}

@ -10,9 +10,21 @@ import lombok.Data;
@Data
public class RegisterDTO {
/**
*
*/
private String email;
/**
*
*/
private String password;
/**
*
*/
private String password2;
/**
*
*/
private String mobile;
/**
*

@ -15,13 +15,18 @@ import javax.persistence.Id;
@Data
@Entity
public class Action {
// 定义一个主键
@Id
// 自动生成主键
@GeneratedValue
private Integer actionId;
// 定义一个动作名称
private String actionName;
// 定义一个动作描述
private String actionDescription;
// 定义一个默认选中状态
private Boolean defaultCheck;
}

@ -20,36 +20,62 @@ import java.util.Date;
@Data
@DynamicUpdate
public class Exam {
// 考试ID
@Id
private String examId;
// 考试名称
private String examName;
// 考试头像
private String examAvatar;
// 考试描述
private String examDescription;
// 考试问题ID
private String examQuestionIds;
// 考试单选题ID
private String examQuestionIdsRadio;
// 考试多选题ID
private String examQuestionIdsCheck;
// 考试判断题ID
private String examQuestionIdsJudge;
// 考试总分
private Integer examScore;
// 考试单选题总分
private Integer examScoreRadio;
// 考试多选题总分
private Integer examScoreCheck;
// 考试判断题总分
private Integer examScoreJudge;
// 考试创建者ID
private String examCreatorId;
// 考试时间限制
private Integer examTimeLimit;
// 考试开始时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examStartDate;
// 考试结束时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examEndDate;
/**
*
*
* , Java
*
*
*/
// 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* Java
* @DynamicUpdate
*
*
* @DynamicUpdate
*
*
*/
// 更新时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

@ -18,39 +18,58 @@ import java.util.Date;
@Entity
public class ExamRecord {
/**
*
*
*
*
*
*/
@Id
private String examRecordId;
/**
*
* id
*
*/
private String examId;
/**
* (_-),
*
* (_
*
* -),
*/
private String answerOptionIds;
/**
*
* userid
*
*/
private String examJoinerId;
/**
*
*
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examJoinDate;
/**
*
* ()
*
*/
private Integer examTimeCost;
/**
*
*
*
*/
private Integer examJoinScore;
/**
*
*
*
*/
private Integer examResultLevel;
}

@ -16,9 +16,12 @@ import javax.persistence.Id;
@Data
@Entity
public class ExamRecordLevel {
// 考试记录等级ID
@Id
@GeneratedValue
private Integer examRecordLevelId;
// 考试记录等级名称
private String examRecordLevelName;
// 考试记录等级描述
private String examRecordLevelDescription;
}

@ -15,13 +15,17 @@ import javax.persistence.Id;
@Data
@Entity
public class Page {
// 定义一个页面ID使用@Id注解表示该字段为主键使用@GeneratedValue注解表示该字段自动生成
@Id
@GeneratedValue
private Integer pageId;
// 定义一个页面名称
private String pageName;
// 定义一个页面描述
private String pageDescription;
// 定义一个动作ID用于存储页面上的动作
private String actionIds;
}

@ -19,21 +19,32 @@ import java.util.Date;
@Entity
@DynamicUpdate
public class Question {
// 问题ID
@Id
private String questionId;
// 问题名称
private String questionName;
// 问题分数
private Integer questionScore;
// 问题创建者ID
private String questionCreatorId;
// 问题等级ID
private Integer questionLevelId;
// 问题类型ID
private Integer questionTypeId;
// 问题分类ID
private Integer questionCategoryId;
// 问题描述
private String questionDescription;
// 问题选项ID
private String questionOptionIds;
// 问题答案选项ID
private String questionAnswerOptionIds;
/**
* , Java
*/
// 创建时间格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@ -41,6 +52,7 @@ public class Question {
* Java
* @DynamicUpdate
*/
// 更新时间格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

@ -18,14 +18,19 @@ import javax.persistence.Id;
@Entity
public class QuestionCategory {
// 主键
@Id
// 自动生成主键
@GeneratedValue
// 将该字段映射到JSON中的id字段
@JsonProperty("id")
private Integer questionCategoryId;
// 将该字段映射到JSON中的name字段
@JsonProperty("name")
private String questionCategoryName;
// 将该字段映射到JSON中的description字段
@JsonProperty("description")
private String questionCategoryDescription;
}

@ -17,8 +17,10 @@ import javax.persistence.Id;
@Entity
@Data
public class QuestionLevel {
// 定义实体类,表示问题等级
@Id
@GeneratedValue
// 定义主键,自动生成
@JsonProperty("id")
private Integer questionLevelId;
@ -27,4 +29,4 @@ public class QuestionLevel {
@JsonProperty("description")
private String questionLevelDescription;
}
}

@ -15,8 +15,11 @@ import javax.persistence.Id;
@Data
@Entity
public class QuestionOption {
// 定义问题选项的ID
@Id
private String questionOptionId;
// 定义问题选项的内容
private String questionOptionContent;
// 定义问题选项的描述
private String questionOptionDescription;
}

@ -17,14 +17,19 @@ import javax.persistence.Id;
@Data
@Entity
public class QuestionType {
// 主键
@Id
// 自动生成主键
@GeneratedValue
// 将该字段映射到JSON中的id字段
@JsonProperty("id")
private Integer questionTypeId;
// 将该字段映射到JSON中的name字段
@JsonProperty("name")
private String questionTypeName;
// 将该字段映射到JSON中的description字段
@JsonProperty("description")
private String questionTypeDescription;
}
}

@ -16,14 +16,22 @@ import javax.persistence.Id;
@Data
@Entity
public class Role {
// 角色ID
@Id
@GeneratedValue
private Integer roleId;
// 角色名称
private String roleName;
// 角色描述
private String roleDescription;
// 角色详情
private String roleDetail;
/**
*
*
* 访(-)
*
*
*/
private String rolePageIds;
}

@ -19,27 +19,47 @@ import java.util.Date;
@Entity
@DynamicUpdate
public class User {
// 用户ID
@Id
private String userId;
// 用户名
private String userUsername;
// 用户昵称
private String userNickname;
// 用户密码
private String userPassword;
// 用户角色ID
private Integer userRoleId;
// 用户头像
private String userAvatar;
// 用户描述
private String userDescription;
// 用户邮箱
private String userEmail;
// 用户电话
private String userPhone;
/**
* , Java
*
*
* ,
*
* Java
*
*
*/
// 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* Java
* @DynamicUpdate
*
* Java
* @DynamicUpdate
*
*
*/
// 更新时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}
}

@ -4,12 +4,19 @@ import lombok.Getter;
/**
*
*
*
* @author liangshanguang
*
*
*/
@Getter
public enum LoginTypeEnum {
/**
*
* 12
*
*
*/
USERNAME(1, "用户名"),
EMAIL(2, "邮箱");

@ -17,11 +17,14 @@ public enum ResultEnum {
ORDER_UPDATE_ERR(15, "考试更新异常"),
ORDER_DETAIL_EMPTY(16, "用户详情为空");
// 构造方法,用于初始化错误码和错误信息
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
// 错误码
private Integer code;
// 错误信息
private String message;
}

@ -12,7 +12,11 @@ import lombok.Getter;
public enum RoleEnum {
/**
*
*
* role
*
*
*/
ADMIN(1, "管理员"),
TEACHER(2, "教师"),

@ -11,15 +11,22 @@ import lombok.Getter;
@Getter
public class ExamException extends RuntimeException {
// 定义异常代码
private Integer code;
// 构造函数传入ResultEnum枚举类型
public ExamException(ResultEnum resultEnum) {
// 调用父类构造函数传入ResultEnum枚举类型的消息
super(resultEnum.getMessage());
// 将ResultEnum枚举类型的代码赋值给异常代码
this.code = resultEnum.getCode();
}
// 构造函数,传入异常代码和消息
public ExamException( Integer code, String message) {
// 调用父类构造函数,传入消息
super(message);
// 将传入的异常代码赋值给异常代码
this.code = code;
}
}

@ -27,44 +27,67 @@ import java.io.PrintWriter;
public class LoginInterceptor implements HandlerInterceptor {
/**
*
* @Component使pplication.yml
*
*
*/
@Value("${interceptors.auth-ignore-uris}")
private String authIgnoreUris;
/**
*
*
* controller
*
*
* @param request
*
* @param response
*
* @param handler
*
* @return
*
* @throws Exception
*
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 打印进入拦截器
System.out.println("进入拦截器啦!");
// 获取请求的URI
String uri = request.getRequestURI();
// 打印URI
System.out.println(uri);
// 打印无需拦截的接口路径
System.out.println("无需拦截的接口路径:" + authIgnoreUris);
// 将无需拦截的接口路径分割成数组
String[] authIgnoreUriArr = authIgnoreUris.split(",");
// 登录和注册接口不需要进行token拦截和校验
for (String authIgnoreUri : authIgnoreUriArr) {
// 如果请求的URI在无需拦截的接口路径中则直接返回true
if (authIgnoreUri.equals(uri)) {
return true;
}
}
// 注意要和前端适配Access-Token属性前端会在登陆后的每个接口请求头加Access-Token属性
// 获取请求头中的token
String token = request.getHeader("Access-Token");
// 如果token不在header中则可能在参数中
if (token == null) {
// token不在header中时也可能在参数中(RequestParam)
// 从参数中获取token
token = request.getParameter("token");
}
// 如果token不为空
if (token != null) {
// 请求中是携带参数的
// 校验token
Claims claims = JwtUtils.checkJWT(token);
// 如果token校验失败
if (claims == null) {
// 返回null说明用户篡改了token导致校验失败
// 返回错误信息
sendJsonMessage(response, JsonData.buildError("token无效请重新登录"));
return false;
}
@ -73,10 +96,12 @@ public class LoginInterceptor implements HandlerInterceptor {
// 用户名
String username = (String) claims.get("username");
// 把这两个参数放到请求中从而可以在controller中获取到不需要在controller中在用Jwt解密了,request.getAttribute("属性名")即可获取
// 将用户id和用户名放到请求中
request.setAttribute("user_id", id);
request.setAttribute("username", username);
return true;
}
// 如果token为空则返回错误信息
sendJsonMessage(response, JsonData.buildError("token为null,请先登录!"));
return false;
}
@ -88,12 +113,19 @@ public class LoginInterceptor implements HandlerInterceptor {
* @param obj
* @throws Exception
*/
public static void sendJsonMessage(HttpServletResponse response, Object obj) throws Exception {
// 发送JSON消息
public static void sendJsonMessage(HttpServletResponse response, Object obj) throws Exception {
// 创建Gson对象
Gson g = new Gson();
// 设置响应内容类型为JSON
response.setContentType("application/json; charset=utf-8");
// 获取响应的PrintWriter对象
PrintWriter writer = response.getWriter();
// 将对象转换为JSON字符串并写入响应
writer.print(g.toJson(obj));
// 关闭PrintWriter对象
writer.close();
// 刷新响应缓冲区
response.flushBuffer();
}
}

@ -13,5 +13,6 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public class DownloadQo {
// 下载路径
String path;
}
}

@ -15,15 +15,21 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class LoginQo {
/**
*
* 12
*
*/
private Integer loginType;
/**
*
* /
*
*/
private String userInfo;
/**
*
*
*
*/
private String password;
}

@ -17,11 +17,19 @@ import org.springframework.web.multipart.MultipartFile;
@NoArgsConstructor
public class UploadModel {
/**
*
*
*
*
*
*/
private MultipartFile[] files;
/**
*
*
*
*
*
*/
private String dir;
}

@ -17,11 +17,19 @@ import org.springframework.web.multipart.MultipartFile;
@NoArgsConstructor
public class UploadModel2 {
/**
*
*
*
*
*
*/
private MultipartFile file;
/**
*
*
*
*
*
*/
private String dir;
}

@ -16,97 +16,177 @@ import java.util.List;
public interface ExamService {
/**
*
*
*
*
*/
List<QuestionVo> getQuestionAll();
/**
*
*
*
*
* @param questionVo
*
*
*
*/
QuestionVo updateQuestion(QuestionVo questionVo);
/**
*
*
*
*
*
* @param questionCreateVo
*
*
*/
void questionCreate(QuestionCreateVo questionCreateVo);
/**
*
*
*
*
* @return
*
*
*/
QuestionSelectionVo getSelections();
/**
*
*
*
*
* @param id id
*
*
* @return VO
*
*
*/
QuestionDetailVo getQuestionDetail(String id);
/**
*
*
*
*
*
*/
List<ExamVo> getExamAll();
/**
* 便
*
*
*
*
* @return
*
*
*
*/
ExamQuestionTypeVo getExamQuestionType();
/**
*
*
*
*
* @param examCreateVo
*
*
*
* @param userId id
*
*
*
* @return
*
*
*
*/
Exam create(ExamCreateVo examCreateVo, String userId);
/**
*
*
*
*
* @return
*
*
*/
List<ExamCardVo> getExamCardList();
/**
* id
*
*
*
*
* @param id exam
*
*
*
* @return VO
*
*
*/
ExamDetailVo getExamDetail(String id);
/**
*
*
*
*
* @param userId
*
*
* @param examId
*
*
* @param answersMap
*
*
* @return
*
*
*/
ExamRecord judge(String userId, String examId, HashMap<String, List<String>> answersMap);
/**
* id
*
*
*
* @param userId id
*
*
* @return
*
*
*/
List<ExamRecordVo> getExamRecordList(String userId);
/**
*
*
*
*
* @param recordId id
*
*
*
* @return
*
*
*
*/
RecordDetailVo getRecordDetail(String recordId);
@ -115,8 +195,17 @@ public interface ExamService {
*
*
* @param examVo
*
*
* @param userId
*
*
*
* @return
*
*
*
*
*/
Exam update(ExamVo examVo, String userId);
}

@ -16,30 +16,54 @@ public interface UserService {
/**
*
*
*
*
* @param registerDTO
*
*
*
* @return
*
*
*/
User register(RegisterDTO registerDTO);
/**
* token
*
*
*
* @param loginQo
*
*
* @return tokennull
*
*
*/
String login(LoginQo loginQo);
/**
* id
*
*
*
*
* @return
*
*
*/
UserVo getUserInfo(String userId);
/**
* ()
*
*
* @param userId id
*
*
* @return
*
*
*/
UserInfoVo getInfo(String userId);
}

@ -10,8 +10,8 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lsgwr.exam.entity.*;
import lsgwr.exam.enums.QuestionEnum;
import lsgwr.exam.service.ExamService;
import lsgwr.exam.repository.*;
import lsgwr.exam.service.ExamService;
import lsgwr.exam.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@ -23,33 +23,42 @@ import java.util.*;
@Transactional
public class ExamServiceImpl implements ExamService {
// 试卷仓库
private final ExamRepository examRepository;
// 考试记录仓库
private final ExamRecordRepository examRecordRepository;
// 问题仓库
private final QuestionRepository questionRepository;
// 用户仓库
private final UserRepository userRepository;
// 问题等级仓库
private final QuestionLevelRepository questionLevelRepository;
// 问题类型仓库
private final QuestionTypeRepository questionTypeRepository;
// 问题类别仓库
private final QuestionCategoryRepository questionCategoryRepository;
private final QuestionOptionRepository questionOptionRepository;
// 问题选项仓库
// 定义一个final类型的QuestionOptionRepository对象
private final QuestionOptionRepository questionOptionRepository;
// 构造函数传入多个Repository对象
public ExamServiceImpl(QuestionRepository questionRepository, UserRepository userRepository, QuestionLevelRepository questionLevelRepository, QuestionTypeRepository questionTypeRepository, QuestionCategoryRepository questionCategoryRepository, QuestionOptionRepository questionOptionRepository, ExamRepository examRepository, ExamRecordRepository examRecordRepository) {
this.questionRepository = questionRepository;
this.userRepository = userRepository;
this.questionLevelRepository = questionLevelRepository;
// 将传入的Repository对象赋值给对应的成员变量
this.questionRepository = questionRepository;//
this.userRepository = userRepository;//
this.questionLevelRepository = questionLevelRepository;//
this.questionTypeRepository = questionTypeRepository;
this.questionCategoryRepository = questionCategoryRepository;
this.questionOptionRepository = questionOptionRepository;
this.examRepository = examRepository;
this.examRecordRepository = examRecordRepository;
}
@Override
public List<QuestionVo> getQuestionAll() {
List<Question> questionList = questionRepository.findAll();
@ -57,9 +66,9 @@ public class ExamServiceImpl implements ExamService {
}
private List<QuestionVo> getQuestionVos(List<Question> questionList) {
// 需要自定义的question列表
// 需要自定义的question列表
List<QuestionVo> questionVoList = new ArrayList<>();
// 循环完成每个属性的定制
// 循环完成每个属性的定制
for (Question question : questionList) {
QuestionVo questionVo = getQuestionVo(question);
questionVoList.add(questionVo);
@ -69,9 +78,9 @@ public class ExamServiceImpl implements ExamService {
private QuestionVo getQuestionVo(Question question) {
QuestionVo questionVo = new QuestionVo();
// 先复制能复制的属性
// 先复制能复制的属性
BeanUtils.copyProperties(question, questionVo);
// 设置问题的创建者
// 设置问题的创建者
questionVo.setQuestionCreator(
Objects.requireNonNull(
userRepository.findById(
@ -79,7 +88,7 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getUserUsername());
// 设置问题的难度
// 设置问题的难度
questionVo.setQuestionLevel(
Objects.requireNonNull(
questionLevelRepository.findById(
@ -87,7 +96,7 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getQuestionLevelDescription());
// 设置题目的类别,比如单选、多选、判断等
// 设置题目的类别,比如单选、多选、判断等
questionVo.setQuestionType(
Objects.requireNonNull(
questionTypeRepository.findById(
@ -95,7 +104,7 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getQuestionTypeDescription());
// 设置题目分类,比如数学、语文、英语、生活、人文等
// 设置题目分类,比如数学、语文、英语、生活、人文等
questionVo.setQuestionCategory(
Objects.requireNonNull(
questionCategoryRepository.findById(
@ -104,20 +113,20 @@ public class ExamServiceImpl implements ExamService {
).getQuestionCategoryName()
);
// 选项的自定义Vo列表
// 选项的自定义Vo列表
List<QuestionOptionVo> optionVoList = new ArrayList<>();
// 获得所有的选项列表
// 获得所有的选项列表
List<QuestionOption> optionList = questionOptionRepository.findAllById(
Arrays.asList(question.getQuestionOptionIds().split("-"))
);
// 获取所有的答案列表optionList中每个option的isAnswer选项
// 获取所有的答案列表optionList中每个option的isAnswer选项
List<QuestionOption> answerList = questionOptionRepository.findAllById(
Arrays.asList(question.getQuestionAnswerOptionIds().split("-"))
);
// 根据选项和答案的id相同设置optionVo的isAnswer属性
// 根据选项和答案的id相同设置optionVo的isAnswer属性
for (QuestionOption option : optionList) {
QuestionOptionVo optionVo = new QuestionOptionVo();
BeanUtils.copyProperties(option, optionVo);
@ -129,14 +138,14 @@ public class ExamServiceImpl implements ExamService {
optionVoList.add(optionVo);
}
// 设置题目的所有选项
// 设置题目的所有选项
questionVo.setQuestionOptionVoList(optionVoList);
return questionVo;
}
@Override
public QuestionVo updateQuestion(QuestionVo questionVo) {
// 1.把需要的属性都设置好
// 1.把需要的属性都设置好
StringBuilder questionAnswerOptionIds = new StringBuilder();
List<QuestionOption> questionOptionList = new ArrayList<>();
List<QuestionOptionVo> questionOptionVoList = questionVo.getQuestionOptionVoList();
@ -144,89 +153,104 @@ public class ExamServiceImpl implements ExamService {
for (int i = 0; i < questionOptionVoList.size(); i++) {
QuestionOptionVo questionOptionVo = questionOptionVoList.get(i);
QuestionOption questionOption = new QuestionOption();
// 2.把questionOptionVo中的属性值复制到questionOption中。
BeanUtils.copyProperties(questionOptionVo, questionOption);
questionOptionList.add(questionOption);
// 3.如果questionOptionVo中的answer属性为true则把questionOptionVo中的questionOptionId属性值添加到questionAnswerOptionIds中。
if (questionOptionVo.getAnswer()) {
if (i != size - 1) {
// 把更新后的答案的id加上去,记得用-连到一起
// 把更新后的答案的id加上去,记得用-连到一起
questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()).append("-");
} else {
// 最后一个不需要用-连接
// 最后一个不需要用-连接
questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId());
}
}
}
// 1.更新问题
// 1.更新问题。
// 根据问题id从数据库中查找问题
Question question = questionRepository.findById(questionVo.getQuestionId()).orElse(null);
// 断言问题不为空
assert question != null;
// 将questionVo中的属性值复制到question中
BeanUtils.copyProperties(questionVo, question);
// 将问题答案选项id转换为字符串并设置到question中
question.setQuestionAnswerOptionIds(questionAnswerOptionIds.toString());
// 保存更新后的question到数据库中
questionRepository.save(question);
// 2.更新所有的option
// 2.更新所有的option。
// 将questionOptionList中的所有option保存到数据库中
questionOptionRepository.saveAll(questionOptionList);
// 返回更新后的问题,方便前端局部刷新
// 返回更新后的问题,方便前端局部刷新。
// 将更新后的question转换为questionVo并返回
return getQuestionVo(question);
}
}
@Override
public void questionCreate(QuestionCreateVo questionCreateVo) {
// 问题创建
Question question = new Question();
// 把能复制的属性都复制过来
// 把能复制的属性都复制过来
BeanUtils.copyProperties(questionCreateVo, question);
// 设置下questionOptionIds和questionAnswerOptionIds需要自己用Hutool生成下
// 设置下questionOptionIds和questionAnswerOptionIds需要自己用Hutool生成下
List<QuestionOption> questionOptionList = new ArrayList<>();
List<QuestionOptionCreateVo> questionOptionCreateVoList = questionCreateVo.getQuestionOptionCreateVoList();
for (QuestionOptionCreateVo questionOptionCreateVo : questionOptionCreateVoList) {
QuestionOption questionOption = new QuestionOption();
// 设置选项的的内容
// 设置选项的的内容
questionOption.setQuestionOptionContent(questionOptionCreateVo.getQuestionOptionContent());
// 设置选项的id
// 设置选项的id
questionOption.setQuestionOptionId(IdUtil.simpleUUID());
questionOptionList.add(questionOption);
}
// 把选项都存起来然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds
// 把选项都存起来然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds
questionOptionRepository.saveAll(questionOptionList);
String questionOptionIds = "";
String questionAnswerOptionIds = "";
// 经过上面的saveAll方法所有的option的主键id都已经持久化了
// 经过上面的saveAll方法所有的option的主键id都已经持久化了
for (int i = 0; i < questionOptionCreateVoList.size(); i++) {
// 获取指定选项
// 获取指定选项
QuestionOptionCreateVo questionOptionCreateVo = questionOptionCreateVoList.get(i);
// 获取保存后的指定对象
// 获取保存后的指定对象
QuestionOption questionOption = questionOptionList.get(i);
questionOptionIds += questionOption.getQuestionOptionId() + "-";
if (questionOptionCreateVo.getAnswer()) {
// 如果是答案的话
// 如果是答案的话
questionAnswerOptionIds += questionOption.getQuestionOptionId() + "-";
}
}
// 把字符串最后面的"-"给去掉
// 把字符串最后面的"-"给去掉
questionAnswerOptionIds = replaceLastSeparator(questionAnswerOptionIds);
questionOptionIds = replaceLastSeparator(questionOptionIds);
// 设置选项id组成的字符串
// 设置选项id组成的字符串
question.setQuestionOptionIds(questionOptionIds);
// 设置答案选项id组成的字符串
// 设置答案选项id组成的字符串
question.setQuestionAnswerOptionIds(questionAnswerOptionIds);
// 自己生成问题的id
// 自己生成问题的id
question.setQuestionId(IdUtil.simpleUUID());
// 先把创建时间和更新时间每次都取当前时间吧
// 先把创建时间和更新时间每次都取当前时间吧
question.setCreateTime(new Date());
question.setUpdateTime(new Date());
// 保存问题到数据库
// 保存问题到数据库
questionRepository.save(question);
}
@Override
// 重写父类方法,获取问题选择列表
public QuestionSelectionVo getSelections() {
// 创建问题选择对象
QuestionSelectionVo questionSelectionVo = new QuestionSelectionVo();
// 设置问题类别列表
questionSelectionVo.setQuestionCategoryList(questionCategoryRepository.findAll());
// 设置问题等级列表
questionSelectionVo.setQuestionLevelList(questionLevelRepository.findAll());
// 设置问题类型列表
questionSelectionVo.setQuestionTypeList(questionTypeRepository.findAll());
// 返回问题选择对象
return questionSelectionVo;
}
@ -245,10 +269,15 @@ public class ExamServiceImpl implements ExamService {
@Override
public QuestionDetailVo getQuestionDetail(String id) {
// 根据id查询问题
Question question = questionRepository.findById(id).orElse(null);
// 创建问题详情对象
QuestionDetailVo questionDetailVo = new QuestionDetailVo();
// 设置问题id
questionDetailVo.setId(id);
// 设置问题名称
questionDetailVo.setName(question.getQuestionName());
// 设置问题描述
questionDetailVo.setDescription(question.getQuestionDescription());
// 问题类型,单选题/多选题/判断题
questionDetailVo.setType(
@ -258,10 +287,10 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getQuestionTypeDescription()
);
// 获取当前问题的选项
// 获取当前问题的选项
String optionIdsStr = trimMiddleLine(question.getQuestionOptionIds());
String[] optionIds = optionIdsStr.split("-");
// 获取选项列表
// 获取选项列表
List<QuestionOption> optionList = questionOptionRepository.findAllById(Arrays.asList(optionIds));
questionDetailVo.setOptions(optionList);
return questionDetailVo;
@ -274,14 +303,14 @@ public class ExamServiceImpl implements ExamService {
}
private List<ExamVo> getExamVos(List<Exam> examList) {
// 需要自定义的exam列表
// 需要自定义的exam列表
List<ExamVo> examVoList = new ArrayList<>();
// 循环完成每个属性的定制
// 循环完成每个属性的定制
for (Exam exam : examList) {
ExamVo examVo = new ExamVo();
// 先尽量复制能复制的所有属性
// 先尽量复制能复制的所有属性
BeanUtils.copyProperties(exam, examVo);
// 设置问题的创建者
// 设置问题的创建者
examVo.setExamCreator(
Objects.requireNonNull(
userRepository.findById(
@ -290,7 +319,7 @@ public class ExamServiceImpl implements ExamService {
).getUserUsername()
);
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
List<ExamQuestionSelectVo> radioQuestionVoList = new ArrayList<>();
List<Question> radioQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsRadio().split("-"))
@ -298,12 +327,12 @@ public class ExamServiceImpl implements ExamService {
for (Question question : radioQuestionList) {
ExamQuestionSelectVo radioQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, radioQuestionVo);
radioQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
radioQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
radioQuestionVoList.add(radioQuestionVo);
}
examVo.setExamQuestionSelectVoRadioList(radioQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
List<ExamQuestionSelectVo> checkQuestionVoList = new ArrayList<>();
List<Question> checkQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsCheck().split("-"))
@ -311,12 +340,12 @@ public class ExamServiceImpl implements ExamService {
for (Question question : checkQuestionList) {
ExamQuestionSelectVo checkQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, checkQuestionVo);
checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
checkQuestionVoList.add(checkQuestionVo);
}
examVo.setExamQuestionSelectVoCheckList(checkQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
List<ExamQuestionSelectVo> judgeQuestionVoList = new ArrayList<>();
List<Question> judgeQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsJudge().split("-"))
@ -324,7 +353,7 @@ public class ExamServiceImpl implements ExamService {
for (Question question : judgeQuestionList) {
ExamQuestionSelectVo judgeQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, judgeQuestionVo);
judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
judgeQuestionVoList.add(judgeQuestionVo);
}
examVo.setExamQuestionSelectVoJudgeList(judgeQuestionVoList);
@ -340,7 +369,7 @@ public class ExamServiceImpl implements ExamService {
@Override
public ExamQuestionTypeVo getExamQuestionType() {
ExamQuestionTypeVo examQuestionTypeVo = new ExamQuestionTypeVo();
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
List<ExamQuestionSelectVo> radioQuestionVoList = new ArrayList<>();
List<Question> radioQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.RADIO.getId());
for (Question question : radioQuestionList) {
@ -350,7 +379,7 @@ public class ExamServiceImpl implements ExamService {
}
examQuestionTypeVo.setExamQuestionSelectVoRadioList(radioQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
List<ExamQuestionSelectVo> checkQuestionVoList = new ArrayList<>();
List<Question> checkQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.CHECK.getId());
for (Question question : checkQuestionList) {
@ -360,7 +389,7 @@ public class ExamServiceImpl implements ExamService {
}
examQuestionTypeVo.setExamQuestionSelectVoCheckList(checkQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
List<ExamQuestionSelectVo> judgeQuestionVoList = new ArrayList<>();
List<Question> judgeQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.JUDGE.getId());
for (Question question : judgeQuestionList) {
@ -374,7 +403,7 @@ public class ExamServiceImpl implements ExamService {
@Override
public Exam create(ExamCreateVo examCreateVo, String userId) {
// 在线考试系统创建
// 在线考试系统创建
Exam exam = new Exam();
BeanUtils.copyProperties(examCreateVo, exam);
exam.setExamId(IdUtil.simpleUUID());
@ -382,35 +411,53 @@ public class ExamServiceImpl implements ExamService {
exam.setCreateTime(new Date());
exam.setUpdateTime(new Date());
// Todo:这两个日志后面是要在前端传入的,这里暂时定为当前日期
exam.setExamStartDate(new Date());
exam.setExamEndDate(new Date());
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
List<ExamQuestionSelectVo> radios = examCreateVo.getRadios();
List<ExamQuestionSelectVo> checks = examCreateVo.getChecks();
List<ExamQuestionSelectVo> judges = examCreateVo.getJudges();
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
for (ExamQuestionSelectVo radio : radios) {
if (radio.getChecked()) {
radioIdsStr += radio.getQuestionId() + "-";
radioCnt++;
}
}
radioIdsStr = replaceLastSeparator(radioIdsStr);
for (ExamQuestionSelectVo check : checks) {
if (check.getChecked()) {
checkIdsStr += check.getQuestionId() + "-";
checkCnt++;
}
}
checkIdsStr = replaceLastSeparator(checkIdsStr);
for (ExamQuestionSelectVo judge : judges) {
if (judge.getChecked()) {
judgeIdsStr += judge.getQuestionId() + "-";
judgeCnt++;
}
}
// 设置考试开始时间和结束时间
exam.setExamStartDate(new Date());
exam.setExamEndDate(new Date());
// 初始化选择题、判断题和单选题的id字符串
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
// 获取选择题、判断题和单选题的列表
List<ExamQuestionSelectVo> radios = examCreateVo.getRadios();
List<ExamQuestionSelectVo> checks = examCreateVo.getChecks();
List<ExamQuestionSelectVo> judges = examCreateVo.getJudges();
// 初始化选择题、判断题和单选题的计数器
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
// 遍历单选题列表
for (ExamQuestionSelectVo radio : radios) {
// 如果单选题被选中
if (radio.getChecked()) {
// 将单选题的id添加到字符串中
radioIdsStr += radio.getQuestionId() + "-";
// 单选题计数器加1
radioCnt++;
}
}
// 去掉最后一个分隔符
radioIdsStr = replaceLastSeparator(radioIdsStr);
// 遍历判断题列表
for (ExamQuestionSelectVo check : checks) {
// 如果判断题被选中
if (check.getChecked()) {
// 将判断题的id添加到字符串中
checkIdsStr += check.getQuestionId() + "-";
// 判断题计数器加1
checkCnt++;
}
}
// 去掉最后一个分隔符
checkIdsStr = replaceLastSeparator(checkIdsStr);
// 遍历选择题列表
for (ExamQuestionSelectVo judge : judges) {
// 如果选择题被选中
if (judge.getChecked()) {
// 将选择题的id添加到字符串中
judgeIdsStr += judge.getQuestionId() + "-";
// 选择题计数器加1
judgeCnt++;
}
}
judgeIdsStr = replaceLastSeparator(judgeIdsStr);
exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr);
// 设置各个题目的id
@ -427,49 +474,64 @@ public class ExamServiceImpl implements ExamService {
@Override
public Exam update(ExamVo examVo, String userId) {
// 创建一个新的Exam对象
Exam exam = new Exam();
// 将examVo中的属性值复制到exam对象中
BeanUtils.copyProperties(examVo, exam);
exam.setExamCreatorId(userId); // 考试的更新人为最新的创建人
exam.setUpdateTime(new Date()); // 考试的更新日期要记录下
// 设置考试更新人为最新的创建人
exam.setExamCreatorId(userId);
// 记录考试更新日期
exam.setUpdateTime(new Date());
// 初始化选择题、判断题、单选题的id字符串
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
String judgeIdsStr = "";//djfndiosbchjdshjiowceduikmn
// 获取选择题、判断题、单选题的列表
List<ExamQuestionSelectVo> radios = examVo.getExamQuestionSelectVoRadioList();
List<ExamQuestionSelectVo> checks = examVo.getExamQuestionSelectVoCheckList();
List<ExamQuestionSelectVo> judges = examVo.getExamQuestionSelectVoJudgeList();
// 初始化选择题、判断题、单选题的数量
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
// 遍历选择题列表将选中的题目id添加到字符串中并统计选择题数量
for (ExamQuestionSelectVo radio : radios) {
if (radio.getChecked()) {
radioIdsStr += radio.getQuestionId() + "-";
radioCnt++;
}
}
// 去除最后一个分隔符
radioIdsStr = replaceLastSeparator(radioIdsStr);
// 遍历判断题列表将选中的题目id添加到字符串中并统计判断题数量
for (ExamQuestionSelectVo check : checks) {
if (check.getChecked()) {
checkIdsStr += check.getQuestionId() + "-";
checkCnt++;
}
}
// 去除最后一个分隔符
checkIdsStr = replaceLastSeparator(checkIdsStr);
// 遍历单选题列表将选中的题目id添加到字符串中并统计单选题数量
for (ExamQuestionSelectVo judge : judges) {
if (judge.getChecked()) {
judgeIdsStr += judge.getQuestionId() + "-";
judgeCnt++;
}
}
// 去除最后一个分隔符。
judgeIdsStr = replaceLastSeparator(judgeIdsStr);
// 设置各个题目的id。
exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr);
// 设置各个题目的id
exam.setExamQuestionIdsRadio(radioIdsStr);
exam.setExamQuestionIdsCheck(checkIdsStr);
exam.setExamQuestionIdsJudge(judgeIdsStr);
// 计算总分数
// 计算总分数
int examScore = radioCnt * exam.getExamScoreRadio() + checkCnt * exam.getExamScoreCheck() + judgeCnt * exam.getExamScoreJudge();
exam.setExamScore(examScore);
// 保存exam对象。
examRepository.save(exam);
// 返回exam对象。
return exam;
}
@ -499,53 +561,53 @@ public class ExamServiceImpl implements ExamService {
@Override
public ExamRecord judge(String userId, String examId, HashMap<String, List<String>> answersMap) {
// 开始考试判分啦~~~
// 1.首先获取考试对象和选项数组
// 开始考试判分啦~~~
// 1.首先获取考试对象和选项数组
ExamDetailVo examDetailVo = getExamDetail(examId);
Exam exam = examDetailVo.getExam();
// 2.然后获取该考试下所有的题目信息
// 2.然后获取该考试下所有的题目信息
List<String> questionIds = new ArrayList<>();
// 2.1 题目id的数组
// 2.1 题目id的数组
List<String> radioIdList = Arrays.asList(examDetailVo.getRadioIds());
List<String> checkIdList = Arrays.asList(examDetailVo.getCheckIds());
List<String> judgeIdList = Arrays.asList(examDetailVo.getJudgeIds());
questionIds.addAll(radioIdList);
questionIds.addAll(checkIdList);
questionIds.addAll(judgeIdList);
// 2.2 每种题目的分数
// 2.2 每种题目的分数
int radioScore = exam.getExamScoreRadio();
int checkScore = exam.getExamScoreCheck();
int judgeScore = exam.getExamScoreJudge();
// 2.3 根据问题id的数组拿到所有的问题对象供下面步骤用
// 2.3 根据问题id的数组拿到所有的问题对象供下面步骤用
List<Question> questionList = questionRepository.findAllById(questionIds);
Map<String, Question> questionMap = new HashMap<>();
for (Question question : questionList) {
questionMap.put(question.getQuestionId(), question);
}
// 3.根据正确答案和用户作答信息进行判分
// 3.根据正确答案和用户作答信息进行判分
Set<String> questionIdsAnswer = answersMap.keySet();
// 存储当前考试每个题目的得分情况
// 存储当前考试每个题目的得分情况
Map<String, Integer> judgeMap = new HashMap<>();
// 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情
// 例子题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3
// 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情
// 例子题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3
StringBuilder answerOptionIdsSb = new StringBuilder();
// 用户此次考试的总分
// 用户此次考试的总分
int totalScore = 0;
for (String questionId : questionIdsAnswer) {
// 获取用户作答地这个题的答案信息
// 获取用户作答地这个题的答案信息
Question question = questionMap.get(questionId);
// 获取答案选项
// 获取答案选项
String questionAnswerOptionIds = replaceLastSeparator(question.getQuestionAnswerOptionIds());
List<String> questionAnswerOptionIdList = Arrays.asList(questionAnswerOptionIds.split("-"));
Collections.sort(questionAnswerOptionIdList);
String answerStr = listConcat(questionAnswerOptionIdList);
// 获取用户作答
// 获取用户作答
List<String> questionUserOptionIdList = answersMap.get(questionId);
Collections.sort(questionUserOptionIdList);
String userStr = listConcat(questionUserOptionIdList);
// 判断questionAnswerOptionIds和answersMap里面的答案是否相等
// 判断questionAnswerOptionIds和answersMap里面的答案是否相等
if (answerStr.equals(userStr)) {
// 说明题目作答正确,下面根据题型给分
// 说明题目作答正确,下面根据题型给分
int score = 0;
if (radioIdList.contains(questionId)) {
score = radioScore;
@ -556,22 +618,22 @@ public class ExamServiceImpl implements ExamService {
if (judgeIdList.contains(questionId)) {
score = judgeScore;
}
// 累计本次考试得分
// 累计本次考试得分
totalScore += score;
// True代表题目答对
// True代表题目答对
answerOptionIdsSb.append(questionId + "@True_" + userStr + "$");
judgeMap.put(questionId, score);
} else {
// 说明题目作答错误,直接判零分,False代表题目答错
// 说明题目作答错误,直接判零分,False代表题目答错
answerOptionIdsSb.append(questionId + "@False_" + userStr + "$");
judgeMap.put(questionId, 0);
}
}
// 4.计算得分记录本次考试结果存到ExamRecord中
// 4.计算得分记录本次考试结果存到ExamRecord中
ExamRecord examRecord = new ExamRecord();
examRecord.setExamRecordId(IdUtil.simpleUUID());
examRecord.setExamId(examId);
// 注意去掉最后可能有的&_-
// 注意去掉最后可能有的&_-
examRecord.setAnswerOptionIds(replaceLastSeparator(answerOptionIdsSb.toString()));
examRecord.setExamJoinerId(userId);
examRecord.setExamJoinDate(new Date());
@ -582,58 +644,60 @@ public class ExamServiceImpl implements ExamService {
@Override
public List<ExamRecordVo> getExamRecordList(String userId) {
// 获取指定用户下的考试记录列表
// 获取指定用户下的考试记录列表
List<ExamRecord> examRecordList = examRecordRepository.findByExamJoinerIdOrderByExamJoinDateDesc(userId);
List<ExamRecordVo> examRecordVoList = new ArrayList<>();
for (ExamRecord examRecord : examRecordList) {
ExamRecordVo examRecordVo = new ExamRecordVo();
// 根据考试记录中的考试ID获取考试信息
Exam exam = examRepository.findById(examRecord.getExamId()).orElse(null);
examRecordVo.setExam(exam);
// 根据用户ID获取用户信息
User user = userRepository.findById(userId).orElse(null);
examRecordVo.setUser(user);
// 设置考试记录信息
examRecordVo.setExamRecord(examRecord);
examRecordVoList.add(examRecordVo);
}
return examRecordVoList;
}
@Override
public RecordDetailVo getRecordDetail(String recordId) {
// 获取考试详情的封装对象
// 获取考试详情的封装对象
ExamRecord record = examRecordRepository.findById(recordId).orElse(null);
RecordDetailVo recordDetailVo = new RecordDetailVo();
recordDetailVo.setExamRecord(record);
// 用户的答案,需要解析
// 用户的答案,需要解析
HashMap<String, List<String>> answersMap = new HashMap<>();
HashMap<String, String> resultsMap = new HashMap<>();
assert record != null;
String answersStr = record.getAnswerOptionIds();
// $分隔题目,因为$在正则中有特殊用途(行尾),所以需要括起来
// $分隔题目,因为$在正则中有特殊用途(行尾),所以需要括起来
String[] questionArr = answersStr.split("[$]");
for (String questionStr : questionArr) {
System.out.println(questionStr);
// 区分开题目标题和选项
// 区分开题目标题和选项
String[] questionTitleResultAndOption = questionStr.split("_");
String[] questionTitleAndResult = questionTitleResultAndOption[0].split("@");
String[] questionOptions = questionTitleResultAndOption[1].split("-");
// 题目:答案选项
// 题目:答案选项
answersMap.put(questionTitleAndResult[0], Arrays.asList(questionOptions));
// 题目True / False
// 题目True / False
resultsMap.put(questionTitleAndResult[0], questionTitleAndResult[1]);
}
recordDetailVo.setAnswersMap(answersMap);
recordDetailVo.setResultsMap(resultsMap);
// 下面再计算正确答案的map
// 下面再计算正确答案的map
ExamDetailVo examDetailVo = getExamDetail(record.getExamId());
List<String> questionIdList = new ArrayList<>();
questionIdList.addAll(Arrays.asList(examDetailVo.getRadioIds()));
questionIdList.addAll(Arrays.asList(examDetailVo.getCheckIds()));
questionIdList.addAll(Arrays.asList(examDetailVo.getJudgeIds()));
// 获取所有的问题对象
// 获取所有的问题对象
List<Question> questionList = questionRepository.findAllById(questionIdList);
HashMap<String, List<String>> answersRightMap = new HashMap<>();
for (Question question : questionList) {
// 记得去掉最后可能出现的特殊字符
// 记得去掉最后可能出现的特殊字符
String questionAnswerOptionIdsStr = replaceLastSeparator(question.getQuestionAnswerOptionIds());
String[] questionAnswerOptionIds = questionAnswerOptionIdsStr.split("-");
answersRightMap.put(question.getQuestionId(), Arrays.asList(questionAnswerOptionIds));
@ -641,7 +705,6 @@ public class ExamServiceImpl implements ExamService {
recordDetailVo.setAnswersRightMap(answersRightMap);
return recordDetailVo;
}
/**
* -
*
@ -650,7 +713,7 @@ public class ExamServiceImpl implements ExamService {
*/
private String replaceLastSeparator(String str) {
String lastChar = str.substring(str.length() - 1);
// 题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔
// 题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔
if ("-".equals(lastChar) || "_".equals(lastChar) || "$".equals(lastChar)) {
str = StrUtil.sub(str, 0, str.length() - 1);
}
@ -672,3 +735,4 @@ public class ExamServiceImpl implements ExamService {
return replaceLastSeparator(sb.toString());
}
}
//注释完毕

@ -109,23 +109,35 @@ public class UserServiceImpl implements UserService {
@Override
public UserVo getUserInfo(String userId) {
// 根据用户ID从数据库中查找用户信息
User user = userRepository.findById(userId).orElse(null);
// 创建一个UserVo对象
UserVo userVo = new UserVo();
// 断言user对象不为空
assert user != null;
// 将user对象的属性复制到userVo对象中
BeanUtils.copyProperties(user, userVo);
// 返回userVo对象
return userVo;
}
@Override
public UserInfoVo getInfo(String userId) {
// 根据用户ID查询用户信息
User user = userRepository.findById(userId).orElse(null);
// 断言用户信息不为空
assert user != null;
// 创建用户信息视图对象
UserInfoVo userInfoVo = new UserInfoVo();
// 1.尽可能的拷贝属性
BeanUtils.copyProperties(user, userInfoVo);
// 获取用户角色ID
Integer roleId = user.getUserRoleId();
// 根据角色ID查询角色信息
Role role = roleRepository.findById(roleId).orElse(null);
// 断言角色信息不为空
assert role != null;
// 获取角色名称
String roleName = role.getRoleName();
// 2.设置角色名称
@ -149,17 +161,27 @@ public class UserServiceImpl implements UserService {
BeanUtils.copyProperties(page, pageVo);
// 4.2 向Page中添加action
List<ActionVo> actionVoList = new ArrayList<>();
String actionIdsStr = page.getActionIds();
String[] actionIdArr = actionIdsStr.split("-");
for (String actionIdStr : actionIdArr) {
Integer actionId = Integer.parseInt(actionIdStr);
Action action = actionRepository.findById(actionId).orElse(null);
ActionVo actionVo = new ActionVo();
assert action != null;
BeanUtils.copyProperties(action, actionVo);
actionVoList.add(actionVo);
}
// 创建一个ActionVo类型的列表
List<ActionVo> actionVoList = new ArrayList<>();
// 获取页面中的actionIds字符串
String actionIdsStr = page.getActionIds();
// 将actionIds字符串按照"-"进行分割,得到一个字符串数组
String[] actionIdArr = actionIdsStr.split("-");
// 遍历字符串数组
for (String actionIdStr : actionIdArr) {
// 将字符串转换为整数
Integer actionId = Integer.parseInt(actionIdStr);
// 根据actionId从数据库中查找对应的Action对象
Action action = actionRepository.findById(actionId).orElse(null);
// 创建一个ActionVo对象
ActionVo actionVo = new ActionVo();
// 断言action对象不为空
assert action != null;
// 将action对象的属性值复制到actionVo对象中
BeanUtils.copyProperties(action, actionVo);
// 将actionVo对象添加到列表中
actionVoList.add(actionVo);
}
// 设置actionVoList到pageVo中然后把pageVo加到pageVoList中
pageVo.setActionVoList(actionVoList);
// 设置pageVoList下面再设置到RoleVo中

@ -55,17 +55,24 @@ public class FileTransUtil {
* @return
*/
public static String uploadFiles(MultipartFile[] uploadfiles, String dir) {
// 打印调试信息
log.debug("Multiple file upload!");
// 将上传的文件名拼接成一个字符串
String uploadedFileName = Arrays.stream(uploadfiles).map(x -> x.getOriginalFilename()).filter(x -> !StringUtils.isEmpty(x)).collect(Collectors.joining(" , "));
// 如果文件名为空,则返回错误信息
if (StringUtils.isEmpty(uploadedFileName)) {
return "文件名不能为空";
}
try {
// 调用工具类保存上传的文件
FileTransUtil.saveUploadedFiles(Arrays.asList(uploadfiles), dir);
} catch (IOException e) {
// 如果保存文件时发生异常,则返回错误信息
return "后台服务异常";
}
// 打印上传成功的日志信息
log.info("file upload successfully! " + uploadedFileName);
// 返回上传成功的提示信息
return "文件上传成功";
}
@ -75,26 +82,37 @@ public class FileTransUtil {
* @param files
* @throws IOException
*/
// 保存上传的文件
public static void saveUploadedFiles(List<MultipartFile> files, String dir) throws IOException {
// 遍历文件列表
for (MultipartFile file : files) {
// 如果文件为空,则跳过
if (file.isEmpty()) {
continue;
}
// 如果文件夹不存在,则创建
if (!FileUtil.exist(dir)) {
// 文件夹不存在就创建
FileUtil.mkdir(dir);
}
// 获取文件的字节数组
byte[] bytes = file.getBytes();
// 获取文件的原始文件名,并替换反斜杠为正斜杠
String fileName = file.getOriginalFilename().replace("\\", "/");
// 如果文件名中包含斜杠,则说明是上传文件夹
if (fileName.lastIndexOf('/')>0){
// 上传文件夹的时候会有这种情况
// 获取文件夹路径
String fileDir = dir + "/" + fileName.substring(0, fileName.lastIndexOf('/'));
// 如果文件夹不存在,则创建
if (!FileUtil.exist(fileDir)) {
// 文件夹不存在就创建,创建文件夹的时候会用到
FileUtil.mkdir(fileDir);
}
}
// 获取文件的路径
Path path = Paths.get(dir + "/" + fileName);
// 将文件写入路径
Files.write(path, bytes);
}
}
@ -107,14 +125,23 @@ public class FileTransUtil {
* @throws IOException
*/
public static ResponseEntity<InputStreamResource> downloadFile(String filePath) throws IOException {
// 记录下载文件的信息
log.info("downloading file : " + filePath);
// 创建文件系统资源
FileSystemResource file = new FileSystemResource(filePath);
// 创建响应头
HttpHeaders headers = new HttpHeaders();
// 设置缓存控制
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
// 设置文件下载时的文件名
headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", new String(file.getFilename().getBytes("gbk"), "iso-8859-1")));
// 设置缓存控制
headers.add("Pragma", "no-cache");
// 设置缓存控制
headers.add("Expires", "0");
// 打印文件名
System.out.println(file.getFilename());
// 返回响应实体
return ResponseEntity
.ok()
.headers(headers)

@ -24,12 +24,18 @@ public class FileUtils {
* @param url
* @param suffix
*/
public static File createFileByUrl(String url, String suffix) {
// 根据URL创建文件
public static File createFileByUrl(String url, String suffix) {
// 从URL获取图片字节数组
byte[] byteFile = getImageFromNetByUrl(url);
// 如果字节数组不为空
if (byteFile != null) {
// 从字节数组获取文件
File file = getFileFromBytes(byteFile, suffix);
// 返回文件
return file;
} else {
// 否则返回null
return null;
}
}
@ -42,9 +48,13 @@ public class FileUtils {
*/
private static byte[] getImageFromNetByUrl(String strUrl) {
try {
// 创建URL对象
URL url = new URL(strUrl);
// 打开连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求方法
conn.setRequestMethod("GET");
// 设置连接超时时间
conn.setConnectTimeout(5 * 1000);
// 通过输入流获取图片数据
InputStream inStream = conn.getInputStream();
@ -65,41 +75,61 @@ public class FileUtils {
* @throws Exception
*/
private static byte[] readInputStream(InputStream inStream) throws Exception {
// 创建一个ByteArrayOutputStream对象用于存储读取的字节数据
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
// 创建一个字节数组,用于存储每次读取的字节数据
byte[] buffer = new byte[1024];
// 定义一个变量,用于存储每次读取的字节数
int len = 0;
// 循环读取输入流中的数据,直到读取完毕
while ((len = inStream.read(buffer)) != -1) {
// 将读取的字节数据写入ByteArrayOutputStream对象中
outStream.write(buffer, 0, len);
}
// 关闭输入流
inStream.close();
// 将ByteArrayOutputStream对象中的字节数据转换为字节数组并返回
return outStream.toByteArray();
}
// 创建临时文件
private static File getFileFromBytes(byte[] b, String suffix) {
// 根据字节数组和后缀名创建临时文件
private static File getFileFromBytes(byte[] b, String suffix) {
// 定义输出流
BufferedOutputStream stream = null;
// 定义文件
File file = null;
try {
// 创建临时文件
file = File.createTempFile("pattern", "." + suffix);
// 输出临时文件位置
System.out.println("临时文件位置:" + file.getCanonicalPath());
// 创建文件输出流
FileOutputStream fstream = new FileOutputStream(file);
// 创建缓冲输出流
stream = new BufferedOutputStream(fstream);
// 将字节数组写入文件
stream.write(b);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
} finally {
// 关闭输出流
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// 打印异常信息
e.printStackTrace();
}
}
}
// 返回文件
return file;
}
public static MultipartFile createImg(String url) {
// 创建一个MultipartFile对象根据给定的url
public static MultipartFile createImg(String url) {
try {
// File转换成MutipartFile
File file = FileUtils.createFileByUrl(url, "jpg");
@ -131,12 +161,15 @@ public class FileUtils {
}
public static boolean base64ToFile(String filePath, String base64Data) throws Exception {
// 定义变量dataPrix和data用于存储base64Data中的数据
String dataPrix = "";
String data = "";
// 判断base64Data是否为空
if (base64Data == null || "".equals(base64Data)) {
return false;
} else {
// 将base64Data按照"base64,"进行分割获取dataPrix和data
String[] d = base64Data.split("base64,");
if (d != null && d.length == 2) {
dataPrix = d[0];

@ -26,17 +26,21 @@ public class JwtUtils {
private static final String APP_SECRET = "liangshanguang";
public static String genJsonWebToken(User user) {
// 判断用户信息是否为空
if (user == null || user.getUserId() == null || user.getUserUsername() == null || user.getUserAvatar() == null) {
return null;
}
// 使用Jwts.builder()创建一个JWT对象
return Jwts.builder().setSubject(SUBJECT)
// 下面3行设置token中间字段携带用户的信息
.claim("id", user.getUserId())
.claim("username", user.getUserUsername())
.claim("avatar", user.getUserAvatar())
// 设置签发时间
.setIssuedAt(new Date())
// 设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
// 使用HS256算法进行签名
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
// 生成的结果字符串太长,这里压缩下
.compact();
@ -50,6 +54,7 @@ public class JwtUtils {
*/
public static Claims checkJWT(String token) {
try {
// 使用Jwts解析token并设置签名密钥为APP_SECRET
return Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody();
} catch (Exception e) {
// 篡改token会导致校验失败走到异常分支这里返回null

@ -10,19 +10,23 @@ import lsgwr.exam.vo.ResultVO;
public class ResultVOUtil {
// 返回一个包含code、msg和object的ResultVO对象
public static ResultVO success(Integer code, String msg, Object object) {
return new ResultVO(code, msg, object);
}
// 返回一个包含object的ResultVO对象默认code为0msg为"成功"
public static ResultVO success(Object object) {
return new ResultVO(0, "成功", object);
}
// 返回一个默认的ResultVO对象默认code为0msg为"成功"object为null
public static ResultVO success() {
return new ResultVO(0, "成功", null);
}
// 返回一个包含code和msg的ResultVO对象默认object为null
public static ResultVO error(Integer code, String msg) {
return new ResultVO(code, msg, null);
}

@ -11,12 +11,15 @@ import lombok.Data;
@Data
public class ActionVo {
// 使用@JsonProperty注解将actionName属性映射到JSON中的action字段
@JsonProperty("action")
private String actionName;
// 使用@JsonProperty注解将actionDescription属性映射到JSON中的describe字段
@JsonProperty("describe")
private String actionDescription;
// 使用@JsonProperty注解将defaultCheck属性映射到JSON中的defaultCheck字段
@JsonProperty("defaultCheck")
private Boolean defaultCheck;
}

@ -10,34 +10,27 @@ import lombok.Data;
*/
@Data
public class ExamCardVo {
/**
* ID
*/
//考试ID
@JsonProperty("id")
private String examId;
/**
*
*/
//考试名称
@JsonProperty("title")
private String examName;
/**
*
*/
//考试头像
@JsonProperty("avatar")
private String examAvatar;
/**
*
*/
//考试描述
@JsonProperty("content")
private String examDescription;
/**
*
*/
//考试分数
@JsonProperty("score")
private Integer examScore;
/**
*
*/
//考试限制的时间,单位为分钟
@JsonProperty("elapse")
private Integer examTimeLimit;
}

@ -23,43 +23,36 @@ public class ExamCreateVo {
@JsonProperty("desc")
private String examDescription;
/**
*
*/
//试时长,单位分钟
@JsonProperty("elapse")
private Integer examTimeLimit;
/**
*
*/
//单选题
private List<ExamQuestionSelectVo> radios;
/**
*
*/
//多选题
private List<ExamQuestionSelectVo> checks;
/**
*
*/
//判断题
private List<ExamQuestionSelectVo> judges;
/**
*
*/
//单选题的分数
@JsonProperty("radioScore")
private Integer examScoreRadio;
/**
*
*/
//多选题的分数
@JsonProperty("checkScore")
private Integer examScoreCheck;
/**
*
*/
//判断题每题的分数
@JsonProperty("judgeScore")
private Integer examScoreJudge;
}

@ -11,24 +11,20 @@ import lombok.Data;
@Data
public class ExamDetailVo {
/**
*
*/
//试的基本信息对象
private Exam exam;
/**
* id
*/
//单选题的id数组
private String[] radioIds;
/**
* id
*/
//多选题的id数组
private String[] checkIds;
/**
* id
*/
//判断题的id数组
private String[] judgeIds;
}

@ -13,29 +13,23 @@ import java.util.List;
@Data
public class ExamPageVo {
/**
*
*/
//分页时每个分页的大小
private Integer pageSize;
/**
* 1
*/
//当前是在第几页注意要比前端传过来地小1
private Integer pageNo;
/**
*
*/
//一共有多少条符合条件的记录
private Long totalCount;
/**
*
*/
//一共有多少页
private Integer totalPage;
/**
*
*/
//当前页的详细数据
@JsonProperty("data")
private List<ExamVo> examVoList;
}

@ -11,9 +11,11 @@ import lombok.Data;
@Data
public class ExamQuestionSelectVo {
// 问题ID
@JsonProperty("id")
private String questionId;
// 问题名称
@JsonProperty("name")
private String questionName;
@ -23,4 +25,4 @@ public class ExamQuestionSelectVo {
*/
@JsonProperty("checked")
private Boolean checked = false;
}
}

@ -13,12 +13,15 @@ import java.util.List;
@Data
public class ExamQuestionTypeVo {
// 单选题列表
@JsonProperty("radios")
private List<ExamQuestionSelectVo> examQuestionSelectVoRadioList;
// 多选题列表
@JsonProperty("checks")
private List<ExamQuestionSelectVo> examQuestionSelectVoCheckList;
// 判断题列表
@JsonProperty("judges")
private List<ExamQuestionSelectVo> examQuestionSelectVoJudgeList;
}

@ -13,18 +13,14 @@ import lombok.Data;
@Data
public class ExamRecordVo {
/**
*
*/
//当前考试记录对应的考试
private Exam exam;
/**
*
*/
//当前考试对应的内容
private ExamRecord examRecord;
/**
*
*/
//参加考试的用户信息
private User user;
}

@ -17,33 +17,44 @@ import java.util.List;
@Data
public class ExamVo {
// 考试id
@JsonProperty("id")
private String examId;
// 考试名称
@JsonProperty("name")
private String examName;
// 考试头像
@JsonProperty("avatar")
private String examAvatar;
// 考试描述
@JsonProperty("desc")
private String examDescription;
// 单选题列表
@JsonProperty("radios")
private List<ExamQuestionSelectVo> examQuestionSelectVoRadioList;
// 多选题列表
@JsonProperty("checks")
private List<ExamQuestionSelectVo> examQuestionSelectVoCheckList;
// 判断题列表
@JsonProperty("judges")
private List<ExamQuestionSelectVo> examQuestionSelectVoJudgeList;
// 考试总分数
@JsonProperty("score")
private Integer examScore;
// 单选题分数
@JsonProperty("radioScore")
private Integer examScoreRadio;
// 多选题分数
@JsonProperty("checkScore")
private Integer examScoreCheck;
// 判断题分数
@JsonProperty("judgeScore")
private Integer examScoreJudge;
@ -86,4 +97,4 @@ public class ExamVo {
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}
}

@ -19,43 +19,36 @@ public class JsonData implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 0 1-1
*/
//状态码 0 表示成功1表示处理中-1表示失败
private Integer code;
/**
*
*/
//数据
private Object data;
/**
*
*/
//描述
private String msg;
/**
*
*/
//成功,传入数据
public static JsonData buildSuccess() {
return new JsonData(0, null, null);
}
/**
*
*/
//成功,传入数据
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null);
}
/**
*
*/
//失败,传入描述信息
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg);
}
/**
* ,
*/
//失败,传入描述信息,状态码
public static JsonData buildError(String msg, Integer code) {
return new JsonData(code, null, msg);
}
@ -71,9 +64,8 @@ public class JsonData implements Serializable {
return new JsonData(0, data, msg);
}
/**
* ,
*/
//成功,传入数据,及状态码
public static JsonData buildSuccess(Object data, int code) {
return new JsonData(code, data, null);
}

@ -13,12 +13,15 @@ import java.util.List;
@Data
public class PageVo {
// 使用@JsonProperty注解将actionEntitySet字段映射到JSON中的actionEntitySet字段
@JsonProperty("actionEntitySet")
private List<ActionVo> actionVoList;
// 使用@JsonProperty注解将permissionId字段映射到JSON中的permissionId字段
@JsonProperty("permissionId")
private String pageName;
// 使用@JsonProperty注解将permissionName字段映射到JSON中的permissionName字段
@JsonProperty("permissionName")
private String pageDescription;
}

@ -13,42 +13,35 @@ import java.util.List;
@Data
public class QuestionCreateSimplifyVo {
/**
*
*/
//题目名称
@JsonProperty("name")
private String questionName;
@JsonProperty("desc")
private String questionDescription;
/**
* id
*/
//问题的难度的id
@JsonProperty("level")
private Integer questionLevelId;
/**
* ()
*/
//问题的类型(单选、多选、判断等)
@JsonProperty("type")
private Integer questionTypeId;
/**
*
*/
//题目的类别表,从内容角度划分,比如数学、语文、英语等
@JsonProperty("category")
private Integer questionCategoryId;
/**
*
*/
//创建选项 里添加的内容
@JsonProperty("option")
private String option;
/**
* truefalse
*/
//问题的选项列表带上了是否是答案的true和false
@JsonProperty("options")
private List<QuestionOptionCreateVo> questionOptionCreateVoList;
}

@ -13,48 +13,41 @@ import java.util.List;
@Data
public class QuestionCreateVo {
/**
*
*/
//题目名称
@JsonProperty("name")
private String questionName;
@JsonProperty("desc")
private String questionDescription;
/**
* ,5
*/
//题目的分数,默认值是5
@JsonProperty("score")
private Integer questionScore = 5;
/**
* idtoken
*/
//题目的创建者的id从token中解析得到
@JsonProperty("creator")
private String questionCreatorId;
/**
* id
*/
//问题的难度的id
@JsonProperty("level")
private Integer questionLevelId;
/**
* ()
*/
//问题的类型(单选、多选、判断等)
@JsonProperty("type")
private Integer questionTypeId;
/**
*
*/
//题目的类别表,从内容角度划分,比如数学、语文、英语等
@JsonProperty("category")
private Integer questionCategoryId;
/**
* truefalse
*/
//问题的选项列表带上了是否是答案的true和false
@JsonProperty("options")
private List<QuestionOptionCreateVo> questionOptionCreateVoList;
}

@ -14,30 +14,23 @@ import java.util.List;
@Data
public class QuestionDetailVo {
/**
* id
*/
//问题的id
private String id;
/**
*
*/
//考试题目
private String name;
/**
*
*/
//考试描述
private String description;
/**
*
*/
//问题的类型
private String type;
/**
*
*/
//问题的选项
private List<QuestionOption> options;
/**
* ,id
*/
//问题的答案,选项的id组成的数组
private List<String> answers = new ArrayList<>();
}

@ -12,15 +12,12 @@ import lombok.Data;
@Data
public class QuestionOptionCreateVo {
/**
*
*/
//问题的内容
@JsonProperty("content")
private String questionOptionContent;
/**
*
*/
//当前的选项是否是问题大答案
@JsonProperty("answer")
private Boolean answer = false;

@ -11,15 +11,19 @@ import lombok.Data;
@Data
public class QuestionOptionVo {
// 题目选项ID
@JsonProperty("id")
private String questionOptionId;
// 题目选项内容
@JsonProperty("content")
private String questionOptionContent;
// 是否为答案
@JsonProperty("answer")
private Boolean answer = false;
// 题目选项描述
@JsonProperty("description")
private String questionOptionDescription;
}

@ -14,29 +14,24 @@ import java.util.List;
@Data
public class QuestionPageVo {
/**
*
*/
//分页时每个分页的大小
private Integer pageSize;
/**
* 1
*/
//当前是在第几页注意要比前端传过来地小1
private Integer pageNo;
/**
*
*/
//一共有多少条符合条件的记录
private Long totalCount;
/**
*
*/
//一共有多少页
private Integer totalPage;
/**
*
*/
//当前页的详细数据
@JsonProperty("data")
private List<QuestionVo> questionVoList;
}

@ -16,12 +16,15 @@ import java.util.List;
@Data
public class QuestionSelectionVo {
// 问题类型列表
@JsonProperty("types")
private List<QuestionType> questionTypeList;
// 问题类别列表
@JsonProperty("categories")
private List<QuestionCategory> questionCategoryList;
// 问题等级列表
@JsonProperty("levels")
private List<QuestionLevel> questionLevelList;
}

@ -15,54 +15,50 @@ import java.util.List;
@Data
public class QuestionVo {
// 使用@JsonProperty注解将属性映射到JSON对象的属性
@JsonProperty("id")
private String questionId;
// 使用@JsonProperty注解将属性映射到JSON对象的属性
@JsonProperty("name")
private String questionName;
// 使用@JsonProperty注解将属性映射到JSON对象的属性
@JsonProperty("score")
private Integer questionScore;
/**
* questionCreatorId
*/
//根据questionCreatorId查询创建人
@JsonProperty("creator")
private String questionCreator;
/**
* questionLevelId
*/
//根据questionLevelId查询问题难度
@JsonProperty("level")
private String questionLevel;
/**
* question_levelid
*/
//问题难度级别在数据库表question_level中的id
@JsonProperty("levelId")
private int questionLevelId;
/**
* questionTypeId
*/
//问题类型根据questionTypeId获取
@JsonProperty("type")
private String questionType;
/**
* question_typeid
*/
//问题类型在数据库表question_type中的id
@JsonProperty("typeId")
private int questionTypeId;
/**
* questionCategoryId
*/
//问题分类根据questionCategoryId获得
@JsonProperty("category")
private String questionCategory;
/**
* question_categoryid
*/
//问题分类在数据库表question_category中的id
@JsonProperty("categoryId")
private int questionCategoryId;
@ -70,17 +66,15 @@ public class QuestionVo {
@JsonProperty("description")
private String questionDescription;
/**
* questionOptionIds,isAnswer
*/
//问题选项列表从questionOptionIds获得,需要自己额外给isAnswer赋值
@JsonProperty("options")
private List<QuestionOptionVo> questionOptionVoList;
/**
* Java
* @DynamicUpdate
*/
//更新时间设计表时设置了自动插入当前时间无需在Java代码中设置了。
//同时@DynamicUpdate注解可以时间当数据库数据变化时自动更新无需人工维护
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

@ -14,22 +14,18 @@ import java.util.List;
@Data
public class RecordDetailVo {
/**
*
*/
//含有考试记录原始信息的对象
private ExamRecord examRecord;
/**
* , idid
*/
//用户此次考试的作答信息, 键是题目的id值是选项id的列表
private HashMap<String, List<String>> answersMap;
/**
* MapidTrue or False
*/
//用户每题作答结果的Map键是问题的id值是用户这题是否回答正确True or False
private HashMap<String, String> resultsMap;
/**
* idid
*/
//正确答案键是题目的id值是正确答案的id组成的列表
private HashMap<String, List<String>> answersRightMap;
}

@ -14,27 +14,28 @@ import lombok.Data;
public class ResultVO<T> {
// 构造函数用于初始化ResultVO对象
public ResultVO(Integer code, String msg, T data) {
// 初始化code属性
this.code = code;
// 初始化msg属性
this.msg = msg;
// 初始化data属性
this.data = data;
}
public ResultVO() {
}
/**
*
*/
//错误码
private Integer code;
/**
*
*/
//提示信息
private String msg = "";
/**
*
*/
//具体内容
private T data;
}

@ -13,15 +13,19 @@ import java.util.List;
@Data
public class RoleVo {
// 角色名称
@JsonProperty("id")
private String roleName;
// 角色描述
@JsonProperty("name")
private String roleDescription;
// 角色详情
@JsonProperty("describe")
private String roleDetail;
// 角色权限
@JsonProperty("permissions")
private List<PageVo> pageVoList;
}

@ -12,15 +12,19 @@ import lombok.Data;
@Data
public class UserInfoVo {
// 用户ID
@JsonProperty("id")
private String userId;
// 用户头像
@JsonProperty("avatar")
private String userAvatar;
// 用户昵称
@JsonProperty("name")
private String userNickname;
// 用户名
@JsonProperty("username")
private String userUsername;
@ -29,18 +33,23 @@ public class UserInfoVo {
*/
private String password = "";
// 用户邮箱
@JsonProperty("email")
private String userEmail;
// 用户电话
@JsonProperty("telephone")
private String userPhone;
// 角色ID
@JsonProperty("roleId")
private String roleName;
// 用户描述
@JsonProperty("welcome")
private String userDescription;
// 角色信息
@JsonProperty("role")
private RoleVo roleVo;
}
}

@ -11,27 +11,35 @@ import lombok.Data;
@Data
public class UserVo {
// 用户ID
@JsonProperty("id")
private String userId;
// 用户名
@JsonProperty("username")
private String userUsername;
// 昵称
@JsonProperty("nickname")
private String userNickname;
// 角色ID
@JsonProperty("role")
private Integer userRoleId;
// 头像
@JsonProperty("avatar")
private String userAvatar;
// 描述
@JsonProperty("description")
private String userDescription;
// 邮箱
@JsonProperty("email")
private String userEmail;
// 电话
@JsonProperty("phone")
private String userPhone;
}

@ -1,44 +1,58 @@
events {
# 工作进程的最大连接数
worker_connections 1024;
}
http {
# 包含mime.types文件
include mime.types;
# 默认文件类型
default_type application/octet-stream;
# 开启sendfile功能
sendfile on;
# 保持连接超时时间
keepalive_timeout 65;
# 定义一个server
server {
# 监听80端口
listen 80;
# 服务器名称
server_name localhost;
# 404页面跳转
location / {
# 如果请求的文件不存在则尝试加载index.html
try_files $uri /index.html;
}
# 静态资源目录即vue打包后的dist里的静态资源
root /usr/share/nginx/html/;
# 默认首页
index index.html index.htm;
# 后端服务的配置
location /api/ {
proxy_redirect off;
# 关闭代理重定向
proxy_redirect off;
# 设置代理请求头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 后端服务地址
proxy_pass http://localhost:9527/api/;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
# 错误页面路径
location = /50x.html {
root html;
}
}
}
}

@ -1,7 +1,13 @@
# 定义语言为node_js
language: node_js
# 定义node_js版本为10.15.0
node_js:
- 10.15.0
# 缓存yarn
cache: yarn
# 定义脚本
script:
# 运行yarn
- yarn
- yarn run lint --no-fix && yarn run build
# 运行yarn run lint --no-fix && yarn run build
- yarn run lint --no-fix && yarn run build

@ -1,6 +1,9 @@
// 导出一个对象用于配置Babel
module.exports = {
// 使用@vue/app预设
presets: [
'@vue/app',
// 使用@babel/preset-env预设并设置useBuiltIns为entry
[
'@babel/preset-env',
{
@ -8,9 +11,10 @@ module.exports = {
}
]
]
// if your use import on Demand, Use this code
// 如果你的项目中使用了按需加载,可以使用以下代码
// ,
// plugins: [
// // 使用import插件配置ant-design-vue库的路径和样式
// [ 'import', {
// 'libraryName': 'ant-design-vue',
// 'libraryDirectory': 'es',

@ -1,23 +1,34 @@
// 导出配置文件
module.exports = {
// 模块文件扩展名
moduleFileExtensions: [
'js',
'jsx',
'json',
'vue'
],
// 转换规则
transform: {
// 将.vue文件转换为jest可识别的格式
'^.+\\.vue$': 'vue-jest',
// 将.css、.styl、.less、.sass、.scss、.svg、.png、.jpg、.ttf、.woff、.woff2文件转换为stub
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
// 将.jsx、.js文件转换为babel可识别的格式
'^.+\\.jsx?$': 'babel-jest'
},
// 模块名称映射
moduleNameMapper: {
// 将../替换为<rootDir>/src/
'^../(.*)$': '<rootDir>/src/$1'
},
// 快照序列化器
snapshotSerializers: [
'jest-serializer-vue'
],
// 测试匹配规则
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
// 测试URL
testURL: 'http://localhost/'
}
}

@ -1,24 +1,35 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<!-- 设置字符编码为utf-8 -->
<meta charset="utf-8">
<!-- 设置IE浏览器兼容模式 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 设置视口宽度为设备宽度初始缩放比例为1.0 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 设置网站图标 -->
<link rel="icon" href="<%= BASE_URL %>logo.png">
<!-- 设置网站标题 -->
<title>在线考试系统</title>
<!-- 设置加载动画样式 -->
<style>#loading-mask{position:fixed;left:0;top:0;height:100%;width:100%;background:#fff;user-select:none;z-index:9999;overflow:hidden}.loading-wrapper{position:absolute;top:50%;left:50%;transform:translate(-50%,-100%)}.loading-dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:64px;width:64px;height:64px;box-sizing:border-box}.loading-dot i{width:22px;height:22px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.loading-dot i:nth-child(1){top:0;left:0}.loading-dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.loading-dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.loading-dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
</head>
<body>
<!-- 如果禁用了JavaScript则显示以下内容 -->
<noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 应用容器 -->
<div id="app">
<!-- 加载动画遮罩层 -->
<div id="loading-mask">
<!-- 加载动画容器 -->
<div class="loading-wrapper">
<!-- 加载动画 -->
<span class="loading-dot loading-dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
</div>
</div>
<!-- built files will be auto injected -->
<!-- 自动注入构建文件 -->
</body>
</html>

@ -1,5 +1,7 @@
<template>
<!-- 使用a-locale-provider组件设置locale属性为locale -->
<a-locale-provider :locale="locale">
<!-- 在div中渲染router-view组件 -->
<div id="app">
<router-view />
</div>
@ -7,12 +9,16 @@
</template>
<script>
// zh_CN
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
// AppDeviceEnquire
import { AppDeviceEnquire } from './utils/mixin'
export default {
// 使AppDeviceEnquire
mixins: [AppDeviceEnquire],
data () {
// localezh_CN
return {
locale: zhCN
}
@ -23,34 +29,41 @@ export default {
}
</script>
<style>
/* 设置#app的高度为100% */
#app {
height: 100%;
}
/* 设置h1, h2, h3, h4, h5, h6的行高、字体大小和字体粗细为初始值 */
h1, h2, h3, h4, h5, h6 {
line-height: inherit !important;
font-size: initial !important;
font-weight: initial !important;
}
/* 设置.page-header .breadcrumb的背景颜色和内边距为初始值 */
.page-header .breadcrumb {
background-color: transparent !important;
padding: inherit !important;
}
/* 设置.row的左边距为初始值 */
.row {
margin-left: inherit !important;
}
/* 设置a标签的鼠标悬停效果为无下划线行高为初始值 */
a:hover {
text-decoration: none !important;
line-height: inherit !important;
}
/* 设置i标签下的svg的垂直对齐方式为初始值 */
i > svg {
vertical-align: initial !important;
}
/* 设置ul标签的下边距为初始值 */
ul {
margin-bottom: inherit !important;
}

@ -3,23 +3,30 @@
import api from './index'
import { axios } from '../utils/request'
// 导出一个函数,用于获取问题列表
export function getQuestionList (parameter) {
// 使用axios发送get请求获取问题列表
return axios({
url: api.ExamQuestionList,
method: 'get',
params: parameter
url: api.ExamQuestionList, // 请求的URL
method: 'get', // 请求的方法
params: parameter // 请求的参数
})
}
// 导出一个函数,用于获取所有题目
export function getQuestionAll () {
// 使用axios发送get请求获取所有题目
return axios({
url: api.ExamQuestionAll,
method: 'get'
})
}
// 导出一个名为questionUpdate的函数参数为parameter
export function questionUpdate (parameter) {
// 打印参数
console.log(parameter)
// 返回一个axios请求请求的url为api.ExamQuestionUpdate请求方法为post请求的数据为parameter
return axios({
url: api.ExamQuestionUpdate,
method: 'post',
@ -27,18 +34,26 @@ export function questionUpdate (parameter) {
})
}
// 导出一个函数,用于获取题目选择
export function getQuestionSelection () {
// 使用axios发送get请求获取题目选择
return axios({
// 请求的URL
url: api.ExamQuestionSelection,
// 请求的方法
method: 'get',
// 请求的头部信息
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
// 导出一个名为questionCreate的函数参数为parameter
export function questionCreate (parameter) {
// 打印参数
console.log(parameter)
// 使用axios发送post请求url为api.ExamQuestionCreate参数为parameter
return axios({
url: api.ExamQuestionCreate,
method: 'post',
@ -46,7 +61,9 @@ export function questionCreate (parameter) {
})
}
// 导出一个函数,用于获取考试列表
export function getExamList (parameter) {
// 使用axios发送get请求获取考试列表
return axios({
url: api.ExamList,
method: 'get',
@ -54,15 +71,18 @@ export function getExamList (parameter) {
})
}
// 导出一个函数,用于获取所有考试
export function getExamAll () {
// 使用axios发送get请求获取所有考试
return axios({
url: api.ExamAll,
method: 'get'
})
}
// 获取所有问题,按照单选、多选和判断进行分类
// 导出一个函数,用于获取所有问题,按照单选、多选和判断进行分类
export function getExamQuestionTypeList () {
// 使用axios发送get请求获取所有问题按照单选、多选和判断进行分类
return axios({
url: api.ExamQuestionTypeList,
method: 'get',
@ -72,7 +92,9 @@ export function getExamQuestionTypeList () {
})
}
// 导出一个函数,用于获取考试卡片列表
export function getExamCardList () {
// 使用axios发送get请求获取考试卡片列表
return axios({
url: api.ExamCardList,
method: 'get',
@ -82,8 +104,11 @@ export function getExamCardList () {
})
}
// 导出一个函数examCreate用于创建考试
export function examCreate (parameter) {
// 打印传入的参数
console.log(parameter)
// 返回一个axios请求请求的url为api.ExamCreate请求方法为post请求的数据为parameter
return axios({
url: api.ExamCreate,
method: 'post',
@ -91,36 +116,44 @@ export function examCreate (parameter) {
})
}
// 导出一个函数examUpdate用于更新考试
export function examUpdate (parameter) {
// 打印传入的参数
console.log(parameter)
// 返回一个axios请求请求的url为api.ExamUpdate请求方法为post请求的数据为parameter
return axios({
url: api.ExamUpdate,
method: 'post',
data: parameter
})
}
// 导出一个函数,用于获取考试详情
export function getExamDetail (examId) {
// 使用axios发送get请求获取考试详情
return axios({
url: api.ExamDetail + examId,
method: 'get',
url: api.ExamDetail + examId, // 请求的URL拼接了examId
method: 'get', // 请求方法为get
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF-8' // 设置请求头指定请求体的类型为json
}
})
}
// 导出一个函数,用于获取考试记录详情
export function getExamRecordDetail (recordId) {
// 使用axios发送get请求获取考试记录详情
return axios({
url: api.recordDetail + recordId,
method: 'get',
url: api.recordDetail + recordId, // 请求的URL拼接了recordId
method: 'get', // 请求方法为get
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF-8' // 设置请求头指定请求体的类型为json
}
})
}
// 导出一个函数,用于获取问题详情
export function getQuestionDetail (questionId) {
// 使用axios发送get请求获取问题详情
return axios({
url: api.QuestionDetail + questionId,
method: 'get',
@ -130,8 +163,11 @@ export function getQuestionDetail (questionId) {
})
}
// 导出一个函数,用于完成考试
export function finishExam (examId, answersMap) {
// 打印答案
console.log(answersMap)
// 使用axios发送post请求完成考试
return axios({
url: api.FinishExam + examId,
method: 'post',
@ -142,7 +178,9 @@ export function finishExam (examId, answersMap) {
})
}
// 导出一个函数,用于获取考试记录列表
export function getExamRecordList () {
// 使用axios发送get请求获取考试记录列表
return axios({
url: api.ExamRecordList,
method: 'get',

@ -1,38 +1,61 @@
// 定义一个api对象包含各种接口的路径
const api = {
// 登录接口
Login: '/auth/login',
// 登出接口
Logout: '/auth/logout',
// 密码找回接口
ForgePassword: '/auth/forge-password',
// 注册接口
Register: '/auth/register',
// 二维码接口
twoStepCode: '/auth/2step-code',
// 发送短信接口
SendSms: '/account/sms',
// 发送短信错误接口
SendSmsErr: '/account/sms_err',
// get my info
// 获取用户信息接口
UserInfo: '/user/info',
// 下面是自己的用户认证的接口
// 用户注册接口
UserRegister: '/user/register',
// 用户登录接口
UserLogin: '/user/login',
// 考试的接口
// 获取问题列表接口
ExamQuestionList: '/exam/question/list',
// 获取所有问题接口
ExamQuestionAll: '/exam/question/all',
// 更新问题接口
ExamQuestionUpdate: '/exam/question/update',
// 选择问题接口
ExamQuestionSelection: '/exam/question/selection',
// 创建问题接口
ExamQuestionCreate: '/exam/question/create',
// 获取考试列表接口
ExamList: '/exam/list',
// 获取所有考试接口
ExamAll: '/exam/all',
// 获取问题列表,按照单选、多选和判断进行分类
// 获取问题列表,按照单选、多选和判断进行分类接口
ExamQuestionTypeList: '/exam/question/type/list',
// 创建考试接口
ExamCreate: '/exam/create',
// 更新考试接口
ExamUpdate: '/exam/update',
// 获取考试卡片列表接口
ExamCardList: '/exam/card/list',
// 获取考试详情
// 获取考试详情接口
ExamDetail: '/exam/detail/',
// 获取考试详情
// 获取问题详情接口
QuestionDetail: '/exam/question/detail/',
// 交卷
// 交卷接口
FinishExam: '/exam/finish/',
// 获取考试记录列表接口
ExamRecordList: '/exam/record/list',
// 获取考试记录详情接口
recordDetail: '/exam/record/detail/'
}
export default api
// 导出api对象
export default api

@ -4,15 +4,24 @@ import { axios } from '../utils/request'
/**
* login func
* parameter: {
*
* username: '',
*
* password: '',
*
* remember_me: true,
*
*
* captcha: '12345'
*
* }
* @param parameter
*
* @returns {*}
*/
// 导出一个名为login的函数参数为parameter
export function login (parameter) {
// 使用axios发送post请求请求地址为api.UserLogin参数为parameter
return axios({
// 用户登录接口改成自己的
url: api.UserLogin,
@ -21,7 +30,9 @@ export function login (parameter) {
})
}
// 导出一个函数,用于获取短信验证码
export function getSmsCaptcha (parameter) {
// 使用axios发送post请求请求的url为api.SendSms参数为parameter
return axios({
url: api.SendSms,
method: 'post',
@ -29,20 +40,27 @@ export function getSmsCaptcha (parameter) {
})
}
// 导出一个函数,用于获取用户信息
export function getInfo () {
// 使用axios发送get请求获取用户信息
return axios({
// 请求的URL
url: api.UserInfo,
// 请求的方法
method: 'get',
// 请求的头部信息
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
// 导出一个名为logout的函数
export function logout () {
// 使用axios发送post请求请求地址为api.Logout
return axios({
url: api.Logout,
method: 'post',
// 设置请求头内容类型为application/json;charset=UTF-8
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
@ -51,12 +69,15 @@ export function logout () {
/**
* get user 2step code open?
*
* @param parameter {*}
*/
// 导出一个名为get2step的函数参数为parameter
export function get2step (parameter) {
// 使用axios发送post请求请求的url为api.twoStepCode请求的数据为parameter
return axios({
url: api.twoStepCode,
method: 'post',
data: parameter
})
}
}

@ -3,7 +3,9 @@
import api from './index'
import { axios } from '../utils/request'
// 导出一个名为login的函数用于用户登录
export function login (parameter) {
// 使用axios发送post请求请求地址为api.UserLogin请求参数为parameter
return axios({
url: api.UserLogin,
method: 'post',
@ -11,7 +13,9 @@ export function login (parameter) {
})
}
// 导出一个名为register的函数用于用户注册
export function register (parameter) {
// 使用axios发送post请求请求地址为api.UserRegister请求参数为parameter
return axios({
url: api.UserRegister,
method: 'post',

@ -1,6 +1,9 @@
<template>
<!-- 使用v-bind指令动态绑定class属性根据size和layout的值来决定class的值 -->
<div :class="['description-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
<!-- 如果title存在则显示title -->
<div v-if="title" class="title">{{ title }}</div>
<!-- 使用a-row组件来包裹slot -->
<a-row>
<slot></slot>
</a-row>
@ -8,10 +11,13 @@
</template>
<script>
// Col
import { Col } from 'ant-design-vue/es/grid/'
// Item
const Item = {
name: 'DetailListItem',
// props
props: {
term: {
type: String,
@ -19,21 +25,27 @@ const Item = {
required: false
}
},
// col
inject: {
col: {
type: Number
}
},
//
render () {
// Colprops
return (
<Col {...{ props: responsive[this.col] }}>
// term
<div class="term">{this.$props.term}</div>
//
<div class="content">{this.$slots.default}</div>
</Col>
)
}
}
//
const responsive = {
1: { xs: 24 },
2: { xs: 24, sm: 12 },
@ -41,12 +53,16 @@ const responsive = {
4: { xs: 24, sm: 12, md: 6 }
}
// DetailList
export default {
name: 'DetailList',
// Item
Item: Item,
// Col
components: {
Col
},
// props
props: {
title: {
type: String,
@ -69,6 +85,7 @@ export default {
default: 'horizontal'
}
},
// col
provide () {
return {
col: this.col > 4 ? 4 : this.col
@ -81,6 +98,7 @@ export default {
.description-list {
//
.title {
color: rgba(0, 0, 0, .85);
font-size: 14px;
@ -88,6 +106,7 @@ export default {
margin-bottom: 16px;
}
//
/deep/ .term {
color: rgba(0, 0, 0, .85);
display: table-cell;
@ -96,6 +115,7 @@ export default {
padding-bottom: 16px;
white-space: nowrap;
//
&:not(:empty):after {
content: ":";
margin: 0 8px 0 2px;
@ -112,6 +132,7 @@ export default {
padding-bottom: 16px;
width: 100%;
//
&:empty {
content: ' ';
height: 38px;
@ -119,6 +140,7 @@ export default {
}
}
//
&.small {
.title {
@ -133,6 +155,7 @@ export default {
}
}
//
&.large {
/deep/ .term, .content {
padding-bottom: 16px;
@ -143,6 +166,7 @@ export default {
}
}
//
&.vertical {
.term {
padding-bottom: 8px;

@ -14,117 +14,53 @@
</div>
</template>
<script>
import types from './type'
export default {
name: 'Exception',
props: {
type: {
type: String,
default: '404'
}
},
data () {
return {
config: types
}
},
methods: {
handleToHome () {
this.$router.push({ name: 'dashboard' })
}
}
}
</script>
<style lang="less">
@import "~ant-design-vue/lib/style/index";
.exception {
display: flex;
align-items: center;
height: 80%;
min-height: 500px;
.imgBlock {
flex: 0 0 62.5%;
width: 62.5%;
padding-right: 152px;
zoom: 1;
&::before,
&::after {
content: ' ';
display: table;
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
}
.imgEle {
float: right;
width: 100%;
max-width: 430px;
height: 360px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
}
.content {
flex: auto;
h1 {
margin-bottom: 24px;
color: #434e59;
font-weight: 600;
font-size: 72px;
line-height: 72px;
}
.desc {
margin-bottom: 16px;
color: @text-color-secondary;
font-size: 20px;
line-height: 28px;
}
.actions {
button:not(:last-child) {
margin-right: 8px;
}
}
}
}
@media screen and (max-width: @screen-xl) {
.exception {
.imgBlock {
padding-right: 88px;
}
}
}
@media screen and (max-width: @screen-sm) {
.exception {
display: block;
text-align: center;
.imgBlock {
margin: 0 auto 24px;
padding-right: 0;
}
}
}
@media screen and (max-width: @screen-xs) {
.exception {
.imgBlock {
margin-bottom: -24px;
overflow: hidden;
}
}
}
</style>
<template>
<div class="exception">
<!-- 图片块 -->
<div class="imgBlock">
<!-- 图片元素 -->
<div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}">
</div>
</div>
<!-- 内容块 -->
<div class="content">
<!-- 标题 -->
<h1>{{ config[type].title }}</h1>
<!-- 描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮 -->
<div class="actions">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template> <!-- 描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮 -->
<div class="actions">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template>
<template>
<div class="exception">
<!-- 图片块 -->
<div class="imgBlock">
<!-- 图片元素 -->
<div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}">
</div>
</div>
<!-- 内容块 -->
<div class="content">
<!-- 标题 -->
<h1>{{ config[type].title }}</h1>
<!-- 描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮 -->
<div class="actions">
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template>

@ -1,20 +1,30 @@
<template>
<!-- 页脚部分 -->
<div class="footer">
<!-- 链接部分 -->
<div class="links">
<!-- 代码仓链接 -->
<a href="https://github.com/19920625lsg/spring-boot-online-exam" target="_blank">代码仓</a>
<!-- 关于我链接 -->
<a href="https://19920625lsg.github.io" target="_blank">关于我</a>
<!-- 联系我链接 -->
<a href="mailto:liangshanguang2@gmail.com">联系我</a>
</div>
<!-- 版权部分 -->
<div class="copyright">
Copyright
<!-- 版权图标 -->
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span>
</div>
</div>
</template>
<script>
// Vue
export default {
//
name: 'GlobalFooter',
//
data () {
return {}
}
@ -23,27 +33,41 @@ export default {
<style lang="less" scoped>
.footer {
//
padding: 0 16px;
//
margin: 24px 0 24px;
//
text-align: center;
//
.links {
//
margin-bottom: 8px;
//
a {
//
color: rgba(0, 0, 0, 0.45);
//
&:hover {
//
color: rgba(0, 0, 0, 0.65);
}
//
&:not(:last-child) {
//
margin-right: 40px;
}
}
}
//
.copyright {
//
color: rgba(0, 0, 0, 0.45);
//
font-size: 14px;
}
}

@ -1,22 +1,35 @@
<template>
<!-- 使用transition组件实现动画效果name属性指定动画名称为showHeader -->
<transition name="showHeader">
<!-- 如果visible为true则显示div -->
<div v-if="visible" class="header-animat">
<a-layout-header
<!-- 如果visible为true则显示a-layout-header组件 -->
<a-layout-header>
v-if="visible"
<!-- 根据fixedHeader和sidebarOpened的值动态添加class -->
:class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
<!-- 设置padding为0 -->
:style="{ padding: '0' }">
<!-- 如果mode为'sidemenu'则显示header -->
<div v-if="mode === 'sidemenu'" class="header">
<!-- 如果device为'mobile'则显示menu-fold图标否则显示menu-unfold图标 -->
<a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/>
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/>
<!-- 显示user-menu组件 -->
<user-menu></user-menu>
</div>
<!-- 否则显示top-nav-header-index -->
<div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide">
<div class="header-index-left">
<!-- 显示logo组件如果device不为'mobile'则显示title -->
<logo class="top-nav-header" :show-title="device !== 'mobile'"/>
<!-- 如果device不为'mobile'则显示s-menu组件 -->
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
<!-- 如果device为'mobile'则显示menu-fold图标 -->
<a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
</div>
<!-- 显示user-menu组件 -->
<user-menu class="header-index-right"></user-menu>
</div>
</div>
@ -72,35 +85,46 @@ export default {
}
},
mounted () {
//
document.body.addEventListener('scroll', this.handleScroll, { passive: true })
},
methods: {
handleScroll () {
//
if (!this.autoHideHeader) {
return
}
//
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop
//
if (!this.ticking) {
this.ticking = true
requestAnimationFrame(() => {
//
if (this.oldScrollTop > scrollTop) {
this.visible = true
// 300
} else if (scrollTop > 300 && this.visible) {
this.visible = false
// 300
} else if (scrollTop < 300 && !this.visible) {
this.visible = true
}
//
this.oldScrollTop = scrollTop
//
this.ticking = false
})
}
},
toggle () {
// toggle
this.$emit('toggle')
}
},
beforeDestroy () {
//
document.body.removeEventListener('scroll', this.handleScroll, true)
}
}

@ -1,59 +1,87 @@
<template>
<a-layout-sider
<!-- 定义一个布局侧边栏 -->
<a-layout-sider>
<!-- 根据不同条件添加不同的class -->
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
<!-- 设置宽度为256px -->
width="256px"
<!-- 是否可折叠 -->
:collapsible="collapsible"
<!-- 绑定collapsed变量 -->
v-model="collapsed"
<!-- 设置触发器为null -->
:trigger="null">
<!-- 引入logo组件 -->
<logo />
<s-menu
<!-- 引入s-menu组件 -->
<s-menu>
<!-- 绑定collapsed变量 -->
:collapsed="collapsed"
<!-- 绑定menus变量 -->
:menu="menus"
<!-- 绑定theme变量 -->
:theme="theme"
<!-- 绑定mode变量 -->
:mode="mode"
<!-- 监听select事件 -->
@select="onSelect"
<!-- 设置样式 -->
style="padding: 16px 0px;"></s-menu>
</a-layout-sider>
</template>
<script>
// Logo
import Logo from '../../components/tools/Logo'
// SMenu
import SMenu from './index'
// mixinmixinDevice
import { mixin, mixinDevice } from '../../utils/mixin'
export default {
name: 'SideMenu',
//
components: { Logo, SMenu },
// 使mixinmixinDevice
mixins: [mixin, mixinDevice],
// props
props: {
// inline
mode: {
type: String,
required: false,
default: 'inline'
},
// dark
theme: {
type: String,
required: false,
default: 'dark'
},
// false
collapsible: {
type: Boolean,
required: false,
default: false
},
// false
collapsed: {
type: Boolean,
required: false,
default: false
},
//
menus: {
type: Array,
required: true
}
},
//
methods: {
//
onSelect (obj) {
// menuSelect
this.$emit('menuSelect', obj)
}
}

@ -6,20 +6,24 @@ const { Item, SubMenu } = Menu
export default {
name: 'SMenu',
props: {
// 菜单数据
menu: {
type: Array,
required: true
},
// 主题
theme: {
type: String,
required: false,
default: 'dark'
},
// 模式
mode: {
type: String,
required: false,
default: 'inline'
},
// 是否折叠
collapsed: {
type: Boolean,
required: false,
@ -28,12 +32,16 @@ export default {
},
data () {
return {
// 打开的菜单项
openKeys: [],
// 选择的菜单项
selectedKeys: [],
// 缓存的打开的菜单项
cachedOpenKeys: []
}
},
computed: {
// 根菜单项的key
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
@ -41,23 +49,29 @@ export default {
}
},
mounted () {
// 组件挂载时更新菜单
this.updateMenu()
},
watch: {
// 监听折叠状态的变化
collapsed (val) {
if (val) {
// 折叠时,缓存打开的菜单项
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
} else {
// 展开时,恢复打开的菜单项
this.openKeys = this.cachedOpenKeys
}
},
// 监听路由的变化
$route: function () {
// 更新菜单
this.updateMenu()
}
},
methods: {
// select menu item
// 选择菜单项
onOpenChange (openKeys) {
// 在水平模式下时执行,并且不再执行后续
if (this.mode === 'horizontal') {
@ -72,18 +86,30 @@ export default {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
},
updateMenu () {
// 更新菜单
// 更新菜单
updateMenu () {
// 获取当前路由匹配的路径
const routes = this.$route.matched.concat()
// 获取当前路由的meta信息
const { hidden } = this.$route.meta
// 如果路由长度大于等于3且meta信息中hidden为true
if (routes.length >= 3 && hidden) {
// 移除最后一个路径
routes.pop()
// 设置选中的路径为倒数第二个路径
this.selectedKeys = [routes[routes.length - 1].path]
} else {
// 设置选中的路径为最后一个路径
this.selectedKeys = [routes.pop().path]
}
// 定义打开的路径
const openKeys = []
// 如果模式为inline
if (this.mode === 'inline') {
// 遍历路由
routes.forEach(item => {
// 将路径添加到打开的路径中
openKeys.push(item.path)
})
}
@ -91,28 +117,34 @@ export default {
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
},
// render
// 渲染
renderItem (menu) {
if (!menu.hidden) {
return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
}
return null
},
// 渲染菜单项
renderMenuItem (menu) {
// 获取菜单项的目标
const target = menu.meta.target || null
// 根据目标判断使用a标签还是router-link标签
const tag = target && 'a' || 'router-link'
// 设置router-link的属性
const props = { to: { name: menu.name } }
// 设置a标签的属性
const attrs = { href: menu.path, target: menu.meta.target }
// 如果菜单项有子菜单并且父菜单是要隐藏子菜单的
// 给子菜单增加一个hidden属性
// 用来给刷新页面时selectedKeys做控制用
if (menu.children && menu.hideChildrenInMenu) {
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
// 都给子菜单增加一个 hidden 属性
// 用来给刷新页面时, selectedKeys 做控制用
menu.children.forEach(item => {
item.meta = Object.assign(item.meta, { hidden: true })
})
}
// 返回渲染后的菜单项
return (
<Item {...{ key: menu.path }}>
<tag {...{ props, attrs }}>
@ -122,11 +154,16 @@ export default {
</Item>
)
},
// 渲染子菜单
renderSubMenu (menu) {
// 创建一个数组用来存放子菜单
const itemArr = []
// 如果父菜单不是要隐藏子菜单的
// 就把子菜单渲染出来
if (!menu.hideChildrenInMenu) {
menu.children.forEach(item => itemArr.push(this.renderItem(item)))
}
// 返回渲染后的子菜单
return (
<SubMenu {...{ key: menu.path }}>
<span slot="title">
@ -137,12 +174,17 @@ export default {
</SubMenu>
)
},
// 渲染图标
renderIcon (icon) {
// 如果图标是none或者undefined则返回null
if (icon === 'none' || icon === undefined) {
return null
}
// 创建一个属性对象
const props = {}
// 如果图标是对象则设置component属性
typeof (icon) === 'object' ? props.component = icon : props.type = icon
// 返回渲染后的图标
return (
<Icon {... { props } }/>
)
@ -150,31 +192,40 @@ export default {
},
render () {
// 解构赋值从this中获取mode、theme、menu属性
const { mode, theme, menu } = this
// 定义props对象包含mode、theme、openKeys属性
const props = {
mode: mode,
theme: theme,
openKeys: this.openKeys
}
// 定义on对象包含select和openChange方法
const on = {
select: obj => {
// 将选中的key赋值给this.selectedKeys
this.selectedKeys = obj.selectedKeys
// 触发select事件并传递obj参数
this.$emit('select', obj)
},
openChange: this.onOpenChange
}
// 遍历menu数组生成menuTree
const menuTree = menu.map(item => {
// 如果item.hidden为true则返回null
if (item.hidden) {
return null
}
// 否则调用renderItem方法生成菜单项
return this.renderItem(item)
})
// {...{ props, on: on }}
// 返回Menu组件vModel绑定this.selectedKeysprops和on分别绑定props和on对象
return (
<Menu vModel={this.selectedKeys} {...{ props, on: on }}>
{menuTree}
</Menu>
)
}
}
}

@ -3,23 +3,31 @@ import Icon from 'ant-design-vue/es/icon'
const { Item, SubMenu } = Menu
export default {
// 导出一个默认对象该对象是一个名为SMenu的组件
render (h)
export default {
// 组件的名称
name: 'SMenu',
// 组件的属性
props: {
// 菜单数据,类型为数组,必填
menu: {
type: Array,
required: true
},
// 主题,类型为字符串,非必填,默认值为'dark'
theme: {
type: String,
required: false,
default: 'dark'
},
// 模式,类型为字符串,非必填,默认值为'inline'
mode: {
type: String,
required: false,
default: 'inline'
},
// 是否折叠类型为布尔值非必填默认值为false
collapsed: {
type: Boolean,
required: false,
@ -27,6 +35,7 @@ export default {
}
},
data () {
// 定义三个变量openKeys用于存储当前打开的菜单项selectedKeys用于存储当前选中的菜单项cachedOpenKeys用于存储折叠前的菜单项
return {
openKeys: [],
selectedKeys: [],
@ -34,123 +43,183 @@ export default {
}
},
computed: {
// 计算属性用于获取根菜单项的key
rootSubmenuKeys: vm => {
const keys = []
// 遍历menu数组将每个菜单项的path添加到keys数组中
vm.menu.forEach(item => keys.push(item.path))
return keys
}
},
created () {
// 组件创建时调用updateMenu方法
this.updateMenu()
},
watch: {
// 监听collapsed属性当折叠状态改变时更新openKeys和cachedOpenKeys
collapsed (val) {
if (val) {
// 如果折叠将当前打开的菜单项保存到cachedOpenKeys中并将openKeys置空
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
} else {
// 如果展开将cachedOpenKeys赋值给openKeys
this.openKeys = this.cachedOpenKeys
}
},
// 监听$route属性当路由改变时调用updateMenu方法
$route: function () {
this.updateMenu()
}
},
methods: {
// 渲染图标
renderIcon: function (h, icon) {
// 如果图标为空或未定义则返回null
if (icon === 'none' || icon === undefined) {
return null
}
// 定义props对象
const props = {}
// 如果图标是对象则将图标赋值给props.component否则将图标赋值给props.type
typeof (icon) === 'object' ? props.component = icon : props.type = icon
// 返回Icon组件并传入props对象
return h(Icon, { props: { ...props } })
},
// 渲染菜单项
renderMenuItem: function (h, menu, pIndex, index) {
// 获取菜单的target属性如果不存在则赋值为null
const target = menu.meta.target || null
// 返回Item组件并传入key属性和子组件
return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [
// 返回router-link组件并传入to属性和target属性
h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [
// 调用renderIcon方法传入h和菜单的icon属性
this.renderIcon(h, menu.meta.icon),
// 返回span组件并传入菜单的title属性
h('span', [menu.meta.title])
])
])
},
renderSubMenu: function (h, menu, pIndex, index) {
// 渲染子菜单
renderSubMenu: function (h, menu, pIndex, index) {
// 定义this2_为当前对象
const this2_ = this
// 定义subItem为子菜单项
const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
// 定义itemArr为子菜单项数组
const itemArr = []
// 定义pIndex_为父菜单项索引
const pIndex_ = pIndex + '_' + index
// 打印menu
console.log('menu', menu)
// 如果菜单项不隐藏子菜单
if (!menu.hideChildrenInMenu) {
// 遍历子菜单项
menu.children.forEach(function (item, i) {
// 将子菜单项添加到itemArr数组中
itemArr.push(this2_.renderItem(h, item, pIndex_, i))
})
}
// 返回子菜单
return h(SubMenu, { key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index }, subItem.concat(itemArr))
},
// 渲染菜单项
renderItem: function (h, menu, pIndex, index) {
// 如果菜单项不隐藏
if (!menu.hidden) {
// 如果菜单项有子菜单且不隐藏子菜单,则渲染子菜单
return menu.children && !menu.hideChildrenInMenu
? this.renderSubMenu(h, menu, pIndex, index)
// 否则渲染菜单项
: this.renderMenuItem(h, menu, pIndex, index)
}
},
renderMenu: function (h, menuTree) {
// 渲染菜单
renderMenu: function (h, menuTree) {
// 定义this2_为当前对象
const this2_ = this
// 定义menuArr为空数组
const menuArr = []
// 遍历menuTree
menuTree.forEach(function (menu, i) {
// 如果menu不隐藏
if (!menu.hidden) {
// 将renderItem方法返回的值添加到menuArr数组中
menuArr.push(this2_.renderItem(h, menu, '0', i))
}
})
// 返回menuArr数组
return menuArr
},
// 打开菜单
onOpenChange (openKeys) {
// 定义latestOpenKey为openKeys中不包含在this.openKeys中的值
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
// 如果latestOpenKey不包含在rootSubmenuKeys中
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
// 将openKeys赋值给this.openKeys
this.openKeys = openKeys
} else {
// 如果latestOpenKey存在将latestOpenKey赋值给this.openKeys
// 否则将this.openKeys赋值为空数组
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
},
updateMenu () {
// 更新菜单
updateMenu () {
// 获取当前路由匹配的路径
const routes = this.$route.matched.concat()
// 如果路由长度大于等于4且meta中hidden属性为true
if (routes.length >= 4 && this.$route.meta.hidden) {
// 移除最后一个路径
routes.pop()
// 设置选中的路径为倒数第二个路径
this.selectedKeys = [routes[2].path]
} else {
// 否则设置选中的路径为最后一个路径
this.selectedKeys = [routes.pop().path]
}
// 定义打开的路径
const openKeys = []
// 如果模式为inline
if (this.mode === 'inline') {
// 遍历路径
routes.forEach(item => {
// 将路径添加到打开的路径中
openKeys.push(item.path)
})
}
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
},
this:collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
}
},
render (h) {
};{
// 使用h函数渲染Menu组件
return h(
Menu,
{
// 设置Menu组件的props属性
props: {
theme: this.$props.theme,
mode: this.$props.mode,
openKeys: this.openKeys,
selectedKeys: this.selectedKeys
},
// 设置Menu组件的事件
on: {
openChange: this.onOpenChange,
select: obj => {
// 设置selectedKeys属性
this.selectedKeys = obj.selectedKeys
// 触发select事件
this.$emit('select', obj)
}
}
},
// 渲染菜单
this.renderMenu(h, this.menu)
)
}
}
}

@ -30,37 +30,51 @@ export default {
name: 'MultiTab',
data () {
return {
//
fullPathList: [],
//
pages: [],
//
activeKey: '',
//
newTabIndex: 0
}
},
created () {
//
this.pages.push(this.$route)
//
this.fullPathList.push(this.$route.fullPath)
//
this.selectedLastPath()
},
methods: {
//
onEdit (targetKey, action) {
this[action](targetKey)
},
//
remove (targetKey) {
//
this.pages = this.pages.filter(page => page.fullPath !== targetKey)
//
this.fullPathList = this.fullPathList.filter(path => path !== targetKey)
//
if (!this.fullPathList.includes(this.activeKey)) {
this.selectedLastPath()
}
},
//
selectedLastPath () {
this.activeKey = this.fullPathList[this.fullPathList.length - 1]
},
// content menu
//
closeThat (e) {
this.remove(e)
},
//
closeLeft (e) {
const currentIndex = this.fullPathList.indexOf(e)
if (currentIndex > 0) {
@ -73,6 +87,7 @@ export default {
this.$message.info('左侧没有标签')
}
},
//
closeRight (e) {
const currentIndex = this.fullPathList.indexOf(e)
if (currentIndex < (this.fullPathList.length - 1)) {
@ -85,6 +100,7 @@ export default {
this.$message.info('右侧没有标签')
}
},
//
closeAll (e) {
const currentIndex = this.fullPathList.indexOf(e)
this.fullPathList.forEach((item, index) => {
@ -93,6 +109,7 @@ export default {
}
})
},
//
closeMenuClick ({ key, item, domEvent }) {
const vkey = domEvent.target.getAttribute('data-vkey')
switch (key) {
@ -111,6 +128,7 @@ export default {
break
}
},
//
renderTabPaneMenu (e) {
return (
<a-menu {...{ on: { click: this.closeMenuClick } }}>
@ -121,7 +139,7 @@ export default {
</a-menu>
)
},
// render
//
renderTabPane (title, keyPath) {
const menu = this.renderTabPaneMenu(keyPath)
@ -133,19 +151,25 @@ export default {
}
},
watch: {
//
'$route': function (newVal) {
//
this.activeKey = newVal.fullPath
//
if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
this.fullPathList.push(newVal.fullPath)
this.pages.push(newVal)
}
},
//
activeKey: function (newPathKey) {
//
this.$router.push({ path: newPathKey })
}
},
render () {
const { onEdit, $data: { pages } } = this
//
const panes = pages.map(page => {
return (
<a-tab-pane

@ -52,20 +52,27 @@ export default {
name: 'HeaderNotice',
data () {
return {
//
loadding: false,
//
visible: false
}
},
methods: {
//
fetchNotice () {
//
if (!this.visible) {
this.loadding = true
// 2
setTimeout(() => {
this.loadding = false
}, 2000)
} else {
//
this.loadding = false
}
//
this.visible = !this.visible
}
}
@ -73,17 +80,22 @@ export default {
</script>
<style lang="css">
/* 设置头部通知的顶部位置 */
.header-notice-wrapper {
top: 50px !important;
}
</style>
<style lang="less" scoped>
/* 设置头部通知的样式 */
.header-notice{
/* 设置显示方式为行内块 */
display: inline-block;
/* 设置过渡效果 */
transition: all 0.3s;
/* 设置span标签的垂直对齐方式 */
span {
vertical-align: initial;
}
}
</style>
</style>

@ -1,27 +1,41 @@
<template>
<!-- 页面头部 -->
<div class="page-header">
<!-- 页面头部索引宽 -->
<div class="page-header-index-wide">
<!-- 面包屑导航 -->
<s-breadcrumb />
<!-- 详情 -->
<div class="detail">
<!-- 主要内容 -->
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
<!-- -->
<div class="row">
<!-- logo -->
<img v-if="logo" :src="logo" class="logo"/>
<!-- 标题 -->
<h1 v-if="title" class="title">{{ title }}</h1>
<!-- 操作 -->
<div class="action">
<slot name="action"></slot>
</div>
</div>
<!-- -->
<div class="row">
<!-- 头像 -->
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar" />
</div>
<!-- 内容 -->
<div v-if="this.$slots.content" class="headerContent">
<slot name="content"></slot>
</div>
<!-- 额外内容 -->
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
<!-- 页面菜单 -->
<div>
<slot name="pageMenu"></slot>
</div>
@ -32,30 +46,37 @@
</template>
<script>
//
import Breadcrumb from '../../components/tools/Breadcrumb'
export default {
name: 'PageHeader',
//
components: {
's-breadcrumb': Breadcrumb
},
//
props: {
// true
title: {
type: [String, Boolean],
default: true,
required: false
},
// logo
logo: {
type: String,
default: '',
required: false
},
//
avatar: {
type: String,
default: '',
required: false
}
},
//
data () {
return {}
}

@ -1,22 +1,33 @@
<template>
<!-- 定义一个result类名的div -->
<div class="result">
<!-- 定义一个div -->
<div>
<!-- 使用a-icon组件根据localIsSuccess的值来决定显示check-circle还是close-circle -->
<a-icon :class="{ 'icon': true, [`${type}`]: true }" :type="localIsSuccess ? 'check-circle' : 'close-circle'"/>
</div>
<!-- 定义一个title类名的div -->
<div class="title">
<!-- 使用slot插槽如果没有传入title插槽则显示title的值 -->
<slot name="title">
{{ title }}
</slot>
</div>
<!-- 定义一个description类名的div -->
<div class="description">
<!-- 使用slot插槽如果没有传入description插槽则显示description的值 -->
<slot name="description">
{{ description }}
</slot>
</div>
<!-- 如果有传入default插槽则显示extra类名的div -->
<div class="extra" v-if="$slots.default">
<!-- 使用slot插槽 -->
<slot></slot>
</div>
<!-- 如果有传入action插槽则显示action类名的div -->
<div class="action" v-if="$slots.action">
<!-- 使用slot插槽 -->
<slot name="action"></slot>
</div>
</div>

@ -1,7 +1,11 @@
<template>
<!-- 设置抽屉索引项 -->
<div class="setting-drawer-index-item">
<!-- 设置抽屉索引项标题 -->
<h3 class="setting-drawer-index-title">{{ title }}</h3>
<!-- 插槽 -->
<slot></slot>
<!-- 如果divider为true则显示分割线 -->
<a-divider v-if="divider"/>
</div>
</template>
@ -10,10 +14,12 @@
export default {
name: 'SettingItem',
props: {
//
title: {
type: String,
default: ''
},
// 线
divider: {
type: Boolean,
default: false
@ -24,13 +30,20 @@ export default {
<style lang="less" scoped>
//
.setting-drawer-index-item {
//
margin-bottom: 24px;
//
.setting-drawer-index-title {
//
font-size: 14px;
//
color: rgba(0, 0, 0, .85);
//
line-height: 22px;
//
margin-bottom: 12px;
}

@ -1,8 +1,11 @@
<template>
<!-- 使用v-bind指令绑定class属性根据条件动态添加class -->
<div :class="[prefixCls, lastCls, blockCls, gridCls]">
<!-- 如果title存在则显示title -->
<div v-if="title" class="antd-pro-components-standard-form-row-index-label">
<span>{{ title }}</span>
</div>
<!-- 显示slot内容 -->
<div class="antd-pro-components-standard-form-row-index-content">
<slot></slot>
</div>
@ -10,6 +13,7 @@
</template>
<script>
//
const classes = [
'antd-pro-components-standard-form-row-index-standardFormRowBlock',
'antd-pro-components-standard-form-row-index-standardFormRowGrid',
@ -17,6 +21,7 @@ const classes = [
]
export default {
name: 'StandardFormRow',
//
props: {
prefixCls: {
type: String,
@ -36,13 +41,17 @@ export default {
type: Boolean
}
},
//
computed: {
// lasttrueclassesnull
lastCls () {
return this.last ? classes[2] : null
},
// blocktrueclassesnull
blockCls () {
return this.block ? classes[0] : null
},
// gridtrueclassesnull
gridCls () {
return this.grid ? classes[1] : null
}
@ -53,12 +62,15 @@ export default {
<style lang="less" scoped>
@import '../index.less';
// index.less
.antd-pro-components-standard-form-row-index-standardFormRow {
display: flex;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px dashed @border-color-split;
//
/deep/ .ant-form-item {
margin-right: 24px;
}
@ -72,6 +84,7 @@ export default {
line-height: 32px;
}
//
.antd-pro-components-standard-form-row-index-label {
flex: 0 0 auto;
margin-right: 24px;
@ -88,6 +101,7 @@ export default {
}
}
//
.antd-pro-components-standard-form-row-index-content {
flex: 1 1 0;
/deep/ .ant-form-item:last-child {
@ -95,12 +109,14 @@ export default {
}
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowLast {
margin-bottom: 0;
padding-bottom: 0;
border: none;
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowBlock {
/deep/ .ant-form-item,
div.ant-form-item-control-wrapper {
@ -108,6 +124,7 @@ export default {
}
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowGrid {
/deep/ .ant-form-item,
div.ant-form-item-control-wrapper {
@ -119,4 +136,4 @@ export default {
}
}
</style>
</style>

@ -176,16 +176,22 @@ export default {
})
}
},
initTotalList (columns) {
// 初始化总列表
initTotalList (columns) {
// 定义一个空数组
const totalList = []
// 判断columns是否为数组
columns && columns instanceof Array && columns.forEach(column => {
// 判断column是否需要统计
if (column.needTotal) {
// 将column添加到totalList中并初始化total为0
totalList.push({
...column,
total: 0
})
}
})
// 返回totalList
return totalList
},
/**
@ -194,14 +200,21 @@ export default {
* @param selectedRows
*/
updateSelect (selectedRowKeys, selectedRows) {
// 将selectedRows赋值给this.selectedRows
this.selectedRows = selectedRows
// 将selectedRowKeys赋值给this.selectedRowKeys
this.selectedRowKeys = selectedRowKeys
// 获取需要统计的列表
const list = this.needTotalList
// 更新needTotalList
this.needTotalList = list.map(item => {
// 返回一个新的对象包含item的所有属性并更新total属性
return {
...item,
total: selectedRows.reduce((sum, val) => {
// 将val中item.dataIndex对应的值转换为整数并累加到sum中
const total = sum + parseInt(get(val, item.dataIndex))
// 如果total不是数字则返回0
return isNaN(total) ? 0 : total
}, 0)
}
@ -222,7 +235,9 @@ export default {
* @returns {*}
*/
renderClear (callback) {
// 如果没有选中行,则返回 null
if (this.selectedRowKeys.length <= 0) return null
// 返回一个 a 标签,点击时调用 callback 函数,并清空选中行
return (
<a style="margin-left: 24px" onClick={() => {
callback()
@ -240,8 +255,10 @@ export default {
// 绘制 清空 按钮
const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? (
// 如果 alert.clear 为 true则调用 this.renderClear(this.clearSelected)
this.renderClear(this.clearSelected)
) : (this.alert !== null && typeof this.alert.clear === 'function') ? (
// 如果 alert.clear 为函数,则调用 this.renderClear(this.alert.clear)
this.renderClear(this.alert.clear)
) : null
@ -259,19 +276,26 @@ export default {
},
render () {
// 定义一个空对象props
const props = {}
// 获取当前组件的data中的所有key
const localKeys = Object.keys(this.$data)
// 判断是否需要显示alert
const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert
// 遍历T.props中的所有key
Object.keys(T.props).forEach(k => {
// 将key的首字母大写并拼接成localKey
const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}`
// 如果localKeys中包含localKey则将localKey对应的值赋给props[k]
if (localKeys.includes(localKey)) {
props[k] = this[localKey]
return props[k]
}
// 如果key为rowSelection
if (k === 'rowSelection') {
// 如果需要使用alert则重新绑定 rowSelection 事件
if (showAlert && this.rowSelection) {
// 如果需要使用alert则重新绑定 rowSelection 事件
props[k] = {
selectedRows: this.selectedRows,
selectedRowKeys: this.selectedRowKeys,
@ -281,21 +305,24 @@ export default {
}
}
return props[k]
// 如果没打算开启 rowSelection 则清空默认的选择项
} else if (!this.rowSelection) {
// 如果没打算开启 rowSelection 则清空默认的选择项
props[k] = null
return props[k]
}
}
// 如果this[k]存在则将this[k]赋给props[k]
this[k] && (props[k] = this[k])
return props[k]
})
// 定义一个table组件
const table = (
<a-table {...{ props, scopedSlots: { ...this.$scopedSlots } }} onChange={this.loadData}>
{ Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>)) }
</a-table>
)
// 返回一个div组件包含alert和table
return (
<div class="table-wrapper">
{ showAlert ? this.renderAlert() : null }
@ -303,4 +330,5 @@ export default {
</div>
)
}
}

@ -1,46 +1,69 @@
/**
* components util
*
*/
/**
* 清理空值对象
*
* @param children
*
* @returns {*[]}
*
*/
// 导出一个函数,用于过滤掉空节点
export function filterEmpty (children = []) {
// 过滤掉没有tag属性和text属性为空字符串的节点
return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
}
/**
* 获取字符串长度英文字符 长度1中文字符长度2
*
* @param {*} str
*
*/
// 导出一个函数,用于获取字符串长度
export const getStrFullLength = (str = '') =>
// 将字符串分割成字符数组然后使用reduce方法遍历数组计算每个字符的长度
str.split('').reduce((pre, cur) => {
// 获取字符的Unicode编码
const charCode = cur.charCodeAt(0)
// 如果字符的Unicode编码在0-128之间说明是英文字符长度为1
if (charCode >= 0 && charCode <= 128) {
return pre + 1
}
// 否则说明是中文字符长度为2
return pre + 2
}, 0)
/**
* 截取字符串根据 maxLength 截取后返回
*
* @param {*} str
*
* @param {*} maxLength
*/
// 导出一个函数,用于截取字符串
export const cutStrByFullLength = (str = '', maxLength) => {
// 初始化显示长度为0
let showLength = 0
// 将字符串分割成字符数组然后使用reduce方法遍历数组截取字符串
return str.split('').reduce((pre, cur) => {
// 获取字符的Unicode编码
const charCode = cur.charCodeAt(0)
// 如果字符的Unicode编码在0-128之间说明是英文字符长度为1
if (charCode >= 0 && charCode <= 128) {
showLength += 1
} else {
// 否则说明是中文字符长度为2
showLength += 2
}
// 如果显示长度小于等于maxLength则将字符添加到结果中
if (showLength <= maxLength) {
return pre + cur
}
// 否则,返回结果
return pre
}, '')
}

@ -19,21 +19,27 @@ export default {
}
},
created () {
//
this.getBreadcrumb()
},
methods: {
getBreadcrumb () {
//
this.breadList = []
// this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: ''}})
//
this.name = this.$route.name
//
this.$route.matched.forEach(item => {
// index
// item.name !== 'index' && this.breadList.push(item)
this.breadList.push(item)
})
}
},
watch: {
//
$route () {
this.getBreadcrumb()
}

@ -1,26 +1,35 @@
<template>
<!-- logo组件 -->
<div class="logo">
<!-- 使用router-link组件跳转到dashboard页面 -->
<router-link :to="{name:'dashboard'}">
<!-- 使用LogoSvg组件显示logo -->
<LogoSvg alt="logo" />
<!-- 如果showTitle为true则显示title -->
<h1 v-if="showTitle">{{ title }}</h1>
</router-link>
</div>
</template>
<script>
// logo.svg
import LogoSvg from '../../assets/logo.svg?inline'
export default {
name: 'Logo',
//
components: {
LogoSvg
},
// props
props: {
//
title: {
type: String,
default: 'Online Exam',
required: false
},
//
showTitle: {
type: Boolean,
default: true,
@ -28,4 +37,4 @@ export default {
}
}
}
</script>
</script>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save