Sun, 18 Apr 2021 22:00:56 GMT

master
大蒟蒻 5 years ago
parent 8477ee9fcf
commit b575aa3c9e

@ -0,0 +1,9 @@
{
"presets": [
"@parcel/babel-preset-env"
],
"plugins": [
"@parcel/babel-plugin-transform-runtime",
"@babel/plugin-transform-typescript"
]
}

148
.gitignore vendored

@ -0,0 +1,148 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,node,vuejs
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,node,vuejs
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env*.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Storybook build outputs
.out
.storybook-out
storybook-static
# rollup.js default build output
dist/
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# Temporary folders
tmp/
temp/
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Vuejs ###
# Recommended template: Node.gitignore
npm-debug.log
yarn-error.log
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node,vuejs

25624
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,29 @@
{
"name": "cugoj-ng-client",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build --no-scope-hoist --no-cache --no-source-maps --public-url . src/index.html --dist-dir release",
"clean": "rimraf *cache dist release"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@babel/preset-typescript": "^7.13.0",
"@parcel/babel-plugin-transform-runtime": "*",
"bootstrap": "^5.0.0-beta3",
"element-plus": "*",
"mathjax": "^3.1.2",
"monaco-editor": "^0.23.0",
"vue": "^3.0.11",
"vue-router": "^4.0.6"
},
"devDependencies": {
"@parcel/transformer-vue": "2.0.0-beta.2",
"parcel": "*",
"rimraf": "^3.0.2"
}
}

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id='app'>
<router-view />
</div>
<script src="index.ts" async></script>
</body>
</html>

@ -0,0 +1,14 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import 'bootstrap/dist/css/bootstrap-grid.css'
import 'bootstrap/dist/css/bootstrap-reboot.css'
import 'bootstrap/dist/css/bootstrap-utilities.css'
import 'element-plus/lib/theme-chalk/index.css';
import router from './router'
createApp({})
.use(router)
.use(ElementPlus)
.mount('#app');

@ -0,0 +1,35 @@
<template>
<header>
<el-menu :default-active="current" mode="horizontal" router class="d-flex">
<el-menu-item index="/"> CUGOJ </el-menu-item>
<el-menu-item index="/problemlist">题目列表 </el-menu-item>
<div id="placeholder"></div>
<el-menu-item index="/login"> {{ user_display }} </el-menu-item>
</el-menu>
</header>
</template>
<script>
import { currentUser } from "../views/user";
export default {
props: ["current"],
data() {
return {
user_display: null,
};
},
async created() {
this.user_display = (await currentUser()) ?? "登录";
},
};
</script>
<style>
header {
padding-bottom: 2em;
}
#placeholder {
flex: 1;
visibility: hidden;
}
</style>

@ -0,0 +1,36 @@
import { createRouter, createWebHashHistory } from 'vue-router'
export default createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/login',
component: () => import('./views/Login.vue')
},
{
path: '/problemlist',
redirect: '/problemlist/1'
},
{
path: '/problemlist/:page',
component: () => import('./views/ProblemList.vue')
},
{
path: '/problem/:pid',
component: () => import('./views/Problem.vue')
},
{
path: '/submit/:pid',
component: () => import('./views/Submit.vue')
},
{
path: '/submit/:cid/:pid',
component: () => import('./views/Submit.vue')
},
]
})

@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

@ -0,0 +1,13 @@
import "mathjax/es5/tex-svg-full";
export function mathjax_load() {
if (!window.MathJax.config.tex.inlineMath) {
window.MathJax.config.tex.inlineMath = [
["$", "$"],
["\\(", "\\)"],
];
window.MathJax.startup.getComponents();
}
}
export function mathjax_typeset() {
window.MathJax.typeset();
}

@ -0,0 +1,4 @@
interface Window {
MonacoEnvironment: any;
MathJax: any;
}

@ -0,0 +1,67 @@
<template>
<el-container direction="vertical">
<navi :current="nav_path"></navi>
<div class="container">
<el-skeleton :rows="15" animated v-if="!ready" />
<el-main v-else>
<div v-if="loggedin">
<h1>{{ user }}</h1>
</div>
<div v-else>
<h1>You are not logged in</h1>
</div>
<div>
<el-card
v-for="(news, idx) in news_list"
:key="idx"
style="margin-bottom: 2rem"
>
<template #header>
<div class="card-header">
{{ news.title }}
<span style="font-size: 50%; margin-left: 1rem">
- [{{ news.author }}]
</span>
</div>
</template>
<div v-html="news.content"></div>
</el-card>
</div>
</el-main>
</div>
</el-container>
</template>
<script lang="ts">
import navi from "../parts/nav.vue";
export default {
components: { navi },
data() {
return {
nav_path: "/",
ready: false,
loggedin: false,
user: null,
news_list: [],
};
},
async created() {
document.title = "CUGOJ - Home";
let news = fetch("/api/News/List");
let res = await fetch("/api/User/WhoAmI").then((x) => x.json());
if (res.user) {
this.user = res.user;
this.loggedin = true;
}
this.news_list = await news.then((x) => x.json());
this.ready = true;
},
};
</script>
<style>
#lst {
width: fit-content;
}
</style>

