Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Question and Answer #83

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/components/ClarificationComment.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<template>
<div class="ClarificationComment">
<div class="CommentHeader">
<a class="username">{{username}}</a> <p style="display: inline-block; margin-left: 5px">{{time}}</p>
<Dropdown placement="bottom-end" class="options" @on-click="selectMenu">
<a>...</a>
<DropdownMenu slot="list">
<DropdownItem name="QA" v-if="isAdmin || isPublic === CLARIFICATION_TYPE.UNPUBLIC">Quick Answer</DropdownItem>
<DropdownItem name="Edit" v-if="isAdmin">Edit</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
<div class="markdown">
<div class="replyTarget" v-if="replyRoot && replyRoot.message">
<p style="margin-left: 5px; margin-bottom: 5px">Reply to:</p>
<div class="reply">
<div class="CommentHeader">
<a class="username">{{replyRoot.username}}</a> <p style="display: inline-block; margin-left: 5px">{{replyRoot.gmtCreate | fromnow}}</p>
</div>
<Markdown :value="replyRoot.message" style="min-height: 50px;" />
</div>
</div>
<Markdown :value="ClarificationContent" style="min-height: 100px; border-top: none" />
</div>
</div>
</template>

<script>
import Markdown from './editor/Markdown';
import { mapGetters } from 'vuex';
import moment from 'moment';
import { CLARIFICATION_TYPE } from '_u/constants';
export default {
name: 'ClarificationComment',
components: { Markdown },
filters: {
fromnow: function(timestamp) {
if (typeof (timestamp) === 'string') {
timestamp = parseInt(timestamp);
}
return moment(new Date(timestamp)).format('YYYY-MM-DD hh:mm:ss');
}
},
props: [
'username',
'time',
'ClarificationContent',
'contestClarificationId',
'isPublic',
'replyRoot'
],
methods: {
selectMenu(name) {
if (name === 'QA') {
this.$emit('on-reply')
}
if (name === 'Edit') {
this.$emit('on-edit', {
clarification: this.ClarificationContent,
id: this.contestClarificationId
})
}
}
},
computed: {
...mapGetters('user', ['isAdmin']),
CLARIFICATION_TYPE: () => CLARIFICATION_TYPE
}
}
</script>

<style scoped>
.ClarificationComment {
margin: 10px;
border: #e1e4e8 1px solid;
}

.CommentHeader {
background: #f6f8fa;
padding-left: 2%;
height: 40px;
line-height: 40px;
position: relative;
border-bottom: #e1e4e8 1px solid;
box-sizing: border-box;
}

.ClarificationComment {
background: #fff
}

.CommentHeader .options{
display: inline-block;
position: absolute;
right: 3%;
font-size: 150%;
}

.username {
border-bottom: 1px solid;
}

.replyTarget {
padding: 20px 20px;
border-bottom: none;
}

.reply {
border: #e1e4e8 1px solid;
}

/deep/ .v-note-wrapper {
z-index: 0 !important;
}

/deep/ .v-show-content {
background-color: #fff !important;
}

</style>
207 changes: 207 additions & 0 deletions src/components/editor/MarkdownEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<template>
<div>
<details style="margin-bottom: 5px" v-show="false">
<summary>Upload File Attachment</summary>
<Upload
multiple
paste
type="drag"
:max-size="102400"
:file-list.sync="fileList"
ref="upload">
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
<p>Click or drag files here to upload</p>
</div>
</Upload>
<Button size="small" type="info" @click="attachAdd">Add</Button>
</details>
<mavon-editor
ref="md"
@imgAdd="$imgAdd"
v-model="markdown"
:externalLink="externalLink"
:toolbars="toolbars"
style="min-height: 600px;"/>
</div>
</template>

<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'

import Upload from '_c/upload/upload';

import api from '_u/api';
import { SDUOJ_ENV } from '_u/env';

// 替换 mavonEditor 的 $img2Url 方法
// 占位符识别 <img src="fileIndex" ...
function $img2Url(fileIndex, url) {
// <img src="1"
var reg_str = '/\\<img src=\\"' + fileIndex + '\\"/g';
/* eslint-disable */
var reg = eval(reg_str);
this.d_value = this.d_value.replace(reg, '<img src="' + url + '"');
this.$refs.toolbar_left.$changeUrl(fileIndex, url);
this.iRender();
}

