添加后台i18n,路由懒加载

This commit is contained in:
Himit_ZH 2021-06-08 17:51:34 +08:00
parent 71490fa713
commit 6e96a7011c
48 changed files with 1551 additions and 800 deletions

View File

@ -68,6 +68,7 @@ Password: 开启SMTP服务后生成的随机授权码
| 2021-05-28 | 增加导入导出题目,增加用户页面的最近登录,开发正式结束,进入维护摸鱼 | Himit_ZH |
| 2021-06-02 | 大更新完善补充前端页面修正判题等待超时时间修补一系列bug | Himit_ZH |
| 2021-06-07 | 修正特殊判题增加前台i18n | Himit_ZH |
| 2021-06-08 | 添加后台i18n,路由懒加载 | Himit_ZH |

View File

@ -2,6 +2,7 @@ package top.hcode.hoj.controller.admin;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -63,15 +64,15 @@ public class ConfigController {
public CommonResult getWebConfig() {
return CommonResult.successResponse(
MapUtil.builder().put("baseUrl", configVo.getBaseUrl())
.put("name", configVo.getName())
.put("shortName", configVo.getShortName())
.put("description", configVo.getDescription())
MapUtil.builder().put("baseUrl", UnicodeUtil.toString(configVo.getBaseUrl()))
.put("name", UnicodeUtil.toString(configVo.getName()))
.put("shortName", UnicodeUtil.toString(configVo.getShortName()))
.put("description", UnicodeUtil.toString(configVo.getDescription()))
.put("register", configVo.getRegister())
.put("recordName", configVo.getRecordName())
.put("recordUrl", configVo.getRecordUrl())
.put("projectName", configVo.getProjectName())
.put("projectUrl", configVo.getProjectUrl()).map()
.put("recordName", UnicodeUtil.toString(configVo.getRecordName()))
.put("recordUrl", UnicodeUtil.toString(configVo.getRecordUrl()))
.put("projectName", UnicodeUtil.toString(configVo.getProjectName()))
.put("projectUrl", UnicodeUtil.toString(configVo.getProjectUrl())).map()
);
}

View File

@ -1,6 +1,7 @@
package top.hcode.hoj.controller.oj;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@ -131,16 +132,16 @@ public class HomeController {
public CommonResult getWebConfig() {
return CommonResult.successResponse(
MapUtil.builder().put("baseUrl", configVo.getBaseUrl())
.put("name", configVo.getName())
.put("shortName", configVo.getShortName())
MapUtil.builder().put("baseUrl", UnicodeUtil.toString(configVo.getBaseUrl()))
.put("name", UnicodeUtil.toString(configVo.getName()))
.put("shortName", UnicodeUtil.toString(configVo.getShortName()))
.put("register", configVo.getRegister())
.put("recordName", configVo.getRecordName())
.put("recordUrl", configVo.getRecordUrl())
.put("description", configVo.getDescription())
.put("email", configVo.getEmailUsername())
.put("projectName", configVo.getProjectName())
.put("projectUrl", configVo.getProjectUrl()).map()
.put("recordName", UnicodeUtil.toString(configVo.getRecordName()))
.put("recordUrl", UnicodeUtil.toString(configVo.getRecordUrl()))
.put("description", UnicodeUtil.toString(configVo.getDescription()))
.put("email", UnicodeUtil.toString(configVo.getEmailUsername()))
.put("projectName", UnicodeUtil.toString(configVo.getProjectName()))
.put("projectUrl", UnicodeUtil.toString(configVo.getProjectUrl())).map()
);
}

View File

@ -1,5 +1,6 @@
package top.hcode.hoj.utils;
import cn.hutool.core.text.UnicodeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.hcode.hoj.pojo.vo.ConfigVo;
@ -52,18 +53,18 @@ public class ConfigUtils {
" port: " + configVo.getRedisPort() + "\n" +
" password: " + configVo.getRedisPassword() + "\n" +
" web-config:\n" +
" base-url: " + configVo.getBaseUrl() + "\n" +
" name: " + configVo.getName() + "\n" +
" short-name: " + configVo.getShortName() + "\n" +
" description: " + configVo.getDescription() + "\n" +
" base-url: " + UnicodeUtil.toUnicode(configVo.getBaseUrl()) + "\n" +
" name: " + UnicodeUtil.toUnicode(configVo.getName()) + "\n" +
" short-name: " + UnicodeUtil.toUnicode(configVo.getShortName()) + "\n" +
" description: " + UnicodeUtil.toUnicode(configVo.getDescription(),true) + "\n" +
" register: " + configVo.getRegister() + "\n" +
" footer:\n" +
" record:\n" +
" name: " + configVo.getRecordName() + "\n" +
" url: " + configVo.getRecordUrl() + "\n" +
" name: " + UnicodeUtil.toUnicode(configVo.getRecordName()) + "\n" +
" url: " + UnicodeUtil.toUnicode(configVo.getRecordUrl()) + "\n" +
" project:\n" +
" name: " + configVo.getProjectName() + "\n" +
" url: " + configVo.getProjectUrl() + "\n" +
" name: " + UnicodeUtil.toUnicode(configVo.getProjectName()) + "\n" +
" url: " + UnicodeUtil.toUnicode(configVo.getProjectUrl()) + "\n" +
" hdu:\n" +
" account:\n" +
" username: " + listToStr(configVo.getHduUsernameList()) + "\n" +

View File

@ -13,9 +13,8 @@
<el-row>
<el-col :md="6" :xs="24">
<h1>{{ toUpper(websiteConfig.shortName) }}</h1>
<p style="line-height:25px">
{{ websiteConfig.description }}
</p>
<span style="line-height:25px" v-html="websiteConfig.description">
</span>
</el-col>
<el-col class="hr-none">
<el-divider></el-divider>

View File

@ -83,7 +83,7 @@ function downloadFile (url) {
document.body.appendChild(link)
link.click()
link.remove()
myMessage.success("Request success, Downloading...")
myMessage.success("Downloading...")
resolve()
}).catch((error) => {
reject(error)

View File

@ -2,7 +2,7 @@
<div style="text-align:center">
<vxe-input
v-model="keyword"
placeholder="Enter keyword"
:placeholder="$t('m.Enter_keyword')"
type="search"
size="medium"
@search-click="filterByKeyword"
@ -18,15 +18,11 @@
>
<vxe-table-column title="ID" min-width="100" field="problemId">
</vxe-table-column>
<vxe-table-column min-width="150" title="Title" field="title">
<vxe-table-column min-width="150" :title="$t('m.Title')" field="title">
</vxe-table-column>
<vxe-table-column title="option" align="center" min-width="100">
<vxe-table-column :title="$t('m.Option')" align="center" min-width="100">
<template v-slot="{ row }">
<el-tooltip
effect="dark"
content="添加该题目到比赛中"
placement="top"
>
<el-tooltip effect="dark" :content="$t('m.Add')" placement="top">
<el-button
icon="el-icon-plus"
size="mini"
@ -97,7 +93,10 @@ export default {
});
},
handleAddProblem(problemID) {
this.$prompt('请输入该题目在比赛中显示的序号ID', '确认').then(
this.$prompt(
this.$i18n.t('m.Enter_The_Problem_Display_ID_in_the_Contest'),
'Tips'
).then(
({ value }) => {
let data = {
pid: problemID,
@ -107,7 +106,7 @@ export default {
api.admin_addProblemFromPublic(data).then(
(res) => {
this.$emit('on-change');
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Add_Successfully'));
this.getPublicProblem(this.page);
},
() => {}

View File

@ -12,7 +12,7 @@
<template v-slot:left-toolbar-after v-if="isAdminRole">
<button
type="button"
title="文件上传"
:title="$t('m.Upload_file')"
class="op-icon fa markdown-upload"
aria-hidden="true"
@click="uploadFile"

View File

@ -1,96 +0,0 @@
<template>
<div class="panel" :class="{'small': small}">
<header>
<div class="title">
<template v-if="$slots.title">
<slot name="title"></slot>
</template>
<template v-else>
{{title}}
</template>
</div>
<div class="header_right">
<slot name="header"></slot>
</div>
</header>
<div class="body">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Panel',
props: {
title: {
type: String,
required: false
},
small: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped lang="less">
.panel {
margin-bottom: 20px;
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
&.small {
max-width: 830px;
min-width: 700px;
margin-left: 20px;
margin-top: 10px;
}
header {
position: relative;
z-index: 10;
> .title {
margin: 0;
color: #333;
border-color: #ddd;
font-size: 18px;
font-weight: 300;
letter-spacing: 0.025em;
height: 60px;
line-height: 45px;
padding: 10px 15px;
border-bottom: 1px solid #eee;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
> .header_right {
position: absolute;
top: 50%;
right: 20px;
transform: translate(0, -50%);
}
}
.body {
padding: 15px;
}
}
</style>
<style lang="less">
.panel-options {
background-color: transparent;
position: relative;
height: 50px;
button {
margin-top: 18px;
margin-right: 10px;
}
> .page {
position: absolute;
right:20px;
top: 20px;
}
}
</style>

View File

@ -104,7 +104,7 @@ export default {
username: [
{
required: true,
message: this.$i18n.t('m.Username_Check'),
message: this.$i18n.t('m.Username_Check_Required'),
trigger: 'blur',
},
{

View File

@ -51,7 +51,7 @@ export default {
api.checkUsernameOrEmail(undefined, value).then(
(res) => {
if (res.data.data.email === false) {
callback(new Error('The email does not exist'));
callback(new Error(this.$i18n.t('m.The_email_does_not_exists')));
} else {
callback();
}
@ -73,7 +73,7 @@ export default {
captcha: [
{
required: true,
message: 'The captcha is required',
message: this.$i18n.t('m.Code_Check_Required'),
trigger: 'blur',
min: 1,
max: 8,
@ -82,7 +82,7 @@ export default {
email: [
{
required: true,
message: 'The email is required',
message: this.$i18n.t('m.Email_Check_Required'),
type: 'email',
trigger: 'blur',
},

View File

@ -0,0 +1,246 @@
export const m = {
// /views/admin/Login.vue
Welcome_to_Login_Admin: 'Welcome to Login Background Management System',
Login: 'Login',
Please_enter_username: 'Please enter username',
Please_enter_password: 'Please enter password',
Admin_Login_Success:'Dear administrator, welcome back~',
Please_check_your_username_or_password:'Please check your username or password',
// /views/admin/Home.vue
Dashboard: 'Dashboard',
General: 'General',
User_Admin: 'User Admin',
Announcement: 'Announcement',
System_Config: 'System Config',
Problem: 'Problem',
Problem_List: 'Problem List',
Create_Problem: 'Create Problem',
Export_Import_Problem: 'Export | Import Problem',
Contest: 'Contest',
Contest_List: 'Contest List',
Create_Contest: 'Create Contest',
Discussion:'Discussion',
Discussion_Admin:'Discussion Admin',
Home_Page:'Home Page',
Logout:'Logout',
// /views/admin/Dashboard.vue
Last_Login: 'Last Login',
Super_Admin:'Super Admin',
Admin:'Admin',
Total_Users:'Total Users',
Today_Submissions:'Today Submissions',
Recent_14_Days_Contests:'Recent 14 Days Contests',
Backend_System:'Backend System',
Server_Number:'Server Number',
Nacos_Status:'Nacos Status',
HTTPS_Status: 'HTTPS Status',
Backend_Service: 'Backend Service',
Name:'Name',
Host:'Host',
Port:'Port',
CPU_Core:'CPU Core',
CPU_Usage:'CPU Usage',
Mem_Usage:'Mem Usage',
Healthy:'Healthy',
Secure:'Secure',
Healthy_Status:'Healthy',
Unhealthy:'Unhealthy',
Judge_Server:'Judge Server',
// /views/admin/general/User.vue
General_User: 'User',
Delete:'Delete',
OnlyAdmin:'OnlyAdmin',
User_Type: 'User Type',
Normal:'Normal',
Disable:'Disable',
Edit_User:'Edit User',
Delete_User:'Delete User',
Import_User: 'Import User',
Import_User_Tips1:'The imported user data only supports user data in CSV format.',
Import_User_Tips2:'There are three columns of data: user name, password, and mailbox. Any column cannot be empty, otherwise the data in this row may fail to be imported.',
Import_User_Tips3:'The first line does not need to write the three column names ("username", "password", "email").',
Import_User_Tips4:'Please import the file saved as UTF-8 code, otherwise Chinese may be garbled.',
Choose_File:'Choose File',
Password: 'Password',
Upload_All:'Upload All',
Clear_All:'Clear All',
Generate_User: 'Generate User',
Prefix:'Prefix',
Suffix:'Suffix',
Start_Number:'Start Number',
End_Number:'End Number',
Password_Length:'Password Length',
Generate_and_Export:'Generate & Export',
The_usernames_will_be:'The usernames will be',
Set_New_PWD:'Set New PWD',
General_New_Password: 'New PWD',
The_end_number_cannot_be_less_than_the_start_number:'The end number cannot be less than the start number',
Please_select_6_to_25_characters_for_password_length:'Please select 6 ~ 25 characters for password length',
Start_Number_Required:'The Start Number is required.',
End_Number_Required:'The End Number is required.',
Password_Length_Checked:'Password length must be numeric',
Delete_User_Tips:'Are you sure you want to delete this user? May be associated to delete the user created announcements, topics, competitions, etc.',
The_number_of_users_selected_cannot_be_empty:'The number of users selected cannot be empty',
Error_Please_check_your_choice:'Wrong, please check your choice.',
Generate_User_Success:'All users in the specified format have been created successfully, and the user table has been downloaded to your computer successfully!',
Generate_Skipped_Reason:'rows user data are filtered because it may be an empty row or a column value is empty.',
Upload_Users_Successfully:'Upload Users Successfully',
// /views/admin/general/Announcement.vue
General_Announcement: 'Announcement',
Create:'Create',
Modified_Time:'Modified Time',
Edit_Announcement:'Edit Announcement',
Create_Announcement:'Create Announcement',
Delete_Announcement:'Delete Announcement',
Announcement_Title: 'Title',
Announcement_Content: 'Content',
Announcement_visible: 'Visible',
Delete_Announcement_Tips:'Are you sure you want to delete this announcement?',
// /views/admin/general/SystemConfig.vue
Website_Config:'Website Config',
Base_Url: 'Base Url',
Web_Name: 'Web Name',
Short_Name: 'Short Name',
Record_Name:'Record Name',
Record_Url:'Record Url',
Project_Name:'Project Name',
Project_Url:'Project Url',
Web_Desc: 'Web Desc',
Allow_Register: 'Allow Register',
SMTP_Config: 'SMTP Config',
Email_BG:'BG IMG',
Email_BG_Desc:'SMTP Template Background IMG Address',
Send_Test_Email:'Send Test Email',
Email: 'Email',
DataSource_Config:'DataSource Config',
Please_input_your_email:'Please input your email',
// /views/admin/problem/ProblemList.vue
Contest_Problem_List: 'Contest Problem List',
Add_Rmote_OJ_Problem:'Add Remote OJ Problem',
Add_From_Public_Problem:'Add From Public Problem',
Auth:'Auth',
Public_Problem:'Public Problem',
Private_Problem:'Private Problem',
Contest_Problem:'Contest Problem',
Download_Testcase:'Download Testcase',
Add_Contest_Problem:'Add Contest Problem',
Remote_OJ:'Remote OJ',
Add:'Add',
Delete_Problem_Tips:'Are you sure you want to delete this problem? Note: the relevant submission data for this issue will also be deleted.',
Add_Successfully:'Add Successfully',
Download_Testcase_Success:'The testcase of this problem has been downloaded successfully!',
Enter_The_Problem_Display_ID_in_the_Contest:'Enter The Problem Display ID in the Contest',
// /views/admin/problem/Problem.vue
Problem_Display_ID: 'Problem Display ID',
Title: 'Title',
Contest_Display_Title:'Contest Display Title',
Contest_Display_ID: 'Contest Display ID',
Description: 'Description',
Input_Description: 'Input Description',
Output_Description: 'Output Description',
Time_Limit: 'Time Limit',
Memory_Limit: 'Memory Limit',
Stack_Limit:'Stack Limit',
Code_Shareable:'Code Shareable',
Languages: 'Languages',
Problem_Examples:'Problem Examples',
Problem_Examples_Desc:'Problem Examples: please do not have more than 2 problem examples. Problem examples are not included in the testcase.',
Problem_Example:'Example',
Example_Input:'Example Input',
Example_Output:'Example Output',
Add_Example: 'Add Example',
Special_Judge: 'Special Judge',
Special_Judge_Code: 'Special Judge Code',
Special_Judge_Tips1:'Why use special judge?',
Special_Judge_Tips2:'The output required by the problem may not be unique, and different results are allowed.',
Special_Judge_Tips3:'The output within a certain precision range is acceptable.',
Use_Special_Judge: 'Use Special Judge',
SPJ_language: 'SPJ language',
Compile: 'Compile',
Code_Template: 'Code Template',
Type: 'Type',
Judge_Samples:'Judge Samples',
Problem_Sample:'Sample',
Sample_Input:'Sample Input',
Sample_Output:'Sample Output',
Sample_Input_File:'Input File',
Sample_Output_File:'Output File',
Sample_Tips:'Sample: the data source of the judger to test the submission.',
Add_Sample: 'Add Sample',
Use_Upload_File:'Use Upload File',
Use_Manual_Input:'Use Manual Input',
Hint: 'Hint',
Source: 'Source',
Auto_Remove_the_Blank_at_the_End_of_Code:'Auto Remove the Blank at the End of Code',
Publish_the_Judging_Result_of_Test_Data:'Publish the Judging Result of Test Data',
Edit_Problem: 'Edit Problme',
Create_Problme: 'Create Problem',
Change_Judge_Method:'If you want to change the judging method, you need to upload the testcase again.',
Add_Tag_Error:'The tag has been added, please do not add it repeatedly!',
Upload_Testcase_Successfully:'Upload Testcase Successfully',
Upload_Testcase_Failed:'Upload Testcase Failed',
is_required:'is required!',
Score_must_be_greater_than_or_equal_to_0:'Score must be greater than or equal to 0!',
Score_must_be_an_integer:'Score must be an integer!',
Spj_Code:'Spj Code',
Spj_Code_not_Compile_Success:'Spj Code was not compiled successfully, please compile again!',
// /views/admin/problem/ImportAndExport.vue
Export_Problem:'Export Problem',
Export:'Export',
Import_Problem:'Import Problem',
Import_QDOJ_Problem:'Import QDOJ Problem',
Export_Problem_NULL_Tips:'The problem selected for export cannot be empty',
// /views/admin/contest/ContestList.vue
Visible:'Visible',
Info:'Info',
View_Contest_Problem_List:'View Contest Problem List',
View_Contest_Announcement_List:'View Contest Announcement List',
Download_Contest_AC_Submission:'Download Contest AC Submissions',
Exclude_admin_submissions:'Exclude admin submissions',
Delete_Contest_Tips:'This operation will delete the contest and its submission, discussion, announcement, record and other data. Do you want to continue?',
// /views/admin/contest/Contest.vue
Contest_Title: 'Contest Title',
Contest_Description: 'Contest Description',
Contest_Start_Time: 'Start Time',
Contest_End_Time: 'End Time',
Contest_Duration:'Contest Duration',
Contest_Rule_Type: 'Contest Rule Type',
Seal_Time_Rank:'Seal Time Rank',
Real_Time_Rank: 'Real Time Rank',
Seal_Rank_Time:'Seal Rank Time',
Contest_Auth: 'Contest Auth',
Contest_Password:'Contest Password',
Contest_Seal_Half_Hour:'Half an hour',
Contest_Seal_An_Hour:'An hour',
Contest_Seal_All_Hour:'All hours',
Edit_Contest:'Edit Contest',
Create_Contest:'Create Contest',
Contest_Duration_Check:'The duration of the contest cannot be less than or equal to zero!',
Contets_Time_Check:'The start time should be earlier than the end time!',
// /views/admin/discussion/Discussion.vue
Discussion_ID:'Discussion ID',
Top:'Top',
Discussion_Report:'Discussion Report',
Reporter:'Reporter',
Report_Time:'Report Time',
View_Report_content:'View Report Content',
View_Discussion:'View Discussion Detail',
Content:'Content',
Report_Content:'Report Content',
The_number_of_discussions_selected_cannot_be_empty:'The number of discussions selected cannot be empty',
}

View File

@ -0,0 +1,245 @@
export const m = {
// /views/admin/Login.vue
Welcome_to_Login_Admin: '欢迎登录后台管理系统',
Login: '登录',
Please_enter_username: '用户名',
Please_enter_password: '密码',
Admin_Login_Success:'尊敬的管理员,欢迎回来~',
Please_check_your_username_or_password:'请检查你的用户名或密码',
// /views/admin/Home.vue
Dashboard: '仪表盘',
General: '常用设置',
User_Admin: '用户管理',
Announcement: '公告管理',
System_Config: '系统配置',
Problem: '题目管理',
Problem_List: '题目列表',
Create_Problem: '增加题目',
Export_Import_Problem: '导入|导出题目',
Contest: '比赛管理',
Contest_List: '比赛列表',
Create_Contest: '创建比赛',
Discussion:'讨论管理',
Discussion_Admin:'讨论管理',
Home_Page:'主页',
Logout:'退出登录',
// /views/admin/Dashboard.vue
Last_Login: '最近登录',
Super_Admin:'超级管理员',
Admin:'管理员',
Total_Users:'总用户数',
Today_Submissions:'今日总交题数',
Recent_14_Days_Contests:'最近两周比赛',
Backend_System:'后端系统',
Server_Number:'服务器数量',
Nacos_Status:'Nacos 状态',
HTTPS_Status: 'HTTPS 状态',
Backend_Service: '后端服务',
Name:'名称',
Host:'主机',
Port:'端口',
CPU_Core:'CPU核心数',
CPU_Usage:'CPU使用率',
Mem_Usage:'内存使用率',
Secure:'不稳定',
Healthy_Status:'状态',
Healthy:'健康',
Unhealthy:'不健康',
Judge_Server:'判题服务器',
// /views/admin/general/User.vue
General_User: '用户管理',
Delete:'删除',
OnlyAdmin:'仅显示管理员',
User_Type: '用户角色',
Normal:'正常',
Disable:'封禁',
Edit_User:'编辑用户',
Delete_User:'删除用户',
Import_User: '导入用户',
Import_User_Tips1:'用户数据导入仅支持csv格式的用户数据。',
Import_User_Tips2:'共三列数据:用户名,密码,邮箱,任一列不能为空,否则该行数据可能导入失败。',
Import_User_Tips3:'第一行不必写(“用户名”,“密码”,“邮箱”)这三个列名',
Import_User_Tips4:'请导入保存为UTF-8编码的文件否则中文可能会乱码。',
Choose_File:'选择文件',
Password: '密码',
Upload_All:'上传全部',
Clear_All:'清除全部',
Generate_User: '生成用户',
Prefix:'前缀',
Suffix:'后缀',
Start_Number:'开始数字',
End_Number:'结束数字',
Password_Length:'密码长度',
Generate_and_Export:'生成 & 导出',
The_usernames_will_be:'生成的用户名将会是',
Set_New_PWD:'设置新密码',
General_New_Password: '新密码',
The_end_number_cannot_be_less_than_the_start_number:'结束数字不能小于开始数字',
Please_select_6_to_25_characters_for_password_length:'请输入6~25作为密码的长度',
Start_Number_Required:'开始数字不能为空',
End_Number_Required:'结束数字不能为空',
Password_Length_Checked:'密码长度必须是数字',
Delete_User_Tips:'你确定要删除该用户?可能会关联删除该用户创建的公告,题目,比赛等。',
The_number_of_users_selected_cannot_be_empty:'选择的用户不能为空',
Error_Please_check_your_choice:'错误,请检查你的输入或选择是否准确',
Generate_User_Success:'所有用户已经被成功创建, 用户的列表数据文件将下载到你的电脑里',
Generate_Skipped_Reason:'行用户数据被过滤,原因是可能为空行或某个列值为空',
Upload_Users_Successfully:'上传用户成功',
// /views/admin/general/Announcement.vue
General_Announcement: '公告管理',
Create:'创建',
Modified_Time:'修改时间',
Edit_Announcement:'编辑公告',
Create_Announcement:'创建公告',
Delete_Announcement:'删除公告',
Announcement_Title: '公告标题',
Announcement_Content: '公告内容',
Announcement_visible: '是否可见',
Delete_Announcement_Tips:'你确实是否删除该公告?',
// /views/admin/general/SystemConfig.vue
Website_Config:'网站设置',
Base_Url: '基础URL',
Web_Name: '网站名称',
Short_Name: '网站简称',
Record_Name:'备案名',
Record_Url:'备案地址',
Project_Name:'项目名',
Project_Url:'项目地址',
Web_Desc: '网站简介',
Allow_Register: '是否允许注册',
SMTP_Config: 'SMTP 设置',
Email_BG:'邮件背景',
Email_BG_Desc:'请输入邮件背景图的URL链接',
Send_Test_Email:'发送测试邮件',
Email: '邮箱',
DataSource_Config:'数据源设置',
Please_input_your_email:'请输入你的邮箱',
// /views/admin/problem/ProblemList.vue
Contest_Problem_List: '比赛题目列表',
Add_Rmote_OJ_Problem:'添加远程OJ题目',
Add_From_Public_Problem:'从公共题库添加题目',
Auth:'权限',
Public_Problem:'公开题目',
Private_Problem:'私有题目',
Contest_Problem:'比赛题目',
Download_Testcase:'下载评测数据',
Add_Contest_Problem:'添加比赛题目',
Remote_OJ:'远程OJ',
Add:'添加',
Delete_Problem_Tips:'确定要删除此问题吗?注意:该问题的相关提交数据也将被删除。',
Add_Successfully:'添加成功',
Download_Testcase_Success:'该题目的评测数据已经被成功下载!',
Enter_The_Problem_Display_ID_in_the_Contest:'请输入该题目在比赛中展示ID',
// /views/admin/problem/Problem.vue
Problem_Display_ID: '题目展示ID',
Title: '题目标题',
Contest_Display_Title:'比赛中的展示标题',
Contest_Display_ID: '比赛中的展示ID',
Description: '描述',
Input_Description: '输入描述',
Output_Description: '输出描述',
Time_Limit: '时间限制',
Memory_Limit: '内存限制',
Stack_Limit:'栈限制',
Code_Shareable:'代码是否可分享',
Languages: '语言列表',
Problem_Examples:'题面样例',
Problem_Examples_Desc:'题目样例请最好不要超过2个题目样例题面样例不纳入评测数据。',
Problem_Example:'样例',
Example_Input:'样例输入',
Example_Output:'样例输出',
Add_Example: '添加样例',
Special_Judge: '特殊判题',
Special_Judge_Code:'特殊判题代码',
Special_Judge_Tips1:'为什么要使用特殊判题?',
Special_Judge_Tips2:'题目要求的输出结果可能不唯一,允许不同结果存在。',
Special_Judge_Tips3:'题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以。例如题目要求保留几位小数,输出结果后几位小数不相同也是正确的。',
Use_Special_Judge: '使用特殊判题',
SPJ_language: 'SPJ语言',
Compile: '编译',
Code_Template: '代码模板',
Type: '类型',
Judge_Samples:'评测数据',
Problem_Sample:'测试用例',
Sample_Input:'用例输入',
Sample_Output:'用例输出',
Sample_Input_File:'输入文件名',
Sample_Output_File:'输出文件名',
Sample_Tips:'评测数据:判题机对该题目的相关提交进行评测的数据来源。',
Add_Sample: '添加用例',
Use_Upload_File:'使用上传文件',
Use_Manual_Input:'使用手动输入',
Hint: '提示',
Source: '来源',
Auto_Remove_the_Blank_at_the_End_of_Code:'自动去除代码末尾空白符',
Publish_the_Judging_Result_of_Test_Data:'公开评测点数据结果',
Edit_Problem: '编辑题目',
Create_Problme: '创建题目',
Change_Judge_Method:'如果你想改变该题目的判题方法,那么你需要重新上传测试数据。',
Add_Tag_Error:'不要添加已有的标签!',
Upload_Testcase_Successfully:'上传评测数据成功',
Upload_Testcase_Failed:'上传评测数据失败',
is_required:'不能为空!',
Score_must_be_greater_than_or_equal_to_0:'分数必须大于0',
Score_must_be_an_integer:'分数必须是整数!',
Spj_Code:'Spj代码',
Spj_Code_not_Compile_Success:'Spj代码没有编译成功请重新编译',
// /views/admin/problem/ImportAndExport.vue
Export_Problem:'导出题目',
Export:'导出',
Import_Problem:'导入题目',
Import_QDOJ_Problem:'导入QDOJ题目',
Export_Problem_NULL_Tips:'选择导出的题目不能为空',
// /views/admin/contest/ContestList.vue
Visible:'是否可见',
Info:'信息',
View_Contest_Problem_List:'查看比赛题目列表',
View_Contest_Announcement_List:'查看比赛公告列表',
Download_Contest_AC_Submission:'下载比赛通过的提交代码',
Exclude_admin_submissions:'排除管理员的提交',
Delete_Contest_Tips:'此操作将删除该比赛以及比赛的提交、讨论、公告、记录等数据, 是否继续?',
// /views/admin/contest/Contest.vue
Contest_Title: '比赛标题',
Contest_Description: '比赛描述',
Contest_Start_Time: '开始时间',
Contest_End_Time: '结束时间',
Contest_Duration:'比赛时长',
Contest_Rule_Type: '比赛赛制',
Seal_Time_Rank:'开启封榜',
Real_Time_Rank: '实时榜单',
Seal_Rank_Time:'封榜时间',
Contest_Auth: '比赛权限',
Contest_Password:'比赛密码',
Contest_Seal_Half_Hour:'比赛结束前半小时',
Contest_Seal_An_Hour:'比赛结束前一小时',
Contest_Seal_All_Hour:'比赛全程',
Edit_Contest:'编辑比赛',
Create_Contest:'创建比赛',
Contest_Duration_Check:'比赛时长不能小于0',
Contets_Time_Check:'开始时间应该早于结束时间',
// /views/admin/discussion/Discussion.vue
Discussion_ID:'讨论ID',
Top:'置顶',
Discussion_Report:'讨论举报',
Reporter:'举报者',
Report_Time:'举报时间',
View_Report_content:'查看举报内容',
View_Discussion:'查看讨论详情',
Content:'内容',
Report_Content:'举报内容',
The_number_of_discussions_selected_cannot_be_empty:'勾选的讨论不能为空',
}

View File

@ -1,9 +1,8 @@
import Vue from 'vue'
import store from "@/store"
import VueI18n from 'vue-i18n'
import elenUS from 'element-ui/lib/locale/lang/en'
import elzhCN from 'element-ui/lib/locale/lang/zh-CN'
import storage from '@/common/storage'
Vue.use(VueI18n)
const languages = [
@ -16,14 +15,14 @@ const messages = {}
for (let lang of languages) {
let locale = lang.value
let m = require(`./oj/${locale}`).m
// Object.assign(m, require(`./admin/${locale}`).m)
Object.assign(m, require(`./admin/${locale}`).m)
messages[locale] = Object.assign({m: m}, lang.el);
}
// load language packages
export default new VueI18n({
locale: store.getters.webLanguage,
locale: storage.get('Web_Language') || 'zh-CN',
messages: messages
})

View File

@ -65,7 +65,8 @@ export const m = {
Set_New_Password:'Set New Password',
Set_New_Password_Msg: 'Please Enter New Password',
Set_New_Password_Again_Msg: 'Please Enter New Password Again',
The_username_does_not_exists:'The username does not exists.',
The_username_does_not_exists:'The username does not exist.',
The_email_does_not_exists:'The email does not exist.',
Your_password_has_been_reset: 'Your password has been reset.',
// /components/oj/setting/Account.vue 账号信息管理页面
@ -193,18 +194,6 @@ export const m = {
Copied_failed:'Copied failed',
// 状态码表示的结果
Accepted: 'Accepted',
Time_Limit_Exceeded: 'Time Limit Exceeded',
Memory_Limit_Exceeded: 'Memory Limit Exceeded',
Runtime_Error: 'Runtime Error',
System_Error: 'System Error',
Pending: 'Pending',
Partial_Accepted: 'Partial Accepted',
Compile_Error: 'Compile Error',
// /views/oj/status/SubmissionList.vue
Mine:'Mine',
Time: 'Time',
@ -398,5 +387,7 @@ export const m = {
Load_More:'Load More',
Delete_Comment_Tips:'This operation will delete the comment and all its replies. Do you want to continue?',
Delete_Reply_Tips:'This operation will delete the reply. Do you want to continue?',
}

View File

@ -31,7 +31,7 @@ export const m = {
Login_No_Account: '没有账号?立即注册!',
Login_Forget_Password: '忘记密码',
Username_Check_Required:'用户名不能为空',
Username_Check_Max:'用户名长度超过255位',
Username_Check_Max:'用户名长度不能超过255位',
Password_Check_Required:'密码不能为空',
Password_Check_Between:'请输入长度为6~20位的密码',
Welcome_Back: '欢迎回来~',
@ -66,6 +66,7 @@ export const m = {
Set_New_Password_Msg: '请输入新密码',
Set_New_Password_Again_Msg: '请再次输入新密码',
The_username_does_not_exists:'用户名不存在',
The_email_does_not_exists:'邮箱不存在',
Your_password_has_been_reset: '您的密码已重置',
// /components/oj/setting/Account.vue 账号信息管理页面
@ -145,7 +146,7 @@ export const m = {
Score: '分数',
// /views/oj/problem/problemList.vue
Problem_List:'题列表',
Problem_List:'列表',
All:'全部',
My_OJ:'本OJ',
Level:'难度',
@ -161,8 +162,8 @@ export const m = {
Statistic: '统计',
Solution:'提交记录',
Description: '题目描述',
Input: '输入格式',
Output: '输出格式',
Input: '输入描述',
Output: '输出描述',
Sample_Input: '样例输入',
Sample_Output: '样例输出',
Hint: '说明',
@ -189,7 +190,7 @@ export const m = {
You_have_submission_in_this_problem_sure_to_cover_it: '您已经提交过该问题的代码,确定重新提交?',
Close:'关闭',
Cancel:'取消',
OK:'OK',
OK:'确定',
Copied_successfully:'复制成功',
Copied_failed:'复制失败',
@ -222,7 +223,7 @@ export const m = {
// /views/oj/rank/ACMRank.vue
ACM_Ranklist: 'ACM 排行榜',
User:'用户',
User:'用户',
Nickname:'昵称',
Mood: '格言',
Rating: '通过率',
@ -388,6 +389,4 @@ export const m = {
Load_More:'加载更多',
Delete_Comment_Tips:'此操作将删除该评论及其所有回复, 是否继续?',
Delete_Reply_Tips:'此操作将删除该回复, 是否继续?',
}

View File

@ -1,17 +1,17 @@
// 引入 view 组件
import Login from '@/views/admin/Login'
import Home from '@/views/admin/Home'
import Dashboard from '@/views/admin/Dashboard'
import User from '@/views/admin/general/User'
import Announcement from '@/views/admin/general/Announcement'
import SystemConfig from '@/views/admin/general/SystemConfig'
import ProblemList from '@/views/admin/problem/ProblemList'
import Problem from '@/views/admin/problem/Problem'
import ProblemImportAndExport from '@/views/admin/problem/ImportAndExport'
import Contest from '@/views/admin/contest/Contest'
import ContestList from '@/views/admin/contest/ContestList'
import DiscussionList from '@/views/admin/discussion/Discussion'
const Login= ()=>import('@/views/admin/Login')
const Home= ()=>import('@/views/admin/Home')
const Dashboard= ()=>import('@/views/admin/Dashboard')
const User= ()=>import('@/views/admin/general/User')
const Announcement= ()=>import('@/views/admin/general/Announcement')
const SystemConfig= ()=>import('@/views/admin/general/SystemConfig')
const ProblemList= ()=>import('@/views/admin/problem/ProblemList')
const Problem= ()=>import('@/views/admin/problem/Problem')
const ProblemImportAndExport= ()=>import('@/views/admin/problem/ImportAndExport')
const Contest= ()=>import('@/views/admin/contest/Contest')
const ContestList= ()=>import('@/views/admin/contest/ContestList')
const DiscussionList= ()=>import('@/views/admin/discussion/Discussion')
const adminRoutes= [
{
path: '/admin/login',
@ -40,7 +40,7 @@ const adminRoutes= [
path: 'user',
name: 'admin-user',
component: User,
meta: { requireSuperAdmin: true,title:'User'},
meta: { requireSuperAdmin: true,title:'User Admin'},
},
{
path: 'announcement',
@ -76,7 +76,7 @@ const adminRoutes= [
path: 'problem/batch-operation',
name: 'admin-problem_batch_operation',
component: ProblemImportAndExport,
meta: { title:'Problem Operation'},
meta: { title:'Export Import_Problem'},
},
{
path: 'contest/create',
@ -124,7 +124,7 @@ const adminRoutes= [
path: 'discussion',
name: 'admin-discussion-list',
component: DiscussionList,
meta: { title:'Discussion List'}
meta: { title:'Discussion Admin'}
},
]
},

View File

@ -61,7 +61,7 @@ router.beforeEach((to, from, next) => {
})
store.commit('changeModalStatus',{mode: 'Login', visible: true})
}
mMessage.error('对不起!您并非超级管理员,您无权操作,请重新登陆!')
mMessage.error('ErrorPlease Login Again!')
store.commit("clearUserInfoAndToken");
}
}else if(to.matched.some(record => record.meta.requireAdmin)){ //判断是否需要管理员权限
@ -78,7 +78,7 @@ router.beforeEach((to, from, next) => {
})
store.commit('changeModalStatus',{mode: 'Login', visible: true})
}
mMessage.error('对不起!您并非管理员,您无权操作,请重新登录!')
mMessage.error('ErrorPlease Login Again!')
store.commit("clearUserInfoAndToken");
}
}else{
@ -98,7 +98,7 @@ router.beforeEach((to, from, next) => {
store.commit('changeModalStatus',{mode: 'Login', visible: true})
}
store.commit("clearUserInfoAndToken");
mMessage.error('请您先登录!')
mMessage.error('Please Login First!')
}
} else { // 不需要认证的页面
next()

View File

@ -1,27 +1,29 @@
import Home from '@/views/oj/Home.vue'
import SetNewPassword from "@/views/oj/user/SetNewPassword.vue"
import UserHome from "@/views/oj/user/UserHome.vue"
import Setting from "@/views/oj/user/Setting.vue"
import ProblemLIst from "@/views/oj/problem/ProblemList.vue"
import Logout from "@/views/oj/user/Logout.vue"
import SubmissionList from "@/views/oj/status/SubmissionList.vue"
import SubmissionDetails from "@/views/oj/status/SubmissionDetails.vue"
import ContestList from "@/views/oj/contest/ContestList.vue"
import Problem from "@/views/oj/problem/Problem.vue"
import ACMRank from "@/views/oj/rank/ACMRank.vue"
import OIRank from "@/views/oj/rank/OIRank.vue"
import ContestDetails from "@/views/oj/contest/ContestDetails.vue"
import ContestProblemList from "@/views/oj/contest/children/ContestProblemList.vue"
import ContestRank from "@/views/oj/contest/children/ContestRank.vue"
import ACMInfoAdmin from "@/views/oj/contest/children/ACMInfoAdmin.vue"
import Announcements from "@/components/oj/common/Announcements.vue"
import ContestComment from "@/views/oj/contest/children/ContestComment.vue"
import ContestRejudgeAdmin from "@/views/oj/contest/children/ContestRejudgeAdmin.vue"
import DiscussionList from "@/views/oj/discussion/discussionList.vue"
import Discussion from "@/views/oj/discussion/discussion.vue"
import Introduction from "@/views/oj/about/Introduction.vue"
import Developer from "@/views/oj/about/Developer.vue"
import NotFound from "@/views/404.vue"
const Home= ()=>import('@/views/oj/Home.vue')
const SetNewPassword= ()=>import("@/views/oj/user/SetNewPassword.vue")
const UserHome= ()=>import("@/views/oj/user/UserHome.vue")
const Setting= ()=>import("@/views/oj/user/Setting.vue")
const ProblemLIst= ()=>import("@/views/oj/problem/ProblemList.vue")
const Logout= ()=>import("@/views/oj/user/Logout.vue")
const SubmissionList= ()=>import("@/views/oj/status/SubmissionList.vue")
const SubmissionDetails= ()=>import("@/views/oj/status/SubmissionDetails.vue")
const ContestList= ()=>import("@/views/oj/contest/ContestList.vue")
const Problem= ()=>import("@/views/oj/problem/Problem.vue")
const ACMRank= ()=>import("@/views/oj/rank/ACMRank.vue")
const OIRank= ()=>import("@/views/oj/rank/OIRank.vue")
const ContestDetails= ()=>import("@/views/oj/contest/ContestDetails.vue")
const ContestProblemList= ()=>import("@/views/oj/contest/children/ContestProblemList.vue")
const ContestRank= ()=>import("@/views/oj/contest/children/ContestRank.vue")
const ACMInfoAdmin= ()=>import("@/views/oj/contest/children/ACMInfoAdmin.vue")
const Announcements= ()=>import("@/components/oj/common/Announcements.vue")
const ContestComment= ()=>import("@/views/oj/contest/children/ContestComment.vue")
const ContestRejudgeAdmin= ()=>import("@/views/oj/contest/children/ContestRejudgeAdmin.vue")
const DiscussionList= ()=>import("@/views/oj/discussion/discussionList.vue")
const Discussion= ()=>import("@/views/oj/discussion/discussion.vue")
const Introduction= ()=>import("@/views/oj/about/Introduction.vue")
const Developer= ()=>import("@/views/oj/about/Developer.vue")
const NotFound= ()=>import("@/views/404.vue")
const ojRoutes = [
{
path: '/',

View File

@ -12,12 +12,16 @@
<span class="panel-title admin-info-name">{{
userInfo.username
}}</span>
<p>{{ isSuperAdmin == true ? '超级管理员' : '管理员' }}</p>
<p>
{{
isSuperAdmin == true ? $t('m.Super_Admin') : $t('m.Admin')
}}
</p>
</el-col>
</el-row>
</div>
<div class="last-info">
<p class="last-info-title home-title">Last Login</p>
<p class="last-info-title home-title">{{ $t('m.Last_Login') }}</p>
<el-form label-width="80px" class="last-info-body">
<el-form-item label="Time:">
<span>{{ session.gmtCreate | localtime }}</span>
@ -42,7 +46,7 @@
<info-card
color="#909399"
icon="fa fa-users"
message="Total Users"
:message="$t('m.Total_Users')"
iconSize="30px"
class="info-item"
:value="infoData.userNum"
@ -50,14 +54,14 @@
<info-card
color="#67C23A"
icon="fa fa-list"
message="Today Submissions"
:message="$t('m.Today_Submissions')"
class="info-item"
:value="infoData.todayJudgeNum"
></info-card>
<info-card
color="#409EFF"
icon="fa fa-trophy"
message="Recent 14 Days Contests"
:message="$t('m.Recent_14_Days_Contests')"
class="info-item"
:value="infoData.recentContestNum"
></info-card>
@ -65,12 +69,14 @@
<!-- <el-card title="System_Overview" v-if="isSuperAdmin"> -->
<el-card>
<div slot="header">
<span class="panel-title home-title">General System</span>
<span class="panel-title home-title">{{
$t('m.Backend_System')
}}</span>
</div>
<el-row>
<el-col :xs="24" :md="8">
<span
>HOJ Server Num
>{{ $t('m.Server_Number') }}
<el-tag effect="dark" color="#2d8cf0" size="mini">{{
generalInfo.backupService.length
}}</el-tag>
@ -78,7 +84,7 @@
</el-col>
<el-col :xs="24" :md="8">
<span
>Nacos Status
>{{ $t('m.Nacos_Status') }}
<el-tag
effect="dark"
color="#19be6b"
@ -93,7 +99,7 @@
</el-col>
<el-col :xs="24" :md="8">
<span
>Https Status
>{{ $t('m.HTTPS_Status') }}
<el-tag
:type="https ? 'success' : 'danger'"
size="small"
@ -104,49 +110,53 @@
</span>
</el-col>
</el-row>
<h2 class="home-title">Backup Service</h2>
<h2 class="home-title">{{ $t('m.Backend_Service') }}</h2>
<vxe-table
stripe
auto-resize
:data="generalInfo.backupService"
align="center"
>
<vxe-table-column title="Name" min-width="130">
<vxe-table-column :title="$t('m.Name')" min-width="130">
<template v-slot="{ row }">
<span>{{ row['serviceId'] }}</span>
</template>
</vxe-table-column>
<vxe-table-column
field="host"
title="Host"
:title="$t('m.Host')"
min-width="110"
></vxe-table-column>
<vxe-table-column
field="port"
title="Port"
:title="$t('m.Port')"
min-width="80"
></vxe-table-column>
<vxe-table-column
min-width="80"
field="backupCores"
title="CPU Core"
:title="$t('m.CPU_Core')"
>
</vxe-table-column>
<vxe-table-column
min-width="100"
field="backupPercentCpuLoad"
title="CPU Usage"
:title="$t('m.CPU_Usage')"
>
</vxe-table-column>
<vxe-table-column
min-width="100"
field="backupPercentMemoryLoad"
title="Mem Usage"
:title="$t('m.Mem_Usage')"
>
</vxe-table-column>
<vxe-table-column field="secure" title="Secure" min-width="80">
<vxe-table-column
field="secure"
:title="$t('m.Secure')"
min-width="80"
>
<template v-slot="{ row }">
<el-tooltip content="是否触发保护阈值" placement="top">
<el-tag effect="dark" color="#ed3f14" v-if="row.secure"
@ -156,15 +166,17 @@
</el-tooltip>
</template>
</vxe-table-column>
<vxe-table-column title="Healthy" min-width="100">
<vxe-table-column :title="$t('m.Healthy_Status')" min-width="100">
<template v-slot="{ row }">
<el-tag
effect="dark"
color="#19be6b"
v-if="row.metadata['nacos.healthy'] == 'true'"
>Healthy</el-tag
>{{ $t('m.Healthy') }}</el-tag
>
<el-tag effect="dark" color="#f90" v-else>Unhealthy</el-tag>
<el-tag effect="dark" color="#f90" v-else>{{
$t('m.Unhealthy')
}}</el-tag>
</template>
</vxe-table-column>
</vxe-table>
@ -174,44 +186,48 @@
<el-card style="margin-top:10px">
<div slot="header">
<span class="panel-title home-title">Judger Service</span>
<span class="panel-title home-title">{{ $t('m.Judge_Server') }}</span>
</div>
<vxe-table stripe auto-resize :data="judgeInfo" align="center">
<vxe-table-column type="seq" width="50"></vxe-table-column>
<vxe-table-column title="Name" min-width="150">
<vxe-table-column :title="$t('m.Name')" min-width="150">
<template v-slot="{ row }">
<span>{{ row.service['serviceId'] }}</span>
</template>
</vxe-table-column>
<vxe-table-column title="Host" min-width="80">
<vxe-table-column :title="$t('m.Host')" min-width="80">
<template v-slot="{ row }">
<span>{{ row.service.host }}</span>
</template>
</vxe-table-column>
<vxe-table-column title="Port" min-width="80">
<vxe-table-column :title="$t('m.Port')" min-width="80">
<template v-slot="{ row }">
<span>{{ row.service.port }}</span>
</template>
</vxe-table-column>
<vxe-table-column min-width="80" field="cpuCores" title="CPU Core">
<vxe-table-column
min-width="80"
field="cpuCores"
:title="$t('m.CPU_Core')"
>
</vxe-table-column>
<vxe-table-column
min-width="100"
field="percentCpuLoad"
title="CPU Usage"
:title="$t('m.CPU_Usage')"
>
</vxe-table-column>
<vxe-table-column
min-width="110"
field="percentMemoryLoad"
title="Memory Usage"
:title="$t('m.Mem_Usage')"
>
</vxe-table-column>
<vxe-table-column title="Secure" min-width="80">
<vxe-table-column :title="$t('m.Secure')" min-width="80">
<template v-slot="{ row }">
<el-tooltip content="是否触发保护阈值" placement="top">
<el-tag effect="dark" color="#ed3f14" v-if="row.service.secure"
@ -221,15 +237,17 @@
</el-tooltip>
</template>
</vxe-table-column>
<vxe-table-column title="Healthy" min-width="100">
<vxe-table-column :title="$t('m.Healthy_Status')" min-width="100">
<template v-slot="{ row }">
<el-tag
effect="dark"
color="#19be6b"
v-if="row.service.metadata['nacos.healthy'] == 'true'"
>Healthy</el-tag
>{{ $t('m.Healthy') }}</el-tag
>
<el-tag effect="dark" color="#f90" v-else>Unhealthy</el-tag>
<el-tag effect="dark" color="#f90" v-else>{{
$t('m.Unhealthy')
}}</el-tag>
</template>
</vxe-table-column>
</vxe-table>
@ -240,7 +258,7 @@
<script>
import { mapGetters } from 'vuex';
import browserDetector from 'browser-detect';
import InfoCard from '@/components/admin/infoCard.vue';
const InfoCard = () => import('@/components/admin/infoCard.vue');
import api from '@/common/api';
export default {

View File

@ -10,46 +10,61 @@
<img :src="imgUrl" alt="oj admin" />
</div>
<el-menu-item index="/admin/">
<i class="fa fa-tachometer" aria-hidden="true"></i>Dashboard
<i class="fa fa-tachometer fa-size" aria-hidden="true"></i
>{{ $t('m.Dashboard') }}
</el-menu-item>
<!-- <el-submenu v-if="isSuperAdmin" index="general"> -->
<el-submenu index="general" v-if="isSuperAdmin">
<template slot="title"><i class="el-icon-menu"></i>General</template>
<el-menu-item index="/admin/user">User</el-menu-item>
<el-menu-item index="/admin/announcement">Announcement</el-menu-item>
<el-menu-item index="/admin/conf">System Config</el-menu-item>
<template slot="title"
><i class="el-icon-menu"></i>{{ $t('m.General') }}</template
>
<el-menu-item index="/admin/user">{{
$t('m.User_Admin')
}}</el-menu-item>
<el-menu-item index="/admin/announcement">{{
$t('m.Announcement')
}}</el-menu-item>
<el-menu-item index="/admin/conf">{{
$t('m.System_Config')
}}</el-menu-item>
</el-submenu>
<!-- <el-submenu index="problem" v-if="hasProblemPermission"> -->
<el-submenu index="problem">
<template slot="title"
><i class="fa fa-bars" aria-hidden="true"></i>Problem</template
>
<el-menu-item index="/admin/problems">Problem List</el-menu-item>
<el-menu-item index="/admin/problem/create"
>Create Problem</el-menu-item
>
<el-menu-item index="/admin/problem/batch-operation"
>Export&Import Problem</el-menu-item
><i class="fa fa-bars fa-size" aria-hidden="true"></i
>{{ $t('m.Problem') }}</template
>
<el-menu-item index="/admin/problems">{{
$t('m.Problem_List')
}}</el-menu-item>
<el-menu-item index="/admin/problem/create">{{
$t('m.Create_Problem')
}}</el-menu-item>
<el-menu-item index="/admin/problem/batch-operation">{{
$t('m.Export_Import_Problem')
}}</el-menu-item>
</el-submenu>
<el-submenu index="contest">
<template slot="title"
><i class="fa fa-trophy" aria-hidden="true"></i>Contest</template
>
<el-menu-item index="/admin/contest">Contest List</el-menu-item>
<el-menu-item index="/admin/contest/create"
>Create Contest</el-menu-item
><i class="fa fa-trophy fa-size" aria-hidden="true"></i
>{{ $t('m.Contest') }}</template
>
<el-menu-item index="/admin/contest">{{
$t('m.Contest_List')
}}</el-menu-item>
<el-menu-item index="/admin/contest/create">{{
$t('m.Create_Contest')
}}</el-menu-item>
</el-submenu>
<el-submenu index="discussion">
<template slot="title"
><i class="fa fa-comments" aria-hidden="true"></i
>Discussion</template
>
<el-menu-item index="/admin/discussion"
>Discussion Admin</el-menu-item
><i class="fa fa-comments fa-size" aria-hidden="true"></i
>{{ $t('m.Discussion') }}</template
>
<el-menu-item index="/admin/discussion">{{
$t('m.Discussion_Admin')
}}</el-menu-item>
</el-submenu>
</el-menu>
<div id="header">
@ -57,17 +72,20 @@
<el-col :span="20">
<div class="breadcrumb-container">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/admin/' }"
>Home page</el-breadcrumb-item
>
<el-breadcrumb-item :to="{ path: '/admin/' }">{{
$t('m.Home_Page')
}}</el-breadcrumb-item>
<el-breadcrumb-item v-for="item in routeList" :key="item.path">
{{ item.meta.title }}
{{ $t('m.' + item.meta.title.replace(' ', '_')) }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</el-col>
<el-col :span="4" v-show="isAuthenticated">
<i class="fa fa-font katex-editor" @click="katexVisible = true"></i>
<i
class="fa fa-font katex-editor fa-size"
@click="katexVisible = true"
></i>
<avatar
:username="userInfo.username"
:inline="true"
@ -85,7 +103,9 @@
}}<i class="el-icon-caret-bottom el-icon--right"></i
></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout">Logout</el-dropdown-item>
<el-dropdown-item command="logout">{{
$t('m.Logout')
}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
@ -101,7 +121,7 @@
{{
websiteConfig.shortName
? websiteConfig.shortName.toUpperCase() + ' ADMIN'
: 'OJ ADMIN'
: 'ADMIN'
}}
<mu-menu slot="right" v-show="isAuthenticated">
<mu-button flat @click="katexVisible = true">
@ -119,7 +139,7 @@
<mu-list slot="content" @change="handleCommand">
<mu-list-item button value="logout">
<mu-list-item-content>
<mu-list-item-title>Logout</mu-list-item-title>
<mu-list-item-title>{{ $t('m.Logout') }}</mu-list-item-title>
</mu-list-item-content>
</mu-list-item>
</mu-list>
@ -139,7 +159,7 @@
<mu-list-item-action>
<mu-icon value="dashboard" size="24"></mu-icon>
</mu-list-item-action>
<mu-list-item-title>Dashboard</mu-list-item-title>
<mu-list-item-title>{{ $t('m.Dashboard') }}</mu-list-item-title>
</mu-list-item>
<mu-list-item
@ -153,7 +173,7 @@
<mu-list-item-action>
<mu-icon value="view_list"></mu-icon>
</mu-list-item-action>
<mu-list-item-title>General</mu-list-item-title>
<mu-list-item-title>{{ $t('m.General') }}</mu-list-item-title>
<mu-list-item-action>
<mu-icon
class="toggle-icon"
@ -169,7 +189,7 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>User</mu-list-item-title>
<mu-list-item-title>{{ $t('m.User_Admin') }}</mu-list-item-title>
</mu-list-item>
<mu-list-item
button
@ -179,7 +199,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Announcement</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Announcement')
}}</mu-list-item-title>
</mu-list-item>
<mu-list-item
button
@ -189,7 +211,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>System Config</mu-list-item-title>
<mu-list-item-title>{{
$t('m.System_Config')
}}</mu-list-item-title>
</mu-list-item>
</mu-list-item>
@ -203,7 +227,7 @@
<mu-list-item-action>
<mu-icon value="menu"></mu-icon>
</mu-list-item-action>
<mu-list-item-title>Problem</mu-list-item-title>
<mu-list-item-title>{{ $t('m.Problem') }}</mu-list-item-title>
<mu-list-item-action>
<mu-icon
class="toggle-icon"
@ -219,7 +243,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Problem List</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Problem_List')
}}</mu-list-item-title>
</mu-list-item>
<mu-list-item
button
@ -229,7 +255,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Create Problem</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Create_Problem')
}}</mu-list-item-title>
</mu-list-item>
<mu-list-item
button
@ -239,7 +267,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Export&Import Problem</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Export_Import_Problem')
}}</mu-list-item-title>
</mu-list-item>
</mu-list-item>
@ -251,9 +281,9 @@
@toggle-nested="openSideMenu = arguments[0] ? 'contest' : ''"
>
<mu-list-item-action>
<mu-icon value=":fa fa-trophy"></mu-icon>
<mu-icon value=":fa fa-trophy fa-size"></mu-icon>
</mu-list-item-action>
<mu-list-item-title>Contest</mu-list-item-title>
<mu-list-item-title>{{ $t('m.Contest') }}</mu-list-item-title>
<mu-list-item-action>
<mu-icon
class="toggle-icon"
@ -269,7 +299,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Contest List</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Contest_List')
}}</mu-list-item-title>
</mu-list-item>
<mu-list-item
button
@ -279,7 +311,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Create Contest</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Create_Contest')
}}</mu-list-item-title>
</mu-list-item>
</mu-list-item>
@ -291,9 +325,9 @@
@toggle-nested="openSideMenu = arguments[0] ? 'discussion' : ''"
>
<mu-list-item-action>
<mu-icon value=":fa fa-comments"></mu-icon>
<mu-icon value=":fa fa-comments fa-size"></mu-icon>
</mu-list-item-action>
<mu-list-item-title>Discussion</mu-list-item-title>
<mu-list-item-title>{{ $t('m.Discussion') }}</mu-list-item-title>
<mu-list-item-action>
<mu-icon
class="toggle-icon"
@ -309,7 +343,9 @@
@click="opendrawer = !opendrawer"
active-class="mobile-menu-active"
>
<mu-list-item-title>Discussion Admin</mu-list-item-title>
<mu-list-item-title>{{
$t('m.Discussion_Admin')
}}</mu-list-item-title>
</mu-list-item>
</mu-list-item>
</mu-list>
@ -319,6 +355,28 @@
<transition name="fadeInUp" mode="out-in">
<router-view></router-view>
</transition>
<div class="footer">
Powered by
<a
:href="websiteConfig.projectUrl"
style="color:#1E9FFF"
target="_blank"
>{{ websiteConfig.projectName }}</a
>
<span style="margin-left:10px">
<el-dropdown @command="changeLanguage" placement="top">
<span class="el-dropdown-link" style="font-size:14px">
<i class="fa fa-globe" aria-hidden="true">
{{ this.webLanguage == 'zh-CN' ? '简体中文' : 'English' }}</i
><i class="el-icon-arrow-up el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh-CN">简体中文</el-dropdown-item>
<el-dropdown-item command="en-US">English</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</div>
</div>
<el-dialog title="Latex Editor" :visible.sync="katexVisible" width="350px">
@ -329,7 +387,7 @@
<script>
import { mapGetters } from 'vuex';
import KatexEditor from '@/components/admin/KatexEditor.vue';
const KatexEditor = () => import('@/components/admin/KatexEditor.vue');
import api from '@/common/api';
import mMessage from '@/common/message';
import Avatar from 'vue-avatar';
@ -381,6 +439,9 @@ export default {
let matched = this.$route.matched.filter((item) => item.meta.title); //
this.routeList = matched;
},
changeLanguage(language) {
this.$store.commit('changeWebLanguage', { language: language });
},
},
computed: {
...mapGetters([
@ -388,6 +449,7 @@ export default {
'isSuperAdmin',
'isAuthenticated',
'websiteConfig',
'webLanguage',
]),
},
watch: {
@ -419,11 +481,12 @@ export default {
width: 110px;
height: 110px;
}
.fa {
margin-right: 5px;
width: 24px;
.fa-size {
text-align: center;
font-size: 18px;
vertical-align: middle;
margin-right: 5px;
width: 24px;
}
a {
background-color: transparent;
@ -462,6 +525,11 @@ img {
height: 50px;
background: #f9fafc;
}
.footer {
margin: 15px;
text-align: center;
font-size: small;
}
@media screen and (min-width: 1080px) {
.content-app {

View File

@ -1,23 +1,23 @@
<template>
<div>
<vue-particles
color="#dedede"
:particleOpacity="0.7"
:particlesNumber="80"
shapeType="circle"
:particleSize="4"
linesColor="#dedede"
:linesWidth="1"
:lineLinked="true"
:lineOpacity="0.4"
:linesDistance="150"
:moveSpeed="3"
:hoverEffect="true"
hoverMode="grab"
:clickEffect="true"
clickMode="push"
>
</vue-particles>
<div>
<vue-particles
color="#dedede"
:particleOpacity="0.7"
:particlesNumber="80"
shapeType="circle"
:particleSize="4"
linesColor="#dedede"
:linesWidth="1"
:lineLinked="true"
:lineOpacity="0.4"
:linesDistance="150"
:moveSpeed="3"
:hoverEffect="true"
hoverMode="grab"
:clickEffect="true"
clickMode="push"
>
</vue-particles>
<div class="form">
<el-form
:model="ruleForm2"
@ -27,13 +27,13 @@
label-width="0px"
class="demo-ruleForm login-container"
>
<h1 class="title">HOJ后台管理</h1>
<h1 class="title">{{ $t('m.Welcome_to_Login_Admin') }}</h1>
<el-form-item prop="username">
<el-input
type="text"
v-model="ruleForm2.username"
auto-complete="off"
placeholder="Please enter username"
:placeholder="$t('m.Please_enter_username')"
@keyup.enter.native="handleLogin"
></el-input>
</el-form-item>
@ -42,7 +42,7 @@
type="password"
v-model="ruleForm2.password"
auto-complete="off"
placeholder="Please enter password"
:placeholder="$t('m.Please_enter_password')"
@keyup.enter.native="handleLogin"
></el-input>
</el-form-item>
@ -52,28 +52,40 @@
style="width: 100%"
@click.native.prevent="handleLogin"
:loading="logining"
>GO
>{{ $t('m.Login') }}
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
import api from "@/common/api";
import mMessage from '@/common/message'
import api from '@/common/api';
import mMessage from '@/common/message';
export default {
data() {
return {
logining: false,
ruleForm2: {
username: "",
password: "",
username: '',
password: '',
},
rules2: {
username: [{ required: true, trigger: "blur" }],
password: [{ required: true, trigger: "blur" }],
username: [
{
required: true,
trigger: 'blur',
message: this.$i18n.t('m.Username_Check_Required'),
},
],
password: [
{
required: true,
trigger: 'blur',
message: this.$i18n.t('m.Password_Check_Required'),
},
],
},
checked: true,
};
@ -83,21 +95,25 @@ export default {
this.$refs.ruleForm2.validate((valid) => {
if (valid) {
this.logining = true;
api.admin_login(this.ruleForm2.username, this.ruleForm2.password).then(
(res) => {
this.logining = false;
const jwt = res.headers["authorization"];
this.$store.commit('changeUserToken',jwt);
this.$store.dispatch('setUserInfo',res.data.data);
mMessage.success("尊敬的管理员,欢迎回来~")
this.$router.push({ name: "admin-dashboard" });
},
() => {
this.logining = false;
}
);
api
.admin_login(this.ruleForm2.username, this.ruleForm2.password)
.then(
(res) => {
this.logining = false;
const jwt = res.headers['authorization'];
this.$store.commit('changeUserToken', jwt);
this.$store.dispatch('setUserInfo', res.data.data);
mMessage.success(this.$i18n.t('m.Admin_Login_Success'));
this.$router.push({ name: 'admin-dashboard' });
},
() => {
this.logining = false;
}
);
} else {
mMessage.error("请检查您的用户名和密码输入是否正确");
mMessage.error(
this.$i18n.t('m.Please_check_your_username_or_password')
);
}
});
},
@ -121,15 +137,15 @@ export default {
.login-container .title {
margin: 0px auto 40px auto;
text-align: center;
color: #1E9FFF;
color: #1e9fff;
font-size: 25px;
font-weight: bold;
}
.login-container .remember {
margin: 0px 0px 35px 0px;
}
.form{
position:relative;
z-index:9999;
.form {
position: relative;
z-index: 9999;
}
</style>

View File

@ -9,48 +9,48 @@
<el-form label-position="top">
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="Contest Title" required>
<el-form-item :label="$t('m.Contest_Title')" required>
<el-input
v-model="contest.title"
placeholder="Enter the Contest Title"
:placeholder="$t('m.Contest_Title')"
></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="Contest Description" required>
<el-form-item :label="$t('m.Contest_Description')" required>
<Editor :value.sync="contest.description"></Editor>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Contest Start Time" required>
<el-form-item :label="$t('m.Contest_Start_Time')" required>
<el-date-picker
v-model="contest.startTime"
@change="changeDuration"
type="datetime"
placeholder="Enter the Contest Start Time"
:placeholder="$t('m.Contest_Start_Time')"
>
</el-date-picker>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Contest End Time" required>
<el-form-item :label="$t('m.Contest_End_Time')" required>
<el-date-picker
v-model="contest.endTime"
@change="changeDuration"
type="datetime"
placeholder="Enter the Contest End Time"
:placeholder="$t('m.Contest_End_Time')"
>
</el-date-picker>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Contest Duration" required>
<el-form-item :label="$t('m.Contest_Duration')" required>
<el-input v-model="durationText" disabled> </el-input>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Contest Type">
<el-form-item :label="$t('m.Contest_Rule_Type')">
<el-radio
class="radio"
v-model="contest.type"
@ -69,7 +69,7 @@
</el-col>
<el-col :md="8" :xs="24" v-if="contest.sealRank">
<el-form-item label="Seal Time Rank">
<el-form-item :label="$t('m.Seal_Time_Rank')">
<el-switch
v-model="contest.sealRank"
active-color="#13ce66"
@ -80,7 +80,7 @@
</el-col>
<el-col :md="16" :xs="24" v-else>
<el-form-item label="Real Time Rank">
<el-form-item :label="$t('m.Real_Time_Rank')">
<el-switch
v-model="contest.sealRank"
active-color="#13ce66"
@ -92,44 +92,47 @@
<el-col :md="8" :xs="24">
<el-form-item
label="Seal Rank Time"
:label="$t('m.Seal_Rank_Time')"
:required="contest.sealRank"
v-show="contest.sealRank"
>
<el-select v-model="seal_rank_time">
<el-option
label="比赛结束前半小时"
:label="$t('m.Contest_Seal_Half_Hour')"
:value="0"
:disabled="contest.duration < 1800"
></el-option>
<el-option
label="比赛结束前一小时"
:label="$t('m.Contest_Seal_An_Hour')"
:value="1"
:disabled="contest.duration < 3600"
></el-option>
<el-option label="比赛全程时间" :value="2"></el-option>
<el-option
:label="$t('m.Contest_Seal_All_Hour')"
:value="2"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Contest Auth" required>
<el-form-item :label="$t('m.Contest_Auth')" required>
<el-select v-model="contest.auth">
<el-option label="公开赛" :value="0"></el-option>
<el-option label="私有赛" :value="1"></el-option>
<el-option label="保护赛" :value="2"></el-option>
<el-option :label="$t('m.Public')" :value="0"></el-option>
<el-option :label="$t('m.Private')" :value="1"></el-option>
<el-option :label="$t('m.Protected')" :value="2"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item
label="Contest Password"
:label="$t('m.Contest_Password')"
v-show="contest.auth != 0"
:required="contest.auth != 0"
>
<el-input
v-model="contest.pwd"
placeholder="Contest Password"
:placeholder="$t('m.Contest_Password')"
></el-input>
</el-form-item>
</el-col>
@ -150,18 +153,20 @@
</el-col> -->
</el-row>
</el-form>
<el-button type="primary" @click.native="saveContest">Save</el-button>
<el-button type="primary" @click.native="saveContest">{{
$t('m.Save')
}}</el-button>
</el-card>
</div>
</template>
<script>
import api from '@/common/api';
import Editor from '@/components/admin/Editor.vue';
import time from '@/common/time';
import moment from 'moment';
import { mapGetters } from 'vuex';
import myMessage from '@/common/message';
const Editor = () => import('@/components/admin/Editor.vue');
export default {
name: 'CreateContest',
components: {
@ -192,7 +197,7 @@ export default {
},
mounted() {
if (this.$route.name === 'admin-edit-contest') {
this.title = 'Edit Contest';
this.title = this.$i18n.t('m.Edit_Contest');
this.disableRuleType = true;
this.getContestByCid();
}
@ -200,11 +205,11 @@ export default {
watch: {
$route() {
if (this.$route.name === 'admin-edit-contest') {
this.title = 'Edit Contest';
this.title = this.$i18n.t('m.Edit_Contest');
this.disableRuleType = true;
this.getContestByCid();
} else {
this.title = 'Create Contest';
this.title = this.$i18n.t('m.Create_Contest');
this.disableRuleType = false;
this.contest = [];
}
@ -255,27 +260,45 @@ export default {
saveContest() {
if (!this.contest.title) {
myMessage.error('比赛的标题不能为空!');
myMessage.error(
this.$i18n.t('m.Contest_Title') + ' ' + this.$i18n.t('m.is_required')
);
return;
}
if (!this.contest.description) {
myMessage.error('比赛的描述不能为空!');
myMessage.error(
this.$i18n.t('m.Contest_Description') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
if (!this.contest.startTime) {
myMessage.error('比赛的开始时间不能为空!');
myMessage.error(
this.$i18n.t('m.Contest_Start_Time') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
if (!this.contest.endTime) {
myMessage.error('比赛的结束时间不能为空!');
myMessage.error(
this.$i18n.t('m.Contest_End_Time') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
if (!this.contest.duration || this.contest.duration <= 0) {
myMessage.error('比赛的时长不能小于或等于0');
myMessage.error(this.$i18n.t('m.Contest_Duration_Check'));
return;
}
if (this.contest.auth != 0 && !this.contest.pwd) {
myMessage.error('当前的比赛模式密码不能为空!');
myMessage.error(
this.$i18n.t('m.Contest_Password') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
@ -315,7 +338,7 @@ export default {
api[funcName](data)
.then((res) => {
myMessage.success(res.data.msg);
myMessage.success('success');
this.$router.push({
name: 'admin-contest-list',
query: { refresh: 'true' },
@ -328,7 +351,7 @@ export default {
let end = this.contest.endTime;
let durationMS = time.durationMs(start, end);
if (durationMS < 0) {
this.durationText = '比赛起始时间不应该晚于结束时间!';
this.durationText = this.$i18n.t('m.Contets_Time_Check');
this.contest.duration = 0;
return;
}

View File

@ -2,12 +2,12 @@
<div>
<el-card>
<div slot="header">
<span class="panel-title home-title">Contest List</span>
<span class="panel-title home-title">{{ $t('m.Contest_List') }}</span>
<div class="filter-row">
<span>
<vxe-input
v-model="keyword"
placeholder="Enter keyword"
:placeholder="$t('m.Enter_keyword')"
type="search"
size="medium"
@search-click="filterByKeyword"
@ -25,17 +25,17 @@
align="center"
>
<vxe-table-column field="id" width="80" title="ID"> </vxe-table-column>
<vxe-table-column field="title" min-width="150" title="Title">
<vxe-table-column field="title" min-width="150" :title="$t('m.Title')">
</vxe-table-column>
<vxe-table-column title="Type" width="100">
<vxe-table-column :title="$t('m.Type')" width="100">
<template v-slot="{ row }">
<el-tag type="gray">{{ row.type | parseContestType }}</el-tag>
</template>
</vxe-table-column>
<vxe-table-column title="Auth" width="100">
<vxe-table-column :title="$t('m.Auth')" width="100">
<template v-slot="{ row }">
<el-tooltip
:content="CONTEST_TYPE_REVERSE[row.auth].tips"
:content="$t('m.' + CONTEST_TYPE_REVERSE[row.auth].tips)"
placement="top"
effect="light"
>
@ -48,7 +48,7 @@
</el-tooltip>
</template>
</vxe-table-column>
<vxe-table-column title="Status" width="100">
<vxe-table-column :title="$t('m.Status')" width="100">
<template v-slot="{ row }">
<el-tag
effect="dark"
@ -59,7 +59,7 @@
</el-tag>
</template>
</vxe-table-column>
<vxe-table-column title="Status" min-width="80">
<vxe-table-column :title="$t('m.Visible')" min-width="80">
<template v-slot="{ row }">
<el-switch
v-model="row.visible"
@ -68,7 +68,7 @@
</el-switch>
</template>
</vxe-table-column>
<vxe-table-column min-width="210" title="More">
<vxe-table-column min-width="210" :title="$t('m.Info')">
<template v-slot="{ row }">
<p>Start Time: {{ row.startTime | localtime }}</p>
<p>End Time: {{ row.endTime | localtime }}</p>
@ -76,10 +76,10 @@
<p>Creator: {{ row.author }}</p>
</template>
</vxe-table-column>
<vxe-table-column min-width="150" title="Option">
<vxe-table-column min-width="150" :title="$t('m.Option')">
<template v-slot="{ row }">
<div style="margin-bottom:10px">
<el-tooltip effect="dark" content="编辑比赛" placement="top">
<el-tooltip effect="dark" :content="$t('m.Edit')" placement="top">
<el-button
icon="el-icon-edit"
size="mini"
@ -90,7 +90,7 @@
</el-tooltip>
<el-tooltip
effect="dark"
content="查看比赛题目列表"
:content="$t('m.View_Contest_Problem_List')"
placement="top"
>
<el-button
@ -105,7 +105,7 @@
<div style="margin-bottom:10px">
<el-tooltip
effect="dark"
content="查看比赛公告列表"
:content="$t('m.View_Contest_Announcement_List')"
placement="top"
>
<el-button
@ -119,7 +119,7 @@
<el-tooltip
effect="dark"
content="下载通过的提交代码"
:content="$t('m.Download_Contest_AC_Submission')"
placement="top"
>
<el-button
@ -133,7 +133,7 @@
</div>
<el-tooltip
effect="dark"
content="删除比赛"
:content="$t('m.Delete')"
placement="top"
v-if="isSuperAdmin"
>
@ -160,16 +160,18 @@
</div>
</el-card>
<el-dialog
title="Download Contest Submissions"
:title="$t('m.Download_Contest_AC_Submission')"
width="320px"
:visible.sync="downloadDialogVisible"
>
<el-switch
v-model="excludeAdmin"
active-text="Exclude admin submissions"
:active-text="$t('m.Exclude_admin_submissions')"
></el-switch>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="downloadSubmissions"> </el-button>
<el-button type="primary" @click="downloadSubmissions">{{
$t('m.OK')
}}</el-button>
</span>
</el-dialog>
</div>
@ -260,24 +262,20 @@ export default {
});
},
deleteContest(contestId) {
this.$confirm(
'此操作将删除该比赛以及比赛的提交、讨论、公告、记录等数据, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
this.$confirm(this.$i18n.t('m.Delete_Contest_Tips'), 'Tips', {
confirmButtonText: this.$i18n.t('m.OK'),
cancelButtonText: this.$i18n.t('m.Cancel'),
type: 'warning',
}).then(() => {
api.admin_deleteContest(contestId).then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Delete_successfully'));
this.currentChange(1);
});
});
},
changeContestVisible(contestId, visible) {
api.admin_changeContestVisible(contestId, visible).then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
});
},
filterByKeyword() {

View File

@ -2,7 +2,9 @@
<div>
<el-card>
<div slot="header">
<span class="panel-title home-title">Discussion List</span>
<span class="panel-title home-title">{{
$t('m.Discussion_Admin')
}}</span>
<div class="filter-row">
<span>
<el-button
@ -10,13 +12,13 @@
icon="el-icon-delete-solid"
@click="deleteDiscussion(null)"
size="small"
>Delete
>{{ $t('m.Delete') }}
</el-button>
</span>
<span>
<vxe-input
v-model="keyword"
placeholder="Enter keyword"
:placeholder="$t('m.Enter_keyword')"
type="search"
size="medium"
@search-click="filterByKeyword"
@ -30,6 +32,7 @@
auto-resize
:data="discussionList"
ref="xTable"
align="center"
:loading="discussionLoadingTable"
:checkbox-config="{ highlight: true, range: true }"
@checkbox-change="handleSelectionChange"
@ -39,42 +42,58 @@
<vxe-table-column field="id" title="ID" width="60"></vxe-table-column>
<vxe-table-column
field="title"
title="Title"
:title="$t('m.Title')"
min-width="150"
></vxe-table-column>
<vxe-table-column
field="author"
title="Author"
:title="$t('m.Author')"
min-width="150"
></vxe-table-column>
<vxe-table-column
field="likeNum"
title="Like Num"
:title="$t('m.Likes')"
min-width="96"
></vxe-table-column>
<vxe-table-column
field="viewNum"
title="View Num"
:title="$t('m.Views')"
min-width="96"
></vxe-table-column>
<vxe-table-column field="gmtCreate" title="Create Time" min-width="150">
<vxe-table-column
field="gmtCreate"
:title="$t('m.Created_Time')"
min-width="150"
>
<template v-slot="{ row }">
{{ row.gmtCreate | localtime }}
</template>
</vxe-table-column>
<vxe-table-column field="status" title="Status" min-width="100">
<vxe-table-column
field="status"
:title="$t('m.Status')"
min-width="100"
>
<template v-slot="{ row }">
<el-select
v-model="row.status"
@change="changeDiscussionStatus(row)"
size="small"
>
<el-option label="正常" :value="0" :key="0"></el-option
><el-option label="封禁" :value="1" :key="1"></el-option>
<el-option :label="$t('m.Normal')" :value="0" :key="0"></el-option
><el-option
:label="$t('m.Disable')"
:value="1"
:key="1"
></el-option>
</el-select>
</template>
</vxe-table-column>
<vxe-table-column min-width="100" field="topPriority" title="Top">
<vxe-table-column
min-width="100"
field="topPriority"
:title="$t('m.Top')"
>
<template v-slot="{ row }">
<el-switch
v-model="row.topPriority"
@ -87,9 +106,9 @@
</el-switch>
</template>
</vxe-table-column>
<vxe-table-column title="Option" min-width="130">
<vxe-table-column :title="$t('m.Option')" min-width="130">
<template v-slot="{ row }">
<el-tooltip effect="dark" content="删除讨论" placement="top">
<el-tooltip effect="dark" :content="$t('m.Delete')" placement="top">
<el-button
icon="el-icon-delete-solid"
size="mini"
@ -98,7 +117,11 @@
>
</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="查看讨论" placement="top">
<el-tooltip
effect="dark"
:content="$t('m.View_Discussion')"
placement="top"
>
<el-button
icon="el-icon-search"
size="mini"
@ -123,7 +146,9 @@
</el-card>
<el-card style="margin-top:20px">
<div slot="header">
<span class="panel-title home-title">Discussion Report</span>
<span class="panel-title home-title">{{
$t('m.Discussion_Report')
}}</span>
</div>
<vxe-table
:loading="discussionReportLoadingTable"
@ -135,20 +160,32 @@
>
<vxe-table-column min-width="60" field="id" title="ID">
</vxe-table-column>
<vxe-table-column min-width="150" field="did" title="Discussion ID">
<vxe-table-column
min-width="150"
field="did"
:title="$t('m.Discussion_ID')"
>
</vxe-table-column>
<vxe-table-column
min-width="150"
field="reporter"
title="Reporter Name"
:title="$t('m.Reporter')"
>
</vxe-table-column>
<vxe-table-column min-width="150" field="gmtCreate" title="Report Time">
<vxe-table-column
min-width="150"
field="gmtCreate"
:title="$t('m.Report_Time')"
>
<template v-slot="{ row }">
{{ row.gmtCreate | localtime }}
</template>
</vxe-table-column>
<vxe-table-column min-width="100" field="status" title="Checked">
<vxe-table-column
min-width="100"
field="status"
:title="$t('m.Checked')"
>
<template v-slot="{ row }">
<el-switch
v-model="row.status"
@ -161,12 +198,12 @@
</el-switch>
</template>
</vxe-table-column>
<vxe-table-column title="Content" min-width="150">
<vxe-table-column :title="$t('m.Option')" min-width="150">
<template v-slot="{ row }">
<el-tooltip
class="item"
effect="dark"
content="点击查看举报内容"
:content="$t('m.View_Report_content')"
placement="top"
>
<el-button
@ -176,7 +213,11 @@
type="success"
></el-button>
</el-tooltip>
<el-tooltip effect="dark" content="查看讨论" placement="top">
<el-tooltip
effect="dark"
:content="$t('m.View_Discussion')"
placement="top"
>
<el-button
icon="el-icon-search"
size="mini"
@ -296,7 +337,7 @@ export default {
status: row.status,
};
api.admin_updateDiscussion(discussion).then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
});
},
handleTopSwitch(row) {
@ -305,7 +346,7 @@ export default {
topPriority: row.topPriority,
};
api.admin_updateDiscussion(discussion).then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
});
},
@ -315,7 +356,7 @@ export default {
status: row.status,
};
api.admin_updateDiscussionReport(discussionReport).then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
});
},
@ -328,13 +369,9 @@ export default {
didList = this.selectedDiscussions;
}
if (didList.length > 0) {
this.$confirm(
'你确定要删除该讨论?这样会关联删除该讨论的评论及回复',
'确认',
{
type: 'warning',
}
).then(
this.$confirm(this.$i18n.t('m.Delete_Discussion_Tips'), 'Tips', {
type: 'warning',
}).then(
() => {
api
.admin_deleteDiscussion(didList)
@ -351,21 +388,26 @@ export default {
() => {}
);
} else {
myMessage.warning('勾选的讨论不能为空!');
myMessage.warning(
this.$i18n.t('m.The_number_of_discussions_selected_cannot_be_empty')
);
}
},
openReportDialog(content) {
let reg = '#(.*?)# ';
let re = RegExp(reg, 'g');
let tmp;
let showContent = '<strong>标签</strong>';
let showContent = '<strong>' + this.$i18n.t('m.Tags') + '</strong>';
while ((tmp = re.exec(content))) {
showContent += tmp[1] + ' ';
}
showContent +=
'<br><br><strong>内容</strong>' + content.replace(/#(.*?)# /g, '');
this.$alert(showContent, '举报内容', {
confirmButtonText: '确定',
'<br><br><strong>' +
this.$i18n.t('m.Content') +
'</strong>' +
content.replace(/#(.*?)# /g, '');
this.$alert(showContent, this.$i18n.t('m.Report_Content'), {
confirmButtonText: this.$i18n.t('m.OK'),
dangerouslyUseHTMLString: true,
});
},

View File

@ -2,7 +2,9 @@
<div>
<el-card>
<div slot="header">
<span class="panel-title home-title">Announcement</span>
<span class="panel-title home-title">{{
$t('m.General_Announcement')
}}</span>
</div>
<div class="create">
<el-button
@ -10,7 +12,7 @@
size="small"
@click="openAnnouncementDialog(null)"
icon="el-icon-plus"
>Create</el-button
>{{ $t('m.Create') }}</el-button
>
</div>
<div class="list">
@ -23,12 +25,16 @@
>
<vxe-table-column min-width="50" field="id" title="ID">
</vxe-table-column>
<vxe-table-column min-width="150" field="title" title="Title">
<vxe-table-column
min-width="150"
field="title"
:title="$t('m.Announcement_Title')"
>
</vxe-table-column>
<vxe-table-column
min-width="150"
field="gmtCreate"
title="Create Time"
:title="$t('m.Created_Time')"
>
<template v-slot="{ row }">
{{ row.gmtCreate | localtime }}
@ -37,15 +43,23 @@
<vxe-table-column
min-width="150"
field="gmtModified"
title="Last Update Time"
:title="$t('m.Modified_Time')"
>
<template v-slot="{ row }">
{{ row.gmtModified | localtime }}
</template>
</vxe-table-column>
<vxe-table-column min-width="150" field="username" title="Author">
<vxe-table-column
min-width="150"
field="username"
:title="$t('m.Author')"
>
</vxe-table-column>
<vxe-table-column min-width="100" field="status" title="Visible">
<vxe-table-column
min-width="100"
field="status"
:title="$t('m.Announcement_visible')"
>
<template v-slot="{ row }">
<el-switch
v-model="row.status"
@ -63,7 +77,7 @@
<el-tooltip
class="item"
effect="dark"
content="编辑公告"
:content="$t('m.Edit_Announcement')"
placement="top"
>
<el-button
@ -76,7 +90,7 @@
<el-tooltip
class="item"
effect="dark"
content="删除公告"
:content="$t('m.Delete_Announcement')"
placement="top"
>
<el-button
@ -112,19 +126,19 @@
@open="onOpenEditDialog"
>
<el-form label-position="top" :model="announcement">
<el-form-item label="公告标题" required>
<el-form-item :label="$t('m.Announcement_Title')" required>
<el-input
v-model="announcement.title"
placeholder="请输入公告标题"
:placeholder="$t('m.Announcement_Title')"
class="title-input"
>
</el-input>
</el-form-item>
<el-form-item label="公告内容" required>
<el-form-item :label="$t('m.Announcement_Content')" required>
<Editor :value.sync="announcement.content"></Editor>
</el-form-item>
<div class="visible-box">
<span>是否显示</span>
<span>{{ $t('m.Announcement_visible') }}</span>
<el-switch
v-model="announcement.status"
:active-value="0"
@ -139,21 +153,21 @@
<el-button
type="danger"
@click.native="showEditAnnouncementDialog = false"
>Cancel</el-button
>
<el-button type="primary" @click.native="submitAnnouncement"
>Save</el-button
>{{ $t('m.Cancel') }}</el-button
>
<el-button type="primary" @click.native="submitAnnouncement">{{
$t('m.OK')
}}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Editor from '@/components/admin/Editor.vue';
import api from '@/common/api';
import myMessage from '@/common/message';
import { mapGetters } from 'vuex';
const Editor = () => import('@/components/admin/Editor.vue');
export default {
name: 'announcement',
components: {
@ -278,7 +292,7 @@ export default {
api[funcName](requestData)
.then((res) => {
this.showEditAnnouncementDialog = false;
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Post_successfully'));
this.init();
})
.catch();
@ -286,9 +300,9 @@ export default {
//
deleteAnnouncement(announcementId) {
this.$confirm('你确定要删除该公告?', 'Warning', {
confirmButtonText: '确定',
cancelButtonText: '取消',
this.$confirm(this.$i18n.t('m.Delete_Announcement_Tips'), 'Warning', {
confirmButtonText: this.$i18n.t('m.OK'),
cancelButtonText: this.$i18n.t('m.Cancel'),
type: 'warning',
})
.then(() => {
@ -312,11 +326,11 @@ export default {
openAnnouncementDialog(row) {
this.showEditAnnouncementDialog = true;
if (row !== null) {
this.announcementDialogTitle = 'Edit Announcement';
this.announcementDialogTitle = this.$i18n.t('m.Edit_Announcement');
this.announcement = Object.assign({}, row);
this.mode = 'edit';
} else {
this.announcementDialogTitle = 'Create Announcement';
this.announcementDialogTitle = this.$i18n.t('m.Create_Announcement');
this.announcement.title = '';
this.announcement.status = 0;
this.announcement.content = '';

View File

@ -2,7 +2,7 @@
<div>
<el-card>
<div slot="header">
<span class="panel-title home-title">Website Config</span>
<span class="panel-title home-title">{{ $t('m.Website_Config') }}</span>
</div>
<el-form
label-position="left"
@ -12,67 +12,67 @@
>
<el-row :gutter="20">
<el-col :md="8" :xs="24">
<el-form-item label="Base URL" required>
<el-form-item :label="$t('m.Base_Url')" required>
<el-input
v-model="websiteConfig.baseUrl"
placeholder="Website Base URL"
:placeholder="$t('m.Base_Url')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Name" required>
<el-form-item :label="$t('m.Web_Name')" required>
<el-input
v-model="websiteConfig.name"
placeholder="Website Name"
:placeholder="$t('m.Web_Name')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Short Name" required>
<el-form-item :label="$t('m.Short_Name')" required>
<el-input
v-model="websiteConfig.shortName"
placeholder="Website Name Shortcut"
:placeholder="$t('m.Short_Name')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Record Name" required>
<el-form-item :label="$t('m.Record_Name')" required>
<el-input
v-model="websiteConfig.recordName"
placeholder="Website Record Name"
:placeholder="$t('m.Record_Name')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Record URL" required>
<el-form-item :label="$t('m.Record_Url')" required>
<el-input
v-model="websiteConfig.recordUrl"
placeholder="Website Record URL"
:placeholder="$t('m.Record_Url')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Project Name" required>
<el-form-item :label="$t('m.Project_Name')" required>
<el-input
v-model="websiteConfig.projectName"
placeholder="Website Project Namet"
:placeholder="$t('m.Project_Name')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Project URL" required>
<el-form-item :label="$t('m.Project_Url')" required>
<el-input
v-model="websiteConfig.projectUrl"
placeholder="Website Project URL"
:placeholder="$t('m.Project_Url')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="24" :xs="24">
<el-form-item label="Website Desc" required>
<el-form-item :label="$t('m.Web_Desc')" required>
<el-input
type="textarea"
placeholder="Website Description"
:placeholder="$t('m.Web_Desc')"
v-model="websiteConfig.description"
maxlength="150"
show-word-limit
@ -81,7 +81,7 @@
</el-form-item>
</el-col>
<el-col :md="24" :xs="24">
<el-form-item label="Allow Register" label-width="120px">
<el-form-item :label="$t('m.Allow_Register')" label-width="120px">
<el-switch
v-model="websiteConfig.register"
active-color="#13ce66"
@ -92,55 +92,58 @@
</el-col>
</el-row>
</el-form>
<el-button type="primary" @click.native="saveWebsiteConfig" size="small"
>Save</el-button
<el-button
type="primary"
@click.native="saveWebsiteConfig"
size="small"
>{{ $t('m.Save') }}</el-button
>
</el-card>
<el-card style="margin-top:15px">
<div slot="header">
<span class="panel-title home-title">SMTP Config</span>
<span class="panel-title home-title">{{ $t('m.SMTP_Config') }}</span>
</div>
<el-form label-position="left" label-width="80px" :model="smtp">
<el-row :gutter="20">
<el-col :md="24" :xs="24">
<el-form-item label="Host" required label-width="80px">
<el-form-item :label="$t('m.Host')" required label-width="80px">
<el-input
v-model="smtp.emailHost"
placeholder="SMTP Host Address"
:placeholder="$t('m.Host')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="24" :xs="24">
<el-form-item label="Port" required label-width="80px">
<el-form-item :label="$t('m.Port')" required label-width="80px">
<el-input
v-model="smtp.emailPort"
placeholder="SMTP Port"
:placeholder="$t('m.Port')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Username" required label-width="80px">
<el-form-item :label="$t('m.Email')" required label-width="80px">
<el-input
v-model="smtp.emailUsername"
placeholder="Account Username"
:placeholder="$t('m.Email')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Password" label-width="80px" required>
<el-form-item :label="$t('m.Password')" label-width="80px" required>
<el-input
v-model="smtp.emailPassword"
type="password"
placeholder="SMTP Server Password"
:placeholder="$t('m.Password')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="24" :xs="24">
<el-form-item label="BG IMG" label-width="80px" required>
<el-form-item :label="$t('m.Email_BG')" label-width="80px" required>
<el-input
v-model="smtp.emailBGImg"
placeholder="SMTP Template Background IMG Address"
:placeholder="$t('m.Email_BG_Desc')"
></el-input>
</el-form-item>
</el-col>
@ -151,90 +154,123 @@
</el-col>
</el-row>
</el-form>
<el-button type="primary" @click.native="saveSMTPConfig" size="small"
>Save</el-button
>
<el-button type="primary" @click.native="saveSMTPConfig" size="small">{{
$t('m.Save')
}}</el-button>
<el-button
type="warning"
@click.native="testSMTPConfig"
v-if="saved"
:loading="loadingBtnTest"
size="small"
>Send Test Email</el-button
>{{ $t('m.Send_Test_Email') }}</el-button
>
</el-card>
<el-card style="margin-top:15px">
<div slot="header">
<span class="panel-title home-title">DataBase Config</span>
<span class="panel-title home-title">{{
$t('m.DataSource_Config')
}}</span>
</div>
<el-form label-position="top" :model="databaseConfig">
<el-row :gutter="20">
<el-col :md="12" :xs="24">
<el-form-item label="MySQL Host" required label-width="80px">
<el-form-item
:label="'MySQL ' + $t('m.Host')"
required
label-width="80px"
>
<el-input
v-model="databaseConfig.dbHost"
placeholder="MySQL Host Address"
:placeholder="'MySQL ' + $t('m.Host')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="MySQL Port" required label-width="80px">
<el-form-item
:label="'MySQL ' + $t('m.Port')"
required
label-width="80px"
>
<el-input
type="number"
v-model="databaseConfig.dbPost"
placeholder="MySQL Port"
:placeholder="'MySQL ' + $t('m.Port')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="MySQL Username" required label-width="80px">
<el-form-item
:label="'MySQL ' + $t('m.Username')"
required
label-width="80px"
>
<el-input
v-model="databaseConfig.dbUsername"
placeholder="MySQL Username"
:placeholder="'MySQL ' + $t('m.Username')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="MySQL Password" label-width="80px" required>
<el-form-item
:label="'MySQL ' + $t('m.Password')"
label-width="80px"
required
>
<el-input
v-model="databaseConfig.dbPassword"
type="password"
placeholder="MySQL Password"
:placeholder="'MySQL ' + $t('m.Password')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Redis Host" label-width="80px" required>
<el-form-item
:label="'Redis ' + $t('m.Host')"
label-width="80px"
required
>
<el-input
v-model="databaseConfig.redisHost"
type="password"
placeholder="Redis Host Address"
:placeholder="'Redis ' + $t('m.Host')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Redis Port" label-width="80px" required>
<el-form-item
:label="'Redis ' + $t('m.Port')"
label-width="80px"
required
>
<el-input
v-model="databaseConfig.redisPort"
type="password"
placeholder="Redis Port"
:placeholder="'Redis ' + $t('m.Port')"
></el-input>
</el-form-item>
</el-col>
<el-col :md="8" :xs="24">
<el-form-item label="Redis Password" label-width="80px" required>
<el-form-item
:label="'Redis ' + $t('m.Password')"
label-width="80px"
required
>
<el-input
v-model="databaseConfig.redisPassword"
type="password"
placeholder="Redis Password"
:placeholder="'Redis ' + $t('m.Password')"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-button type="primary" @click.native="saveDataBaseConfig" size="small"
>Save</el-button
<el-button
type="primary"
@click.native="saveDataBaseConfig"
size="small"
>{{ $t('m.Save') }}</el-button
>
</el-card>
</div>
@ -268,7 +304,7 @@ export default {
this.smtp = res.data.data;
} else {
this.init = true;
myMessage.warning('请先添加STMP的配置');
myMessage.warning('No STMP Config');
}
});
api
@ -288,22 +324,22 @@ export default {
saveSMTPConfig() {
api.admin_editSMTPConfig(this.smtp).then(
(res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
this.saved = true;
},
() => {}
);
},
testSMTPConfig() {
this.$prompt('Please input your email', '', {
this.$prompt(this.$i18n.t('m.Please_input_your_email'), '', {
inputPattern: /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
inputErrorMessage: 'Error email format',
inputErrorMessage: this.$i18n.t('m.Email_Check_Format'),
})
.then(({ value }) => {
this.loadingBtnTest = true;
api.admin_testSMTPConfig(value).then(
(res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Post_Successfully'));
this.loadingBtnTest = false;
},
() => {
@ -314,10 +350,19 @@ export default {
.catch(() => {});
},
saveWebsiteConfig() {
for (var key in this.websiteConfig) {
if (key == 'register') {
continue;
} else {
if (this.websiteConfig[key].replace(/(^\s*)|(\s*$)/g, '')) {
this.websiteConfig[key] = 'None';
}
}
}
api
.admin_editWebsiteConfig(this.websiteConfig)
.then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
})
.catch(() => {});
},
@ -325,7 +370,7 @@ export default {
api
.admin_editDataBaseConfig(this.databaseConfig)
.then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
})
.catch(() => {});
},

View File

@ -2,7 +2,7 @@
<div class="view">
<el-card>
<div slot="header">
<span class="panel-title home-title">User</span>
<span class="panel-title home-title">{{ $t('m.General_User') }}</span>
<div class="filter-row">
<span>
<el-button
@ -10,13 +10,13 @@
icon="el-icon-delete-solid"
@click="deleteUsers(null)"
size="small"
>Delete
>{{ $t('m.Delete') }}
</el-button>
</span>
<span>
<vxe-input
v-model="keyword"
placeholder="Enter keyword"
:placeholder="$t('m.Enter_keyword')"
type="search"
size="medium"
@search-click="filterByKeyword"
@ -26,10 +26,10 @@
<span>
<el-switch
v-model="onlyAdmin"
active-text="OnlyAdmin"
:active-text="$t('m.OnlyAdmin')"
:width="40"
@change="filterByAdmin"
inactive-text="All"
:inactive-text="$t('m.All')"
>
</el-switch>
</span>
@ -53,40 +53,58 @@
></vxe-table-column>
<vxe-table-column
field="username"
title="Username"
:title="$t('m.User')"
min-width="140"
></vxe-table-column>
<vxe-table-column
field="realname"
title="Real Name"
:title="$t('m.RealName')"
min-width="140"
></vxe-table-column>
<vxe-table-column
field="email"
title="Email"
:title="$t('m.Email')"
min-width="150"
></vxe-table-column>
<vxe-table-column field="gmtCreate" title="Create Time" min-width="150">
<vxe-table-column
field="gmtCreate"
:title="$t('m.Created_Time')"
min-width="150"
>
<template v-slot="{ row }">
{{ row.gmtCreate | localtime }}
</template>
</vxe-table-column>
<vxe-table-column field="role" title="User Type" min-width="100">
<vxe-table-column
field="role"
:title="$t('m.User_Type')"
min-width="100"
>
<template v-slot="{ row }">
{{ getRole(row.roles) | parseRole }}
</template>
</vxe-table-column>
<vxe-table-column field="status" title="Status" min-width="100">
<vxe-table-column
field="status"
:title="$t('m.Status')"
min-width="100"
>
<template v-slot="{ row }">
<el-tag effect="dark" color="#19be6b" v-if="row.status == 0"
>正常</el-tag
>
<el-tag effect="dark" color="#ed3f14" v-else>禁用</el-tag>
<el-tag effect="dark" color="#19be6b" v-if="row.status == 0">{{
$t('m.Normal')
}}</el-tag>
<el-tag effect="dark" color="#ed3f14" v-else>{{
$t('m.Disable')
}}</el-tag>
</template>
</vxe-table-column>
<vxe-table-column title="Option" min-width="150">
<vxe-table-column :title="$t('m.Option')" min-width="150">
<template v-slot="{ row }">
<el-tooltip effect="dark" content="编辑用户" placement="top">
<el-tooltip
effect="dark"
:content="$t('m.Edit_User')"
placement="top"
>
<el-button
icon="el-icon-edit-outline"
size="mini"
@ -95,7 +113,11 @@
>
</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="删除用户" placement="top">
<el-tooltip
effect="dark"
:content="$t('m.Delete_User')"
placement="top"
>
<el-button
icon="el-icon-delete-solid"
size="mini"
@ -122,15 +144,12 @@
<!-- 导入csv用户数据 -->
<el-card style="margin-top:20px">
<div slot="header">
<span class="panel-title home-title">Import Users</span>
<span class="panel-title home-title">{{ $t('m.Import_User') }}</span>
</div>
<p>1. 用户数据导入仅支持csv格式的用户数据</p>
<p>
2. 共三列数据:
用户名密码邮箱任一列不能为空否则该行数据可能导入失败
</p>
<p>3. 第一行不必写(用户名密码邮箱)这三个列名</p>
<p>4. 请导入保存为UTF-8编码的文件否则中文可能会乱码</p>
<p>1. {{ $t('m.Import_User_Tips1') }}</p>
<p>2. {{ $t('m.Import_User_Tips2') }}</p>
<p>3. {{ $t('m.Import_User_Tips3') }}</p>
<p>4. {{ $t('m.Import_User_Tips4') }}</p>
<el-upload
v-if="!uploadUsers.length"
action=""
@ -138,23 +157,35 @@
accept=".csv"
:before-upload="handleUsersCSV"
>
<el-button size="small" icon="el-icon-folder-opened" type="primary"
>Choose File</el-button
>
<el-button size="small" icon="el-icon-folder-opened" type="primary">{{
$t('m.Choose_File')
}}</el-button>
</el-upload>
<template v-else>
<vxe-table :data="uploadUsersPage" stripe auto-resize>
<vxe-table-column title="Username" field="username" min-width="150">
<vxe-table-column
:title="$t('m.Username')"
field="username"
min-width="150"
>
<template v-slot="{ row }">
{{ row[0] }}
</template>
</vxe-table-column>
<vxe-table-column title="Password" field="password" min-width="150">
<vxe-table-column
:title="$t('m.Password')"
field="password"
min-width="150"
>
<template v-slot="{ row }">
{{ row[1] }}
</template>
</vxe-table-column>
<vxe-table-column title="Email" field="email" min-width="150">
<vxe-table-column
:title="$t('m.Email')"
field="email"
min-width="150"
>
<template v-slot="{ row }">
{{ row[2] }}
</template>
@ -167,14 +198,14 @@
size="small"
icon="el-icon-upload"
@click="handleUsersUpload"
>Upload All
>{{ $t('m.Upload_All') }}
</el-button>
<el-button
type="danger"
size="small"
icon="el-icon-delete"
@click="handleResetData"
>Clear All
>{{ $t('m.Clear_All') }}
</el-button>
<el-pagination
class="page"
@ -191,7 +222,7 @@
<!--生成用户数据-->
<el-card style="margin-top:20px">
<div slot="header">
<span class="panel-title home-title">Generate Users</span>
<span class="panel-title home-title">{{ $t('m.Generate_User') }}</span>
</div>
<el-form
:model="formGenerateUser"
@ -200,7 +231,7 @@
>
<el-row :gutter="10">
<el-col :md="5" :xs="24">
<el-form-item label="Prefix" prop="prefix">
<el-form-item :label="$t('m.Prefix')" prop="prefix">
<el-input
v-model="formGenerateUser.prefix"
placeholder="Prefix"
@ -208,7 +239,7 @@
</el-form-item>
</el-col>
<el-col :md="5" :xs="24">
<el-form-item label="Suffix" prop="suffix">
<el-form-item :label="$t('m.Suffix')" prop="suffix">
<el-input
v-model="formGenerateUser.suffix"
placeholder="Suffix"
@ -216,7 +247,7 @@
</el-form-item>
</el-col>
<el-col :md="5" :xs="24">
<el-form-item label="Start Number" prop="number_from">
<el-form-item :label="$t('m.Start_Number')" prop="number_from">
<el-input-number
v-model="formGenerateUser.number_from"
style="width: 100%"
@ -224,7 +255,7 @@
</el-form-item>
</el-col>
<el-col :md="5" :xs="24">
<el-form-item label="End Number" prop="number_to">
<el-form-item :label="$t('m.End_Number')" prop="number_to">
<el-input-number
v-model="formGenerateUser.number_to"
style="width: 100%"
@ -232,10 +263,13 @@
</el-form-item>
</el-col>
<el-col :md="4" :xs="24">
<el-form-item label="Password Length" prop="password_length">
<el-form-item
:label="$t('m.Password_Length')"
prop="password_length"
>
<el-input
v-model.number="formGenerateUser.password_length"
placeholder="Password Length"
:placeholder="$t('m.Password_Length')"
></el-input>
</el-form-item>
</el-col>
@ -249,13 +283,13 @@
:loading="loadingGenerate"
size="small"
>
Generate & Export
{{ $t('m.Generate_and_Export') }}
</el-button>
<span
class="userPreview"
v-if="formGenerateUser.number_from <= formGenerateUser.number_to"
>
The usernames will be
{{ $t('m.The_usernames_will_be') }}
{{
formGenerateUser.prefix +
formGenerateUser.number_from +
@ -290,7 +324,11 @@
</el-card>
<!--编辑用户的对话框-->
<el-dialog title="User Info" :visible.sync="showUserDialog" width="350px">
<el-dialog
:title="$t('m.User')"
:visible.sync="showUserDialog"
width="350px"
>
<el-form
:model="selectUser"
label-width="100px"
@ -300,22 +338,22 @@
>
<el-row :gutter="10">
<el-col :span="24">
<el-form-item label="Username" required prop="username">
<el-form-item :label="$t('m.Username')" required prop="username">
<el-input v-model="selectUser.username"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="Real Name" prop="realname">
<el-form-item :label="$t('m.RealName')" prop="realname">
<el-input v-model="selectUser.realname"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="Email" prop="email">
<el-form-item :label="$t('m.Email')" prop="email">
<el-input v-model="selectUser.email"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="Set NewPwd">
<el-form-item :label="$t('m.Set_New_PWD')">
<el-switch
:active-value="true"
:inactive-value="false"
@ -325,15 +363,19 @@
</el-form-item>
</el-col>
<el-col :span="24" v-if="selectUser.setNewPwd == 1">
<el-form-item label="New Pwd" required prop="password">
<el-form-item
:label="$t('m.General_New_Password')"
required
prop="password"
>
<el-input
v-model="selectUser.password"
placeholder="Enter the new password"
:placeholder="$t('m.General_New_Password')"
></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="User Type">
<el-form-item :label="$t('m.User_Type')">
<el-select v-model="selectUser.type">
<el-option
label="超级管理员"
@ -375,7 +417,7 @@
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="Status">
<el-form-item :label="$t('m.Status')">
<el-switch
:active-value="0"
:inactive-value="1"
@ -387,10 +429,12 @@
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="danger" @click.native="showUserDialog = false"
>Cancel</el-button
>
<el-button type="primary" @click.native="saveUser">Save</el-button>
<el-button type="danger" @click.native="showUserDialog = false">{{
$t('m.Cancel')
}}</el-button>
<el-button type="primary" @click.native="saveUser">{{
$t('m.OK')
}}</el-button>
</span>
</el-dialog>
</div>
@ -406,13 +450,25 @@ export default {
data() {
const CheckTogtFrom = (rule, value, callback) => {
if (value < this.formGenerateUser.number_from) {
callback(new Error('用户结束编号不能小于起始编号'));
callback(
new Error(
this.$i18n.t(
'm.The_end_number_cannot_be_less_than_the_start_number'
)
)
);
}
callback();
};
const CheckPwdLength = (rule, value, callback) => {
if (value < 6 || value > 25) {
callback(new Error('密码长度请选择6~25字符长度'));
callback(
new Error(
this.$i18n.t(
'm.Please_select_6_to_25_characters_for_password_length'
)
)
);
}
callback();
};
@ -423,7 +479,7 @@ export default {
res.data.data.username === true &&
value != this.selectUser.username
) {
callback(new Error('用户名已存在'));
callback(new Error(this.$i18n.t('m.The_username_already_exists')));
} else {
callback();
}
@ -435,7 +491,7 @@ export default {
api.checkUsernameOrEmail(undefined, value).then(
(res) => {
if (res.data.data.email === true && value != this.selectUser.email) {
callback(new Error('邮箱已存在'));
callback(new Error(this.$i18n.t('m.The_email_already_exists')));
} else {
callback();
}
@ -477,30 +533,29 @@ export default {
{
validator: CheckUsernameNotExist,
trigger: 'blur',
message: 'The username already exists',
message: this.$i18n.t('m.The_username_already_exists'),
},
{
max: 255,
message: 'The longest length of a username is 255',
message: this.$i18n.t('m.Username_Check_Max'),
trigger: 'blur',
},
],
realname: [
{
max: 255,
message: 'The longest length of a username is 255',
trigger: 'blur',
},
],
email: [
{
type: 'email',
message: 'The email format is incorrect',
message: this.$i18n.t('m.Email_Check_Format'),
trigger: 'blur',
},
{
validator: CheckEmailNotExist,
message: 'The email already exists',
message: this.$i18n.t('m.The_email_already_exists'),
trigger: 'blur',
},
],
@ -519,17 +574,29 @@ export default {
},
formGenerateRules: {
number_from: [
{ required: true, message: '编号起始不能为空', trigger: 'blur' },
{
required: true,
message: this.$i18n.t('m.Start_Number_Required'),
trigger: 'blur',
},
],
number_to: [
{ required: true, message: '最大编号不能为空', trigger: 'blur' },
{
required: true,
message: this.$i18n.t('m.End_Number_Required'),
trigger: 'blur',
},
{ validator: CheckTogtFrom, trigger: 'blur' },
],
password_length: [
{ required: true, message: '密码长度不能为空', trigger: 'blur' },
{
required: true,
message: this.$i18n.t('m.Password_Check_Required'),
trigger: 'blur',
},
{
type: 'number',
message: '密码长度必须为数字类型',
message: this.$i18n.t('m.Password_Length_Checked'),
trigger: 'blur',
},
{ validator: CheckPwdLength, trigger: 'blur' },
@ -554,7 +621,7 @@ export default {
.admin_editUser(this.selectUser)
.then((res) => {
//
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
this.getUserList(this.currentPage);
})
.then(() => {
@ -606,13 +673,11 @@ export default {
ids = this.selectedUsers;
}
if (ids.length > 0) {
this.$confirm(
'你确定要删除该用户?可能会关联删除该用户创建的公告,题目,比赛等。',
'确认',
{
type: 'warning',
}
).then(
this.$confirm(this.$i18n.t('m.Delete_User_Tips'), 'Tips', {
confirmButtonText: this.$i18n.t('m.OK'),
cancelButtonText: this.$i18n.t('m.Cancel'),
type: 'warning',
}).then(
() => {
api
.admin_deleteUsers(ids)
@ -629,7 +694,9 @@ export default {
() => {}
);
} else {
myMessage.warning('勾选的用户不能为空!');
myMessage.warning(
this.$i18n.t('m.The_number_of_users_selected_cannot_be_empty')
);
}
},
@ -651,7 +718,7 @@ export default {
generateUser() {
this.$refs['formGenerateUser'].validate((valid) => {
if (!valid) {
myMessage.error('请在生成用户名规则中选择或输入正确的值');
myMessage.error(this.$i18n.t('m.Error_Please_check_your_choice'));
return;
}
this.loadingGenerate = true;
@ -661,12 +728,9 @@ export default {
.then((res) => {
this.loadingGenerate = false;
myMessage.success(res.data.msg);
let url = '/file/generate-user-excel?key=' + res.data.data.key;
let url = '/api/file/generate-user-excel?key=' + res.data.data.key;
utils.downloadFile(url).then(() => {
this.$alert(
'所有指定格式用户创建成功,用户表已成功下载到您的电脑里了!',
'提醒'
);
this.$alert(this.$i18n.t('m.Generate_User_Success'), 'Tips');
});
this.getUserList(1);
})
@ -684,7 +748,7 @@ export default {
let delta = results.data.length - data.length;
if (delta > 0) {
myMessage.warning(
delta + '行用户数据被过滤,原因是可能为空行或某个列值为空'
delta + this.$i18n.t('m.Generate_Skipped_Reason')
);
}
this.uploadUsersCurrentPage = 1;
@ -702,7 +766,7 @@ export default {
.then((res) => {
this.getUserList(1);
this.handleResetData();
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Upload_Users_Successfully'));
})
.catch(() => {});
},

View File

@ -2,7 +2,7 @@
<div>
<el-card>
<div slot="header">
<span class="panel-title home-title">Export Problems</span>
<span class="panel-title home-title">{{ $t('m.Export_Problem') }}</span>
<div class="filter-row">
<span>
<el-button
@ -10,13 +10,13 @@
size="small"
@click="exportProblems"
icon="el-icon-arrow-down"
>Export
>{{ $t('m.Export') }}
</el-button>
</span>
<span>
<vxe-input
v-model="keyword"
placeholder="Enter keyword"
:placeholder="$t('m.Enter_keyword')"
type="search"
size="medium"
@keyup.enter.native="filterByKeyword"
@ -38,12 +38,16 @@
<vxe-table-column type="checkbox" width="60"> </vxe-table-column>
<vxe-table-column title="ID" min-width="100" field="id">
</vxe-table-column>
<vxe-table-column min-width="150" title="Title" field="title">
<vxe-table-column min-width="150" :title="$t('m.Title')" field="title">
</vxe-table-column>
<vxe-table-column min-width="150" field="author" title="Author">
<vxe-table-column
min-width="150"
field="author"
:title="$t('m.Author')"
>
</vxe-table-column>
<vxe-table-column field="gmtCreate" title="Create Time">
<vxe-table-column field="gmtCreate" :title="$t('m.Created_Time')">
<template v-slot="{ row }">
{{ row.create_time | localtime }}
</template>
@ -64,7 +68,7 @@
<el-card style="margin-top:15px">
<div slot="header">
<span class="panel-title home-title">Import Problems</span>
<span class="panel-title home-title">{{ $t('m.Import_Problem') }}</span>
</div>
<el-upload
ref="HOJ"
@ -84,7 +88,7 @@
type="primary"
slot="trigger"
icon="el-icon-folder-opened"
>Choose File</el-button
>{{ $t('m.Choose_File') }}</el-button
>
<el-button
style="margin-left: 10px;"
@ -92,14 +96,16 @@
type="success"
@click="submitUpload('HOJ')"
icon="el-icon-upload"
>Upload</el-button
>{{ $t('m.Upload') }}</el-button
>
</el-upload>
</el-card>
<el-card style="margin-top:15px">
<div slot="header">
<span class="panel-title home-title">Import QDOJ Problems</span>
<span class="panel-title home-title">{{
$t('m.Import_QDOJ_Problem')
}}</span>
</div>
<el-upload
ref="QDOJ"
@ -119,7 +125,7 @@
type="primary"
slot="trigger"
icon="el-icon-folder-opened"
>Choose File</el-button
>{{ $t('m.Choose_File') }}</el-button
>
<el-button
style="margin-left: 10px;"
@ -127,7 +133,7 @@
type="success"
@click="submitUpload('QDOJ')"
icon="el-icon-upload"
>Upload</el-button
>{{ $t('m.Upload') }}</el-button
>
</el-upload>
</el-card>
@ -183,6 +189,10 @@ export default {
},
exportProblems() {
let params = [];
if (this.selected_problems.length <= 0) {
myMessage.error(this.$i18n.t('m.Export_Problem_NULL_Tips'));
return;
}
for (let p of this.selected_problems) {
params.push('pid=' + p.id);
}

View File

@ -13,9 +13,13 @@
>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item prop="problemId" label="Problem ID" required>
<el-form-item
prop="problemId"
:label="$t('m.Problem_Display_ID')"
required
>
<el-input
placeholder="Enter the display id of problem"
:placeholder="$t('m.Problem_Display_ID')"
v-model="problem.problemId"
>
</el-input>
@ -24,9 +28,9 @@
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item prop="title" label="Title" required>
<el-form-item prop="title" :label="$t('m.Title')" required>
<el-input
placeholder="Enter the title of problem"
:placeholder="$t('m.Title')"
v-model="problem.title"
></el-input>
</el-form-item>
@ -35,18 +39,18 @@
<el-row :gutter="20" v-if="contestID">
<el-col :md="12" :xs="24">
<el-form-item label="Display Title" required>
<el-form-item :label="$t('m.Contest_Display_Title')" required>
<el-input
placeholder="Enter the display title of problem in contest"
:placeholder="$t('m.Contest_Display_Title')"
v-model="contestProblem.displayTitle"
></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :xs="24">
<el-form-item label="Display ID" required>
<el-form-item :label="$t('m.Contest_Display_ID')" required>
<el-input
placeholder="Enter the display ID of problem in contest"
:placeholder="$t('m.Contest_Display_ID')"
v-model="contestProblem.displayId"
></el-input>
</el-form-item>
@ -55,7 +59,11 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item prop="description" label="Description" required>
<el-form-item
prop="description"
:label="$t('m.Description')"
required
>
<Editor :value.sync="problem.description"></Editor>
</el-form-item>
</el-col>
@ -63,37 +71,37 @@
<el-row :gutter="20">
<el-col :md="6" :xs="24">
<el-form-item label="Time Limit(ms)" required>
<el-form-item :label="$t('m.Time_Limit') + '(ms)'" required>
<el-input
type="Number"
placeholder="Enter the time limit of problem"
:placeholder="$t('m.Time_Limit')"
v-model="problem.timeLimit"
:disabled="problem.isRemote"
></el-input>
</el-form-item>
</el-col>
<el-col :md="6" :xs="24">
<el-form-item label="Memory Limit(mb)" required>
<el-form-item :label="$t('m.Memory_Limit') + '(mb)'" required>
<el-input
type="Number"
placeholder="Enter the memory limit of problem"
:placeholder="$t('m.Memory_Limit')"
v-model="problem.memoryLimit"
:disabled="problem.isRemote"
></el-input>
</el-form-item>
</el-col>
<el-col :md="6" :xs="24">
<el-form-item label="Stack Limit(mb)" required>
<el-form-item :label="$t('m.Stack_Limit') + '(mb)'" required>
<el-input
type="Number"
placeholder="Enter the stack limit of problem"
:placeholder="$t('m.Stack_Limit')"
v-model="problem.stackLimit"
:disabled="problem.isRemote"
></el-input>
</el-form-item>
</el-col>
<el-col :md="6" :xs="24">
<el-form-item label="Level" required>
<el-form-item :label="$t('m.Level')" required>
<el-select
class="difficulty-select"
placeholder="Enter the level of problem"
@ -114,7 +122,7 @@
<el-col :span="24">
<el-form-item
prop="input_description"
label="Input Description"
:label="$t('m.Input')"
required
>
<Editor :value.sync="problem.input"></Editor>
@ -123,7 +131,7 @@
<el-col :span="24">
<el-form-item
prop="output_description"
label="Output Description"
:label="$t('m.Output')"
required
>
<Editor :value.sync="problem.output"></Editor>
@ -133,16 +141,25 @@
<el-row :gutter="20">
<el-col :md="4" :xs="24">
<el-form-item label="Auth">
<el-form-item :label="$t('m.Auth')">
<el-select v-model="problem.auth" size="small">
<el-option label="公开" :value="1"></el-option>
<el-option label="私有" :value="2"></el-option>
<el-option label="比赛题目" :value="3"></el-option>
<el-option
:label="$t('m.Public_Problem')"
:value="1"
></el-option>
<el-option
:label="$t('m.Private_Problem')"
:value="2"
></el-option>
<el-option
:label="$t('m.Contest_Problem')"
:value="3"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="4" :xs="24">
<el-form-item label="Code Shareable">
<el-form-item :label="$t('m.Code_Shareable')">
<el-switch
v-model="problem.codeShare"
active-text=""
@ -152,7 +169,7 @@
</el-form-item>
</el-col>
<el-col :md="16" :xs="24">
<el-form-item label="Tags" required>
<el-form-item :label="$t('m.Tags')" required>
<el-tag
v-for="tag in problemTags"
closable
@ -179,7 +196,7 @@
</el-autocomplete>
<el-tooltip
effect="dark"
content="添加新标签"
:content="$t('m.Add')"
placement="top"
v-else
>
@ -195,7 +212,11 @@
</el-row>
<el-row>
<el-col :md="24" :xs="24">
<el-form-item label="Language" :error="error.languages" required>
<el-form-item
:label="$t('m.Languages')"
:error="error.languages"
required
>
<el-checkbox-group v-model="problemLanguages">
<el-tooltip
class="spj-radio"
@ -214,10 +235,10 @@
<div>
<div class="panel-title home-title">
Problem Examples
{{ $t('m.Problem_Examples') }}
<el-popover placement="right" trigger="hover">
<p>
题目样例请最好不要超过2个题目样例题面样例不纳入评测数据
{{ $t('m.Problem_Examples_Desc') }}
</p>
<i slot="reference" class="el-icon-question"></i>
</el-popover>
@ -227,7 +248,7 @@
:key="'example' + index"
>
<Accordion
:title="'Example' + (index + 1)"
:title="$t('m.Problem_Example') + (index + 1)"
:isOpen="example.isOpen ? true : false"
:index="index"
@changeVisible="changeExampleVisible"
@ -239,15 +260,15 @@
slot="header"
@click="deleteExample(index)"
>
Delete
{{ $t('m.Delete') }}
</el-button>
<el-row :gutter="20">
<el-col :xs="24" :md="12">
<el-form-item label="Input Example" required>
<el-form-item :label="$t('m.Example_Input')" required>
<el-input
:rows="5"
type="textarea"
placeholder="Input Example"
:placeholder="$t('m.Example_Input')"
v-model="example.input"
style="white-space: pre-line"
>
@ -255,11 +276,11 @@
</el-form-item>
</el-col>
<el-col :xs="24" :md="12">
<el-form-item label="Output Example" required>
<el-form-item :label="$t('m.Example_Output')" required>
<el-input
:rows="5"
type="textarea"
placeholder="Output Example"
:placeholder="$t('m.Example_Output')"
v-model="example.output"
>
</el-input>
@ -276,20 +297,16 @@
@click="addExample()"
icon="el-icon-plus"
type="small"
>Add Example
>{{ $t('m.Add_Example') }}
</el-button>
</div>
<template v-if="!problem.isRemote">
<div class="panel-title home-title">
Special Judge
{{ $t('m.Special_Judge') }}
<el-popover placement="right" trigger="hover">
<p>使用特殊判题的原因</p>
<p>1. 题目要求的输出结果可能不唯一允许不同结果存在</p>
<p>
2.
题目最终要求输出一个浮点数而且会告诉只要答案和标准答案相差不超过某个较小的数就可以
例如题目要求保留几位小数输出结果后几位小数不相同也是正确的
</p>
<p>{{ $t('m.Special_Judge_Tips1') }}</p>
<p>1. {{ $t('m.Special_Judge_Tips2') }}</p>
<p>2. {{ $t('m.Special_Judge_Tips3') }}</p>
<i slot="reference" class="el-icon-question"></i>
</el-popover>
</div>
@ -298,14 +315,16 @@
<el-checkbox
v-model="problem.spj"
@click.native.prevent="switchSpj()"
>Use Special Judge</el-checkbox
>{{ $t('m.Use_Special_Judge') }}</el-checkbox
>
</el-col>
</el-form-item>
<el-form-item v-if="problem.spj">
<Accordion title="Special Judge Code">
<Accordion :title="$t('m.Special_Judge_Code')">
<template slot="header">
<span style="margin-right:5px;">SPJ language</span>
<span style="margin-right:5px;"
>{{ $t('m.SPJ_language') }}</span
>
<el-radio-group v-model="problem.spjLanguage">
<el-tooltip
class="spj-radio"
@ -325,7 +344,7 @@
@click="compileSPJ"
:loading="loadingCompile"
style="margin-left:10px"
>Complie
>{{ $t('m.Compile') }}
</el-button>
</template>
<code-mirror
@ -336,11 +355,11 @@
</el-form-item>
</template>
<el-form-item style="margin-top: 20px" label="Hint">
<el-form-item style="margin-top: 20px" :label="$t('m.Hint')">
<Editor :value.sync="problem.hint"></Editor>
</el-form-item>
<el-form-item label="Code Template">
<el-form-item :label="$t('m.Code_Template')">
<el-row>
<el-col
:span="24"
@ -359,7 +378,7 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="Type">
<el-form-item :label="$t('m.Type')">
<el-radio-group
v-model="problem.type"
:disabled="disableRuleType || problem.isRemote"
@ -373,17 +392,17 @@
<el-row :gutter="20" v-if="!problem.isRemote">
<div class="panel-title home-title">
Judge Samples
{{ $t('m.Judge_Samples') }}
<el-popover placement="right" trigger="hover">
<p>评测数据判题机对该题目的相关提交进行评测的数据来源</p>
<p>{{ $t('m.Sample_Tips') }}</p>
<i slot="reference" class="el-icon-question"></i>
</el-popover>
</div>
<el-switch
v-model="problem.isUploadCase"
active-text="Use Upload File"
inactive-text="Use Manual Input"
:active-text="$t('m.Use_Upload_File')"
:inactive-text="$t('m.Use_Manual_Input')"
style="margin: 10px 0"
>
</el-switch>
@ -398,8 +417,11 @@
:on-success="uploadSucceeded"
:on-error="uploadFailed"
>
<el-button size="small" type="primary" icon="el-icon-upload"
>Choose File</el-button
<el-button
size="small"
type="primary"
icon="el-icon-upload"
>{{ $t('m.Choose_File') }}</el-button
>
</el-upload>
</el-form-item>
@ -411,15 +433,27 @@
:data="problem.testCaseScore"
align="center"
>
<vxe-table-column field="input" title="Input" min-width="100">
<vxe-table-column
field="input"
:title="$t('m.Sample_Input_File')"
min-width="100"
>
</vxe-table-column>
<vxe-table-column field="output" title="Output" min-width="100">
<vxe-table-column
field="output"
:title="$t('m.Sample_Output_File')"
min-width="100"
>
</vxe-table-column>
<vxe-table-column field="score" title="Score" min-width="100">
<vxe-table-column
field="score"
:title="$t('m.Score')"
min-width="100"
>
<template v-slot="{ row }">
<el-input
size="small"
placeholder="Score"
:placeholder="$t('m.Score')"
v-model="row.score"
:disabled="problem.type != 1"
>
@ -436,7 +470,7 @@
:key="'sample' + index"
>
<Accordion
:title="'Sample' + (index + 1)"
:title="$t('m.Problem_Sample') + (index + 1)"
:isOpen="sample.isOpen ? true : false"
:index="index"
@changeVisible="changeSampleVisible"
@ -448,26 +482,26 @@
slot="header"
@click="deleteSample(index)"
>
Delete
{{ $t('m.Delete') }}
</el-button>
<el-row :gutter="20">
<el-col :xs="24" :md="12">
<el-form-item label="Input Sample" required>
<el-form-item :label="$t('m.Sample_Input')" required>
<el-input
:rows="5"
type="textarea"
placeholder="Input Sample"
:placeholder="$t('m.Sample_Input')"
v-model="sample.input"
>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :md="12">
<el-form-item label="Output Sample" required>
<el-form-item :label="$t('m.Sample_Output')" required>
<el-input
:rows="5"
type="textarea"
placeholder="Output Sample"
:placeholder="$t('m.Sample_Output')"
v-model="sample.output"
>
</el-input>
@ -477,11 +511,11 @@
:span="24"
v-show="problem.type == 1 && sample.score != null"
>
<el-form-item label="IO Score">
<el-form-item :label="$t('m.Score')">
<el-input
type="number"
size="small"
placeholder="The score of the testcase"
:placeholder="$t('m.Score')"
v-model="sample.score"
>
</el-input>
@ -497,21 +531,21 @@
@click="addSample()"
icon="el-icon-plus"
type="small"
>Add Sample
>{{ $t('m.Add_Sample') }}
</el-button>
</div>
</div>
</el-row>
<el-form-item label="Source">
<el-form-item :label="$t('m.Source')">
<el-input
placeholder="Enter the problem where from"
:placeholder="$t('m.Source')"
v-model="problem.source"
></el-input>
</el-form-item>
<el-form-item
label="Auto Remove the Blank at the End of Code"
:label="$t('m.Auto_Remove_the_Blank_at_the_End_of_Code')"
v-if="!problem.isRemote"
>
<el-switch
@ -523,7 +557,7 @@
</el-form-item>
<el-form-item
label="Publish the Judging Result of Test Data"
:label="$t('m.Publish_the_Judging_Result_of_Test_Data')"
v-if="!problem.isRemote"
>
<el-switch
@ -534,23 +568,23 @@
</el-switch>
</el-form-item>
<el-button type="primary" @click.native="submit()" size="small"
>Save</el-button
>
<el-button type="primary" @click.native="submit()" size="small">{{
$t('m.Save')
}}</el-button>
</el-form>
</el-card>
</div>
</template>
<script>
import Editor from '@/components/admin/Editor';
import Accordion from '@/components/admin/Accordion';
import CodeMirror from '@/components/admin/CodeMirror';
import utils from '@/common/utils';
import { mapGetters } from 'vuex';
import api from '@/common/api';
import myMessage from '@/common/message';
import { PROBLEM_LEVEL_RESERVE } from '@/common/constants';
const Editor = () => import('@/components/admin/Editor.vue');
const Accordion = () => import('@/components/admin/Accordion.vue');
const CodeMirror = () => import('@/components/admin/CodeMirror.vue');
export default {
name: 'Problem',
components: {
@ -780,7 +814,7 @@ export default {
init() {
if (this.mode === 'edit') {
this.pid = this.$route.params.problemId;
this.title = 'Edit Problem';
this.title = this.$i18n.t('m.Edit_Problem');
let funcName = {
'admin-edit-problem': 'admin_getProblem',
'admin-edit-contest-problem': 'admin_getContestProblem',
@ -826,7 +860,7 @@ export default {
});
} else {
this.addExample();
this.title = 'Create Problem';
this.title = this.$i18n.t('m.Create_Problem');
for (let item of this.allLanguage) {
this.problemLanguages.push(item.name);
}
@ -848,15 +882,11 @@ export default {
switchSpj() {
if (this.testCaseUploaded) {
this.$confirm(
'如果你想改变该题目的判题方法,那么你需要重新上传测试数据。',
'注意',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
this.$confirm(this.$i18n.t('m.Change_Judge_Method'), 'Tips', {
confirmButtonText: this.$i18n.t('m.OK'),
cancelButtonText: this.$i18n.t('m.Cancel'),
type: 'warning',
})
.then(() => {
this.problem.spj = !this.problem.spj;
this.resetTestCase();
@ -890,7 +920,7 @@ export default {
selectTag(item) {
for (var i = 0; i < this.problemTags.length; i++) {
if (this.problemTags[i].name == item.value) {
myMessage.warning('该标签已添加,请不要重复添加!');
myMessage.warning(this.$i18n.t('m.Add_Tag_Error'));
this.tagInput = '';
return;
}
@ -904,7 +934,7 @@ export default {
if (this.tagInput) {
for (var i = 0; i < this.problemTags.length; i++) {
if (this.problemTags[i].name == this.tagInput) {
myMessage.warning('该标签已添加,请不要重复添加!');
myMessage.warning(this.$i18n.t('m.Add_Tag_Error'));
this.tagInput = '';
return;
}
@ -963,7 +993,7 @@ export default {
this.testCaseUploaded = false;
return;
}
myMessage.success('上传测试数据包成功');
myMessage.success(this.$i18n.t('m.Upload_Testcase_Successfully'));
let fileList = response.data.fileList;
let averSorce = (100 / fileList.length).toFixed(0);
@ -981,7 +1011,7 @@ export default {
this.problem.uploadTestcaseDir = response.data.fileListDir;
},
uploadFailed() {
myMessage.error('上传测试数据包失败');
myMessage.error(this.$i18n.t('m.Upload_Testcase_Failed'));
},
compileSPJ() {
@ -1016,42 +1046,62 @@ export default {
submit() {
if (!this.problem.problemId) {
myMessage.error('题目的展示ID不能为空');
myMessage.error(
this.$i18n.t('m.Problem_Display_ID') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
if (this.contestID) {
if (!this.contestProblem.displayId) {
myMessage.error('比赛题目的展示ID不能为空');
myMessage.error(
this.$i18n.t('m.Contest_Display_ID') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
if (!this.contestProblem.displayTitle) {
myMessage.error('比赛题目的展示标题不能为空!');
myMessage.error(
this.$i18n.t('m.Contest_Display_Title') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
}
if (!this.problem.examples.length) {
myMessage.error('题面测试数据是不能为空!至少输入一项!');
myMessage.error(
this.$i18n.t('m.Problem_Examples') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
for (let example of this.problem.examples) {
if (!example.input || !example.output) {
myMessage.error('每一项题面测试数据的输入输出都不能为空!');
return;
}
}
if (!this.problem.isRemote) {
//
if (!this.problem.isUploadCase) {
if (!this.problemSamples.length) {
myMessage.error('评测数据不能为空!请手动输入评测数据!');
myMessage.error(
this.$i18n.t('m.Judge_Samples') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
for (let sample of this.problemSamples) {
if (!sample.input && !sample.output) {
myMessage.error('每一项评测数据的输入和输出都不能同时为空!');
myMessage.error(
this.$i18n.t('m.Sample_Input') +
' or ' +
this.$i18n.t('m.Sample_Output') +
' ' +
this.$i18n.t('m.is_required')
);
return;
}
}
@ -1061,11 +1111,13 @@ export default {
for (let item of this.problemSamples) {
try {
if (parseInt(item.score) < 0) {
myMessage.error('测评得分小于0是无效的');
myMessage.error(
this.$i18n.t('m.Score_must_be_greater_than_or_equal_to_0')
);
return;
}
} catch (e) {
myMessage.error('测评得分的结果必须是整数类型!');
myMessage.error(this.$i18n.t('m.Score_must_be_an_integer'));
return;
}
}
@ -1075,7 +1127,10 @@ export default {
// createedittrueedit
if (!this.testCaseUploaded) {
this.error.testCase = '评测数据不能为空!请先上传评测数据!';
this.error.testCase =
this.$i18n.t('m.Judge_Samples') +
' ' +
this.$i18n.t('m.is_required');
myMessage.error(this.error.testCase);
return;
}
@ -1085,11 +1140,13 @@ export default {
for (let item of this.problem.testCaseScore) {
try {
if (parseInt(item.score) <= 0) {
myMessage.error('测评得分小于0是无效的');
myMessage.error(
this.$i18n.t('m.Score_must_be_greater_than_or_equal_to_0')
);
return;
}
} catch (e) {
myMessage.error('测评得分的结果必须是数字类型!');
myMessage.error(this.$i18n.t('m.Score_must_be_an_integer'));
return;
}
}
@ -1097,17 +1154,19 @@ export default {
}
}
if (!this.problemTags.length) {
this.error.tags = '请添加至少一个题目标签!';
this.error.tags =
this.$i18n.t('m.Tags') + ' ' + this.$i18n.t('m.is_required');
myMessage.error(this.error.tags);
return;
}
if (this.problem.spj) {
if (!this.problem.spjCode) {
this.error.spj = '特殊判题的程序代码不能为空!';
this.error.spj =
this.$i18n.t('m.Spj_Code') + ' ' + this.$i18n.t('m.is_required');
myMessage.error(this.error.spj);
} else if (!this.problem.spjCompileOk) {
this.error.spj = '特殊判题的程序没有编译成功,请重新编译!';
this.error.spj = this.$i18n.t('m.Spj_Code_not_Compile_Success');
}
if (this.error.spj) {
myMessage.error(this.error.spj);
@ -1116,7 +1175,8 @@ export default {
}
if (!this.problemLanguages.length) {
this.error.languages = '请至少给题目选择一项编程语言!';
this.error.languages =
this.$i18n.t('m.Language') + ' ' + this.$i18n.t('m.is_required');
myMessage.error(this.error.languages);
return;
}
@ -1211,14 +1271,14 @@ export default {
this.contestProblem['cid'] = this.$route.params.contestId;
}
api.admin_setContestProblemInfo(this.contestProblem).then((res) => {
myMessage.success(res.data.msg);
myMessage.success('success');
this.$router.push({
name: 'admin-contest-problem-list',
params: { contestId: this.$route.params.contestId },
});
});
} else {
myMessage.success(res.data.msg);
myMessage.success('success');
this.$router.push({ name: 'admin-problem-list' });
}
})

View File

@ -3,7 +3,7 @@
<el-card>
<div slot="header">
<span class="panel-title home-title">{{
contestId ? 'Contest Problem List' : 'Problem List'
contestId ? $t('m.Contest_Problem_List') : $t('m.Problem_List')
}}</span>
<div class="filter-row">
<span>
@ -12,7 +12,7 @@
size="small"
@click="goCreateProblem"
icon="el-icon-plus"
>Create
>{{ $t('m.Create') }}
</el-button>
<el-button
type="success"
@ -20,7 +20,7 @@
v-if="!contestId"
@click="AddRemoteOJProblemDialogVisible = true"
icon="el-icon-plus"
>Add Remote OJ Problem
>{{ $t('m.Add_Rmote_OJ_Problem') }}
</el-button>
<el-button
v-if="contestId"
@ -28,13 +28,13 @@
size="small"
icon="el-icon-plus"
@click="addProblemDialogVisible = true"
>Add From Public Problem
>{{ $t('m.Add_From_Public_Problem') }}
</el-button>
</span>
<span>
<vxe-input
v-model="keyword"
placeholder="Enter keyword"
:placeholder="$t('m.Enter_keyword')"
type="search"
size="medium"
@search-click="filterByKeyword"
@ -54,16 +54,24 @@
>
<vxe-table-column min-width="100" field="problemId" title="ID">
</vxe-table-column>
<vxe-table-column field="title" min-width="150" title="Title">
<vxe-table-column field="title" min-width="150" :title="$t('m.Title')">
</vxe-table-column>
<vxe-table-column field="author" min-width="150" title="Author">
<vxe-table-column
field="author"
min-width="150"
:title="$t('m.Author')"
>
</vxe-table-column>
<vxe-table-column min-width="150" field="gmtCreate" title="Create Time">
<vxe-table-column
min-width="150"
field="gmtCreate"
:title="$t('m.Created_Time')"
>
<template v-slot="{ row }">
{{ row.gmtCreate | localtime }}
</template>
</vxe-table-column>
<vxe-table-column min-width="130" field="auth" title="Auth">
<vxe-table-column min-width="130" field="auth" :title="$t('m.Auth')">
<template v-slot="{ row }">
<el-select
v-model="row.auth"
@ -71,10 +79,13 @@
size="small"
:disabled="row.auth == 3 && !contestId"
>
<el-option label="公开" :value="1"></el-option>
<el-option label="私有" :value="2"></el-option>
<el-option :label="$t('m.Public_Problem')" :value="1"></el-option>
<el-option
label="比赛题目"
:label="$t('m.Private_Problem')"
:value="2"
></el-option>
<el-option
:label="$t('m.Contest_Problem')"
:value="3"
:disabled="!contestId"
></el-option>
@ -83,7 +94,7 @@
</vxe-table-column>
<vxe-table-column title="Option" min-width="200">
<template v-slot="{ row }">
<el-tooltip effect="dark" content="编辑题目" placement="top">
<el-tooltip effect="dark" :content="$t('m.Edit')" placement="top">
<el-button
icon="el-icon-edit-outline"
size="mini"
@ -93,7 +104,11 @@
</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="下载测试样例" placement="top">
<el-tooltip
effect="dark"
:content="$t('m.Download_Testcase')"
placement="top"
>
<el-button
icon="el-icon-download"
size="mini"
@ -103,7 +118,7 @@
</el-button>
</el-tooltip>
<el-tooltip effect="dark" content="删除题目" placement="top">
<el-tooltip effect="dark" :content="$t('m.Delete')" placement="top">
<el-button
icon="el-icon-delete-solid"
size="mini"
@ -129,7 +144,7 @@
</el-card>
<el-dialog
title="Add Contest Problem"
:title="$t('m.Add_Contest_Problem')"
v-if="contestId"
width="90%"
:visible.sync="addProblemDialogVisible"
@ -142,13 +157,13 @@
</el-dialog>
<el-dialog
title="Add Remote OJ Problem"
:title="$t('m.Add_Rmote_OJ_Problem')"
width="350px"
:visible.sync="AddRemoteOJProblemDialogVisible"
@close-on-click-modal="false"
>
<el-form>
<el-form-item label="Remote OJ">
<el-form-item :label="$t('m.Remote_OJ')">
<el-select v-model="otherOJName" size="small">
<el-option
:label="remoteOj.name"
@ -158,7 +173,7 @@
></el-option>
</el-select>
</el-form-item>
<el-form-item label="Problem ID">
<el-form-item :label="$t('m.Problem_ID')">
<el-input v-model="otherOJProblemId" size="small"></el-input>
</el-form-item>
<el-form-item style="text-align:center">
@ -167,7 +182,7 @@
icon="el-icon-plus"
@click="addRemoteOJProblem"
:loading="addRemoteOJproblemLoading"
>Add
>{{ $t('m.Add') }}
</el-button>
</el-form-item>
</el-form>
@ -274,18 +289,14 @@ export default {
changeProblemAuth(row) {
api.admin_changeProblemPublic(row).then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
});
},
deleteProblem(id) {
this.$confirm(
'确定要删除此问题吗?注意:该问题的相关提交数据也将被删除。',
'删除题目',
{
type: 'warning',
}
).then(
this.$confirm(this.$i18n.t('m.Delete_Problem_Tips'), 'Tips', {
type: 'warning',
}).then(
() => {
let funcName =
this.routeName === 'admin-problem-list'
@ -293,7 +304,7 @@ export default {
: 'admin_deleteContestProblem';
api[funcName](id)
.then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Delete_Successfully'));
this.getProblemList(this.currentPage);
})
.catch(() => {});
@ -312,7 +323,7 @@ export default {
}
api[funcName](data)
.then((res) => {
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Update_Successfully'));
this.getProblemList(this.currentPage);
})
.catch(() => {});
@ -320,7 +331,7 @@ export default {
downloadTestCase(problemID) {
let url = '/api/file/download-testcase?pid=' + problemID;
utils.downloadFile(url).then(() => {
this.$alert('该题目的测试样例已成功下载!', '提醒');
this.$alert(this.$i18n.t('m.Download_Testcase_Success'), 'Tips');
});
},
filterByKeyword() {
@ -334,7 +345,7 @@ export default {
(res) => {
this.addRemoteOJproblemLoading = false;
this.AddRemoteOJProblemDialogVisible = false;
myMessage.success(res.data.msg);
myMessage.success(this.$i18n.t('m.Add_Successfully'));
this.currentChange(1);
},
(err) => {

View File

@ -207,11 +207,11 @@
<script>
import time from '@/common/time';
import api from '@/common/api';
import Announcements from '@/components/oj/common/Announcements.vue';
import { CONTEST_STATUS_REVERSE } from '@/common/constants';
import { mapState } from 'vuex';
import { addCodeBtn } from '@/common/codeblock';
import Avatar from 'vue-avatar';
const Announcements = () => import('@/components/oj/common/Announcements.vue');
export default {
name: 'home',
components: {

View File

@ -106,9 +106,9 @@
<script>
import utils from '@/common/utils';
import Highlight from '@/components/oj/common/Highlight';
import { JUDGE_STATUS } from '@/common/constants';
import { addCodeBtn } from '@/common/codeblock';
const Highlight = () => import('@/components/oj/common/Highlight');
export default {
components: {
Highlight,

View File

@ -197,15 +197,13 @@
import api from '@/common/api';
import { mapGetters } from 'vuex';
import utils from '@/common/utils';
import Pagination from '@/components/oj/common/Pagination';
import time from '@/common/time';
import {
CONTEST_STATUS_REVERSE,
CONTEST_TYPE,
CONTEST_TYPE_REVERSE,
} from '@/common/constants';
import myMessage from '@/common/message';
const Pagination = () => import('@/components/oj/common/Pagination');
const limit = 10;
export default {
@ -278,7 +276,7 @@ export default {
this.filterByChange();
},
toContest(contest) {
if (contest.type !== CONTEST_TYPE.PUBLIC && !this.isAuthenticated) {
if (!this.isAuthenticated) {
myMessage.warning(this.$i18n.t('m.Please_login_first'));
this.$store.dispatch('changeModalStatus', { visible: true });
} else {

View File

@ -145,8 +145,7 @@
<script>
import moment from 'moment';
import { mapActions } from 'vuex';
import Pagination from '@/components/oj/common/Pagination';
const Pagination = () => import('@/components/oj/common/Pagination');
import time from '@/common/time';
import utils from '@/common/utils';
import ContestRankMixin from './contestRankMixin';

View File

@ -106,10 +106,9 @@
</template>
<script>
import { mapState } from 'vuex';
import Pagination from '@/components/oj/common/Pagination.vue';
import api from '@/common/api';
import myMessage from '@/common/message';
const Pagination = () => import('@/components/oj/common/Pagination');
export default {
name: 'ACM-Info-Admin',
components: {

View File

@ -2,7 +2,6 @@
<comment :cid="$route.params.contestID"></comment>
</template>
<script>
import comment from '@/components/oj/comment/comment';
export default {
name: 'ContestComment',
components: {

View File

@ -6,10 +6,9 @@
<script>
import { mapGetters } from 'vuex';
import ACMContestRank from './ACMContestRank.vue';
import OIContestRank from './OIContestRank.vue';
import { RULE_TYPE } from '@/common/constants';
const ACMContestRank = () => import('./ACMContestRank.vue');
const OIContestRank = () => import('./OIContestRank.vue');
const NullComponent = {
name: 'null-component',
template: '<div></div>',

View File

@ -129,10 +129,9 @@
</template>
<script>
import { mapActions } from 'vuex';
import Pagination from '@/components/oj/common/Pagination';
import ContestRankMixin from './contestRankMixin';
import utils from '@/common/utils';
const Pagination = () => import('@/components/oj/common/Pagination');
export default {
name: 'OIContestRank',
components: {

View File

@ -202,14 +202,13 @@
</template>
<script>
import comment from '@/components/oj/comment/comment';
import api from '@/common/api';
import myMessage from '@/common/message';
import { addCodeBtn } from '@/common/codeblock';
import Avatar from 'vue-avatar';
import { mapGetters, mapActions } from 'vuex';
import Editor from '@/components/admin/Editor.vue';
const Editor = () => import('@/components/admin/Editor.vue');
const comment = () => import('@/components/oj/comment/comment');
export default {
components: {
comment,

View File

@ -354,11 +354,12 @@
</template>
<script>
import Avatar from 'vue-avatar';
import Pagination from '@/components/oj/common/Pagination';
import Editor from '@/components/admin/Editor.vue';
import api from '@/common/api';
import myMessage from '@/common/message';
import { mapGetters, mapActions } from 'vuex';
import 'element-ui/lib/theme-chalk/display.css';
import Pagination from '@/components/oj/common/Pagination';
const Editor = () => import('@/components/admin/Editor.vue');
export default {
components: {
Avatar,

View File

@ -355,7 +355,6 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import CodeMirror from '@/components/oj/common/CodeMirror.vue';
import storage from '@/common/storage';
import utils from '@/common/utils';
import {
@ -370,6 +369,7 @@ import { pie, largePie } from './chartData';
import api from '@/common/api';
import myMessage from '@/common/message';
import { addCodeBtn } from '@/common/codeblock';
const CodeMirror = () => import('@/components/oj/common/CodeMirror.vue');
//
const filtedStatus = ['wa', 'ce', 'ac', 'pa', 'tle', 'mle', 're', 'pe'];

View File

@ -262,9 +262,9 @@ import {
JUDGE_STATUS_RESERVE,
REMOTE_OJ,
} from '@/common/constants';
import Pagination from '@/components/oj/common/Pagination';
import myMessage from '@/common/message';
import 'element-ui/lib/theme-chalk/display.css';
import Pagination from '@/components/oj/common/Pagination';
export default {
name: 'ProblemList',
components: {

View File

@ -81,11 +81,10 @@
<script>
import api from '@/common/api';
import Pagination from '@/components/oj/common/Pagination';
import utils from '@/common/utils';
import { RULE_TYPE } from '@/common/constants';
import { mapGetters } from 'vuex';
const Pagination = () => import('@/components/oj/common/Pagination');
export default {
name: 'acm-rank',
components: {

View File

@ -80,11 +80,10 @@
<script>
import api from '@/common/api';
import Pagination from '@/components/oj/common/Pagination';
import utils from '@/common/utils';
import { RULE_TYPE } from '@/common/constants';
import { mapGetters } from 'vuex';
const Pagination = () => import('@/components/oj/common/Pagination');
export default {
name: 'acm-rank',
components: {

View File

@ -208,9 +208,11 @@
import api from '@/common/api';
import { JUDGE_STATUS, JUDGE_STATUS_RESERVE } from '@/common/constants';
import utils from '@/common/utils';
import Highlight from '@/components/oj/common/Highlight';
import myMessage from '@/common/message';
import { addCodeBtn } from '@/common/codeblock';
const Highlight = () => import('@/components/oj/common/Highlight');
export default {
name: 'submissionDetails',
components: {

View File

@ -308,6 +308,7 @@ import {
import utils from '@/common/utils';
import Pagination from '@/components/oj/common/Pagination';
import myMessage from '@/common/message';
import 'element-ui/lib/theme-chalk/display.css';
export default {
name: 'submissionList',
components: {

View File

@ -17,8 +17,8 @@
</el-card>
</template>
<script>
import Account from '@/components/oj/setting/Account';
import UserInfo from '@/components/oj/setting/UserInfo';
const Account = () => import('@/components/oj/setting/Account');
const UserInfo = () => import('@/components/oj/setting/UserInfo');
export default {
components: {
Account,