@ -0,0 +1,60 @@
<template>
<navi :current="nav_path"></navi>
<div class="container">
<el-form label-width="80px" id="loginForm">
<el-form-item label="用户名">
<el-input v-model="user.name" name="username"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="user.pass"
name="password"
type="password"
show-password
>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import navi from "../parts/nav.vue";
import { ElMessage } from "element-plus";
export default {
components: { navi },
data() {
return {
nav_path: "/login",
user: {},
};
},
methods: {
async login() {
let loginForm = document.querySelector<HTMLFormElement>("#loginForm")!;
let postData = new FormData(loginForm);
let res = await fetch("/api/User/Login", {
method: "POST",
body: postData,
});
if (res.status == 200) {
ElMessage({
message: "登录成功",
type: "success",
duration: 2000,
onClose: () => this.$router.back(),
});
} else {
ElMessage.error(await res.text());
}
},
},
};
</script>
<style>
</style>

@ -0,0 +1,144 @@
<template>
<navi></navi>
<div v-loading.fullscreen.lock="!ready"></div>
<div class="container" v-if="ready">
<div class="text-center">
<h2 v-html="`${problem.problem_id}: ${problem.title}`"></h2>
<span>时间限制{{ problem.time_limit }}s</span>
<el-divider direction="vertical"></el-divider>
<span>内存限制{{ problem.memory_limit }}MB</span>
<el-divider direction="vertical"></el-divider>
<span>通过{{ problem.accepted }}/{{ problem.submit }}</span>
<br />
<router-link
custom
:to="`/submit/${problem.problem_id}`"
v-slot="{ href }"
>
<el-link icon="el-icon-edit" type="primary" :href="href">
提交
</el-link>
</router-link>
<el-divider direction="vertical"></el-divider>
<el-link
icon="el-icon-view"
type="primary"
:href="`http://acm.cug.edu.cn/status.php?problem_id=${pid}`"
>
状态
</el-link>
<el-divider></el-divider>
</div>
<el-card class="my-3">
<template #header>
<h5>题目描述</h5>
</template>
<div v-html="problem.description"></div>
</el-card>
<el-card class="my-3">
<template #header>
<h5>输入</h5>
</template>
<div v-html="problem.input"></div>
</el-card>
<el-card class="my-3">
<template #header>
<h5>输出</h5>
</template>
<div v-html="problem.output"></div>
</el-card>
<el-card class="my-3">
<template #header>
<h5>样例</h5>
</template>
<div class="position-relative">
<span class="border-caption">样例输入</span>
<pre v-html="problem.sample_input"></pre>
</div>
<div style="height: 1rem"></div>
<div class="position-relative">
<span class="border-caption">样例输出</span>
<pre v-html="problem.sample_output"></pre>
</div>
</el-card>
<el-card class="my-3">
<template #header>
<h5>提示</h5>
</template>
<div v-html="problem.hint"></div>
</el-card>
</div>
</template>
<script lang="ts">
import navi from "../parts/nav.vue";
import { ElMessage } from "element-plus";
import { mathjax_typeset, mathjax_load } from "../utils/mathjax";
export default {
components: { navi },
data() {
return {
ready: false,
problem: {} as any,
};
},
created() {
mathjax_load();
},
updated() {
mathjax_typeset();
},
computed: {
pid() {
return this.$route.params.pid;
},
},
watch: {
"$route.params": {
handler({ pid }) {
pid && this.fetchProblem(pid);
},
immediate: true,
},
},
methods: {
async fetchProblem(pid: number) {
this.ready = false;
let res = await fetch(`/api/Problem/${pid}`);
if (res.status == 200) {
this.problem = await res.json();
document.title = this.problem.title;
} else {
ElMessage({
message: "题目不存在",
type: "error",
duration: 2000,
onClose: () => this.$router.back(),
});
}
this.ready = true;
},
},
};
</script>
<style>
.border-caption {
font-size: 0.8rem;
position: absolute;
left: 1rem;
top: -0.5rem;
background: white;
padding: 0 0.2rem;
}
pre {
border: 1px solid lightgrey;
border-radius: 0.3rem;
padding: 1rem;
margin: 0 0.1rem;
}
#affix-submit {
right: 3rem;
bottom: 3rem;
}
</style>