// 替换 mavonEditor 的 $imgAdd 方法
// 生产的 markdown 图片为 html 中的 img 标签,不再是 ![]()
function $imgAdd(pos, $file, isinsert) {
if (isinsert === undefined) isinsert = true;
var $vm = this;
if (this.__rFilter == null) {
this.__rFilter = /^image\//i;
}
this.__oFReader = new FileReader();
this.__oFReader.onload = function (oFREvent) {
$vm.markdownIt.image_add(pos, oFREvent.target.result);
$file.miniurl = oFREvent.target.result;
if (isinsert === true) {
// 去除特殊字符
/* eslint-disable no-useless-escape */
$file._name = $file.name.replace(/[\[\]\(\)\+\{\}&\|\\\*^%$#@\-]/g, '');

$vm.insertText($vm.getTextareaDom(),
{
prefix: `<img src="${pos}" alt="${$file._name}" style="zoom:100%;" />`,
subfix: '',
str: ''
});
$vm.$nextTick(function () {
$vm.$emit('imgAdd', pos, $file);
})
}
}
if ($file) {
var oFile = $file;
if (this.__rFilter.test(oFile.type)) {
this.__oFReader.readAsDataURL(oFile);
}
}
}

export default {
name: 'MarkdownEditor',
components: { mavonEditor, Upload },
data: function() {
const externalLink = SDUOJ_ENV.PROD ? {
markdown_css: function () {
// 这是你的markdown css文件路径
return '/markdown/github-markdown.min.css';
},
hljs_js: function () {
// 这是你的hljs文件路径
return '/highlightjs/highlight.min.js';
},
hljs_css: function (css) {
// 这是你的代码高亮配色文件路径
return '/highlightjs/styles/' + css + '.min.css';
},
hljs_lang: function (lang) {
// 这是你的代码高亮语言解析路径
return '/highlightjs/languages/' + lang + '.min.js';
},
katex_css: function () {
// 这是你的katex配色方案路径路径
return '/katex/katex.min.css';
},
katex_js: function () {
// 这是你的katex.js路径
return '/katex/katex.min.js';
}
} : true;
return {
externalLink,
markdown: '',
fileList: [],
toolbars: {
bold: true, // 粗体
italic: true, // 斜体
superscript: true, // 上角标
subscript: true, // 下角标
code: true, // code
fullscreen: true, // 全屏编辑
/* 1.3.5 */
undo: true, // 上一步
redo: true, // 下一步
preview: true, // 预览
}
}
},
methods: {
$imgAdd: function(pos, file) {
const formdata = new FormData();
formdata.append('files', file);
api.multiUpload(formdata).then(ret => {
const $vm = this.$refs.md;
$vm.$img2Url(pos, `/api/filesys/download/${ret[0].id}/${ret[0].name}`);
$vm.$refs.toolbar_left.img_file = [[0, null]];
$vm.$refs.toolbar_left.num = 0;
}, err => {
this.$Message.error(err.message);
})
},
$attachAdd: function(files) {
return new Promise((resolve, reject) => {
const formdata = new FormData();
files.forEach(file => {
formdata.append('files', file.file)
});
api.multiUpload(formdata).then(ret => {
ret.forEach(o => {
const $vm = this.$refs.md;
// 去除特殊字符
/* eslint-disable no-useless-escape */
const _name = o.name.replace(/[\[\]\(\)\+\{\}&\|\\\*^%$#@\-]/g, '');
$vm.insertText($vm.getTextareaDom(),
{
prefix: '[' + _name + '](' + `/api/filesys/download/${o.id}/${o.name.replace(' ', '_')}` + ')',
subfix: '',
str: ''
}
);
});
resolve();
}, reject);
})
},
$clear: function() {
this.$refs.upload.clearFiles();
const $vm = this.$refs.md;
$vm.$refs.toolbar_left.img_file = [[0, null]];
$vm.$refs.toolbar_left.num = 0;
},
attachAdd: function () {
const removeLoading = this.$Message.loading({
content: 'Uploading',
duration: 0
});
this.$attachAdd(this.fileList).then(() => {
this.$refs.upload.clearFiles();
}).catch(err => {
this.$Message.error(err.message);
}).finally(() => {
removeLoading();
});
},
setMarkdown: function(markdown) {
this.markdown = markdown;
},
getMarkdown: function () {
return this.markdown;
}
},
mounted: function() {
const $vm = this.$refs.md;
$vm.$imgAdd = $imgAdd.bind($vm);
$vm.$img2Url = $img2Url.bind($vm);
}
}
</script>

<style scoped>

</style>
6 changes: 6 additions & 0 deletions src/router/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ const routes = [
path: 'rank',
meta: { scrollToTop: true },
component: () => import('@/views/contest/ContestRankView')
},
{
name: 'contest-clarification',
path: 'clarification',
meta: { scrollToTop: true },
component: () => import('@/views/contest/ContestClarificationView')
}
]
},
Expand Down
4 changes: 4 additions & 0 deletions src/store/modules/contest.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ const getters = {
}
},

template: (state) => {
return state.contest.template
},

submitUserNum: (state, getters) => {
const sum = Array(state.problems.length);
for (let j = 0; j < state.problems.length; j++) sum[j] = 0;
Expand Down
23 changes: 23 additions & 0 deletions src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ axios.defaults.withCredentials = true;

function post(url, data) {
data = data || {};

return new Promise((resolve, reject) => {
axios.post(url, data)
.then(response => {
Expand Down Expand Up @@ -274,6 +275,28 @@ export default {
singleUpload: function(data) {
return post('/filesys/upload', data);
},
/* QA part */
createQuestion: function(data) {
return post('/contest/createQuestion', data)
},
createReply: function (data) {
return post('/contest/reply', data)
},
getQuestion: function (data) {
return get('/contest/listQuestion', data)
},
getQuestionDetail: function (data) {
return get('/contest/questionDetail', data)
},
deleteQuestion: function (data) {
return get('/contest/delete', data)
},
editReply: function (data) {
return get('/manage/contest/edit', data)
},
setPublic: function (data) {
return post('/manage/contest/publicIt', data)
},
/* third-party-login */
thirdPartyLogin: function(params) {
return get('/user/thirdPartyLogin', params);
Expand Down
5 changes: 5 additions & 0 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,8 @@ export const GROUP_STATUS_TYPE = {
JOINED: 2,
REJECTED: 3
};

export const CLARIFICATION_TYPE = {
UNPUBLIC: 'UNPUBLIC',
PUBLIC: 'PUBLIC'
}
Loading