@ -0,0 +1,94 @@
<template>
<navi :current="nav_path"></navi>
<div class="container">
<el-pagination
class="d-flex justify-content-center"
layout="prev, pager, next"
:page-count="totalPages"
:current-page="curPage"
@current-change="changePage"
>
</el-pagination>
<el-table :data="problemList" stripe>
<el-table-column
prop="problem_id"
label="题目编号"
width="80"
align="right"
>
</el-table-column>
<el-table-column label="标题">
<template #default="scope">
<router-link
custom
:to="`/problem/${scope.row.problem_id}`"
v-slot="{ href }"
>
<el-link type="primary" target="_blank" :href="href">
{{ scope.row.title }}
</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column prop="submit" label="提交" width="60"> </el-table-column>
<el-table-column prop="accepted" label="通过" width="60">
</el-table-column>
</el-table>
<div class="m-4"></div>
<el-pagination
class="d-flex justify-content-center"
layout="prev, pager, next"
:page-count="totalPages"
:current-page="curPage"
@current-change="changePage"
>
</el-pagination>
</div>
</template>
<script lang="ts">
import navi from "../parts/nav.vue";
export default {
components: { navi },
data() {
return {
nav_path: "/problemlist",
ready: false,
problemList: [],
totalPages: null,
};
},
computed: {
curPage() {
return parseInt(this.$route.params.page as string);
},
},
async created() {
document.title = "CUGOJ - ProblemList";
},
watch: {
"$route.params": {
handler({ page }) {
page && this.fetchProblemList(parseInt(page));
},
immediate: true,
},
},
methods: {
async fetchProblemList(page: number) {
this.ready = false;
let problemListRequest = fetch(`/api/Problem/List/${page}`);
let res = await problemListRequest.then((x) => x.json());
this.problemList = res.problemList;
this.totalPages = res.totalPages;
this.ready = true;
},
changePage(newPage: number) {
this.$router.push({ params: { page: newPage } });
},
},
};
</script>
<style>
</style>

@ -0,0 +1,93 @@
<template>
<navi></navi>
<div class="container">
<el-row>
<el-col :md="12" class="d-flex px-3" style="align-items: center">
<b>Problem {{ pid }}</b>
</el-col>
<el-col
:md="12"
class="d-flex px-3"
style="align-items: center; justify-content: flex-end"
>
<el-space wrap>
<span>语言</span>
<el-select v-model="lang" placeholder="请选择">
<el-option v-for="l in langs" :key="l" :value="l"> </el-option>
</el-select>
<el-button type="primary" @click="submit"></el-button>
</el-space>
</el-col>
</el-row>
<el-divider></el-divider>
<div id="codeblock" style="height: 80vh"></div>
<el-divider></el-divider>
</div>
</template>
<script lang="ts">
import * as monaco from "monaco-editor";
import navi from "../parts/nav.vue";
import { ElNotification } from "element-plus";
let editor = {} as monaco.editor.IStandaloneCodeEditor;
export default {
components: { navi },
data() {
return {
langs: ["C", "CPP", "Java", "Python"],
lang: localStorage.getItem("preferedLang") ?? "CPP",
};
},
computed: {
pid() {
return this.$route.params.pid;
},
},
watch: {
lang() {
localStorage.setItem("preferedLang", this.lang);
monaco.editor.setModelLanguage(
editor.getModel()!,
this.lang.toLowerCase()
);
},
},
mounted() {
editor = monaco.editor.create(document.querySelector("#codeblock")!, {
language: this.lang.toLowerCase(),
automaticLayout: true,
});
},
methods: {
async submit() {
let form = new FormData();
form.append("lang", this.lang);
form.append("code", editor.getValue());
let req = await fetch(`/api/Problem/Submit/${this.pid}`, {
method: "POST",
body: form,
});
if (req.status == 200) {
ElNotification({
type: "success",
message: `提交成功提交id: ${await req.text()}`,
duration: 3000,
onClose: () => {
window.open(
`http://acm.cug.edu.cn/status.php?problem_id=${this.pid}`
);
},
});
} else {
ElNotification({
type: "error",
message: await req.text(),
});
}
},
},
};
</script>
<style>
</style>

@ -0,0 +1,4 @@
export async function currentUser() {
let res = await fetch("/api/User/WhoAmI").then(x => x.json());
return res.user;
}

@ -0,0 +1,71 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
Loading…
Cancel
Save