refactor axios.

This commit is contained in:
2023-01-08 14:42:54 +08:00
parent 7bea386dbc
commit e306e6b463
16 changed files with 270 additions and 113 deletions

99
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@element-plus/icons-vue": "^2.0.10",
"@monaco-editor/loader": "^1.3.2",
"@vueuse/core": "^9.10.0",
"@vueuse/integrations": "^9.10.0",
"axios": "^1.2.2",
"element-plus": "^2.2.28",
"normalize.css": "^8.0.1",
@@ -778,6 +779,86 @@
}
}
},
"node_modules/@vueuse/integrations": {
"version": "9.10.0",
"resolved": "https://registry.npmmirror.com/@vueuse/integrations/-/integrations-9.10.0.tgz",
"integrity": "sha512-MLGVbN3i9gRq3pb8VRZXgPvbNJcUUvgR5pmbc1QZj4Z1vvsvxam159AwWEJdyX2I39a1E7EkmBujtiXtVckO5g==",
"dependencies": {
"@vueuse/core": "9.10.0",
"@vueuse/shared": "9.10.0",
"vue-demi": "*"
},
"peerDependencies": {
"async-validator": "*",
"axios": "*",
"change-case": "*",
"drauu": "*",
"focus-trap": "*",
"fuse.js": "*",
"idb-keyval": "*",
"jwt-decode": "*",
"nprogress": "*",
"qrcode": "*",
"universal-cookie": "*"
},
"peerDependenciesMeta": {
"async-validator": {
"optional": true
},
"axios": {
"optional": true
},
"change-case": {
"optional": true
},
"drauu": {
"optional": true
},
"focus-trap": {
"optional": true
},
"fuse.js": {
"optional": true
},
"idb-keyval": {
"optional": true
},
"jwt-decode": {
"optional": true
},
"nprogress": {
"optional": true
},
"qrcode": {
"optional": true
},
"universal-cookie": {
"optional": true
}
}
},
"node_modules/@vueuse/integrations/node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/metadata": {
"version": "9.10.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.10.0.tgz",
@@ -2639,6 +2720,24 @@
}
}
},
"@vueuse/integrations": {
"version": "9.10.0",
"resolved": "https://registry.npmmirror.com/@vueuse/integrations/-/integrations-9.10.0.tgz",
"integrity": "sha512-MLGVbN3i9gRq3pb8VRZXgPvbNJcUUvgR5pmbc1QZj4Z1vvsvxam159AwWEJdyX2I39a1E7EkmBujtiXtVckO5g==",
"requires": {
"@vueuse/core": "9.10.0",
"@vueuse/shared": "9.10.0",
"vue-demi": "*"
},
"dependencies": {
"vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"requires": {}
}
}
},
"@vueuse/metadata": {
"version": "9.10.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.10.0.tgz",

View File

@@ -13,6 +13,7 @@
"@element-plus/icons-vue": "^2.0.10",
"@monaco-editor/loader": "^1.3.2",
"@vueuse/core": "^9.10.0",
"@vueuse/integrations": "^9.10.0",
"axios": "^1.2.2",
"element-plus": "^2.2.28",
"normalize.css": "^8.0.1",

View File

@@ -21,6 +21,7 @@ const routes = [
{
path: "problem/:problemID",
component: () => import("./oj/problem/detail.vue"),
props: true,
},
{
path: "status",
@@ -30,6 +31,7 @@ const routes = [
{
path: "status/:statusID",
component: () => import("./oj/status/detail.vue"),
props: true,
},
{
path: "contest",
@@ -39,6 +41,12 @@ const routes = [
{
path: "contest/:contestID",
component: () => import("./oj/contest/detail.vue"),
props: true,
},
{
path: "contest/:contestID/problem/:problemID",
component: () => import("./oj/problem/detail.vue"),
props: true,
},
{
path: "rank",

View File

@@ -1,12 +1,14 @@
import { getACRate } from "./../utils/functions"
import { DIFFICULTY } from "./../utils/constants"
import { Problem, LANGUAGE } from "./../utils/types"
import http from "./../utils/http"
import { useAxios } from "@vueuse/integrations/useAxios"
function filterResult(result: any) {
function filterResult(result: Problem) {
const newResult: any = {
displayID: result._id,
_id: result._id,
title: result.title,
difficulty: DIFFICULTY[<"Low" | "Mid" | "High">result.difficulty],
difficulty: DIFFICULTY[result.difficulty],
tags: result.tags,
submission: result.submission_number,
rate: getACRate(result.accepted_number, result.submission_number),
@@ -46,7 +48,7 @@ export async function getProblemList(
}
export function getProblemTagList() {
return http.get("problem/tags")
return useAxios<{ id: number; name: string }[]>("problem/tags", http)
}
export function getRandomProblemID() {
@@ -54,11 +56,20 @@ export function getRandomProblemID() {
}
export function getProblem(id: string) {
return http.get("problem", {
params: { problem_id: id },
return useAxios<Problem>("problem", { params: { problem_id: id } }, http)
}
export function getSubmission(id: string) {
return http.get("submission", {
params: { id },
})
}
export function getWebsite() {
return http.get("website")
export function submitCode(data: {
problem_id: number
contest_id?: number
language: LANGUAGE
code: string
}) {
return http.post("submission", data)
}

View File

@@ -35,11 +35,11 @@ onMounted(userStore.getMyProfile)
<el-menu-item index="/status">提交</el-menu-item>
<el-menu-item index="/rank">排名</el-menu-item>
</el-menu>
<div v-if="userStore.isLoaded && !userStore.isAuthed" class="actions">
<div v-if="userStore.isFinished && !userStore.isAuthed" class="actions">
<el-button @click="loginStore.show">登录</el-button>
<el-button @click="signupStore.show">注册</el-button>
</div>
<div v-if="userStore.isLoaded && userStore.isAuthed" class="actions">
<div v-if="userStore.isFinished && userStore.isAuthed" class="actions">
<el-dropdown @command="handleDropdown">
<el-button>{{ userStore.user.username }}</el-button>
<template #dropdown>

View File

@@ -1,29 +1,28 @@
<script lang="ts" setup>
import loader, { Monaco } from "@monaco-editor/loader"
import { ref, onBeforeUnmount, onMounted, watch, reactive } from "vue"
import { ref, onBeforeUnmount, onMounted, watch, reactive, computed } from "vue"
import {
LANGUAGE,
LANGUAGE_LABEL,
LANGUAGE_VALUE,
SOURCES,
} from "../../../utils/constants"
import { isMobile } from "../../../utils/breakpoints"
import { submitCode } from "../../api"
import { Problem } from "../../../utils/types"
const { problem } = defineProps<{
problem: {
languages: Array<LANGUAGE>
template: { [key in LANGUAGE]?: string }
}
const { problem, contestID = "" } = defineProps<{
contestID?: string
problemID?: string
problem: Problem
}>()
const state = reactive({
values: ref({ ...SOURCES }),
language: problem.languages[0] || "C",
isMobile,
submissionId: "",
})
const monacoEditorRef = ref()
let monaco: Monaco
function reset() {
@@ -34,9 +33,7 @@ function reset() {
}
}
onMounted(() => {
init()
})
onMounted(init)
onBeforeUnmount(() => {
monaco.editor.getModels().forEach((model) => model.dispose())
@@ -77,6 +74,29 @@ async function init() {
state.values[state.language] = monaco.editor.getModels()[0].getValue()
})
}
const submitDisabled = computed(() => {
const code = state.values[state.language]
return (
code.trim() === "" ||
code === problem.template[state.language] ||
code === SOURCES[state.language]
)
})
async function submit() {
const data = {
problem_id: problem.id,
language: state.language,
code: state.values[state.language],
}
if (contestID) {
//@ts-ignore
data.contest_id = parseInt(contestID)
}
const res = await submitCode(data)
state.submissionId = res.data.submission_id
}
</script>
<template>
@@ -107,7 +127,9 @@ async function init() {
<el-form class="actions">
<el-form-item>
<el-button>运行</el-button>
<el-button type="primary">提交</el-button>
<el-button type="primary" :disabled="submitDisabled" @click="submit">
提交
</el-button>
</el-form-item>
</el-form>
</template>

View File

@@ -1,5 +1,7 @@
<script setup lang="ts">
const { problem } = defineProps(["problem"])
import { Problem } from "../../../utils/types"
const { problem } = defineProps<{ problem: Problem }>()
</script>
<template>

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import { DIFFICULTY } from "../../../utils/constants"
import { getACRate, getTagColor } from "../../../utils/functions"
import { getACRate, getTagColor, parseTime } from "../../../utils/functions"
import { isDesktop } from "../../../utils/breakpoints"
import { Problem } from "../../../utils/types"
const { problem } = defineProps(["problem"])
const { problem } = defineProps<{ problem: Problem }>()
</script>
<template>
@@ -14,19 +15,20 @@ const { problem } = defineProps(["problem"])
<el-descriptions-item label="出题人">
{{ problem.created_by.username }}
</el-descriptions-item>
<el-descriptions-item label="难度">
<el-tag disable-transitions :type="getTagColor(problem.difficulty)">
{{ DIFFICULTY[<"Low" | "Mid" | "High">problem.difficulty] }}
</el-tag>
<el-descriptions-item label="创建时间">
{{ parseTime(problem.create_time) }}
</el-descriptions-item>
<el-descriptions-item label="时间限制">
{{ problem.time_limit }}毫秒
</el-descriptions-item>
<el-descriptions-item label="内存限制">
{{ problem.memory_limit }}MB
</el-descriptions-item>
<el-descriptions-item label="类型">
{{ problem.rule_type }}
<el-descriptions-item label="难度">
<el-tag disable-transitions :type="getTagColor(problem.difficulty)">
{{ DIFFICULTY[problem.difficulty] }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="提交正确">

View File

@@ -1,39 +1,19 @@
<script setup lang="ts">
import { onMounted, ref } from "vue"
import { useRoute } from "vue-router"
import Editor from "./components/editor.vue"
import ProblemContent from "./components/problem-content.vue"
import ProblemInfo from "./components/problem-info.vue"
import { getProblem } from "../api"
import { isDesktop, isMobile } from "../../utils/breakpoints"
const route = useRoute()
const contestID = route.params.contestID as string
const problemID = route.params.problemID as string
const problem = ref({
_id: "",
created_by: {},
io_mode: {},
languages: [],
samples: [],
statistic_info: {},
tags: [],
template: {},
})
async function init() {
const res = await getProblem(problemID)
problem.value = res.data
}
onMounted(() => {
init()
})
const { problemID = "", contestID = "" } = defineProps<{
problemID?: string
contestID?: string
}>()
const { data: problem, isFinished } = getProblem(problemID)
</script>
<template>
<el-row v-if="problem._id">
<el-row v-if="isFinished && problem">
<el-col :span="isDesktop ? 12 : 24">
<el-tabs type="border-card">
<el-tab-pane label="题目描述">

View File

@@ -17,9 +17,10 @@ const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const problems = ref([])
const tags = ref(<{ id: number; name: string }[]>[])
const total = ref(0)
const { data: tags } = getProblemTagList()
const query = reactive({
keyword: route.query.keyword || "",
difficulty: route.query.difficulty || "",
@@ -28,11 +29,6 @@ const query = reactive({
limit: parseInt(<string>route.query.limit) || 10,
})
async function listTags() {
const res = await getProblemTagList()
tags.value = res.data
}
async function listProblems() {
query.keyword = route.query.keyword || ""
query.difficulty = route.query.difficulty || ""
@@ -77,7 +73,7 @@ async function getRandom() {
}
function goProblem(row: any) {
router.push("/problem/" + row.displayID)
router.push("/problem/" + row._id)
}
watch(() => query.page, routePush)
@@ -98,12 +94,9 @@ watch(
)
// TODO: 这里会在登录时候执行两次有BUG
watch(() => userStore.isLoaded && userStore.isAuthed, listProblems)
watch(() => userStore.isFinished && userStore.isAuthed, listProblems)
onMounted(() => {
listTags()
listProblems()
})
onMounted(listProblems)
</script>
<template>
@@ -158,11 +151,7 @@ onMounted(() => {
/></el-icon>
</template>
</el-table-column>
<el-table-column
prop="displayID"
label="编号"
:width="isDesktop ? 100 : 60"
/>
<el-table-column prop="_id" label="编号" :width="isDesktop ? 100 : 60" />
<el-table-column prop="title" label="标题" />
<el-table-column label="难度" width="100">
<template #default="scope">

View File

@@ -1,7 +1,10 @@
import { useAxios } from "@vueuse/integrations/useAxios"
import http from "../utils/http"
export function login(data: { username: string; password: string }) {
return http.post("login", data)
return useAxios("login", { method: "post", data }, http, {
immediate: false,
})
}
export function logout() {
@@ -9,7 +12,7 @@ export function logout() {
}
export function getUserInfo(username: string) {
return http.get("profile", {
params: { username },
return useAxios("profile", { method: "get", params: { username } }, http, {
immediate: false,
})
}

View File

@@ -1,5 +1,5 @@
import { defineStore } from "pinia"
import { computed, ref } from "vue"
import { computed } from "vue"
import {
PROBLEM_PERMISSION,
STORAGE_KEY,
@@ -9,8 +9,7 @@ import storage from "../../utils/storage"
import { getUserInfo } from "../api"
export const useUserStore = defineStore("user", () => {
const profile = ref<any>({})
const isLoaded = ref(false)
const { data: profile, isFinished, execute } = getUserInfo("")
const user = computed(() => profile.value.user || {})
const isAuthed = computed(() => !!user.value.email)
const isAdminRole = computed(
@@ -26,10 +25,7 @@ export const useUserStore = defineStore("user", () => {
)
async function getMyProfile() {
isLoaded.value = false
const res = await getUserInfo("")
isLoaded.value = true
profile.value = res.data || {}
await execute()
storage.set(STORAGE_KEY.AUTHED, !!user.value.email)
}
@@ -39,7 +35,7 @@ export const useUserStore = defineStore("user", () => {
}
return {
profile,
isLoaded,
isFinished,
user,
isAdminRole,
isSuperAdmin,

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { FormInstance } from "element-plus"
import { reactive, ref } from "vue"
import { computed, reactive, ref } from "vue"
import { useSignupStore } from "../../oj/stores/signup"
import { login } from "../../shared/api"
import { useLoginStore } from "../stores/login"
@@ -9,8 +9,6 @@ import { useUserStore } from "../stores/user"
const loginStore = useLoginStore()
const signupStore = useSignupStore()
const userStore = useUserStore()
const loading = ref(false)
const errorMessage = ref("")
const loginRef = ref<FormInstance>()
const form = reactive({
username: "",
@@ -23,23 +21,19 @@ const rules = reactive({
{ min: 6, max: 20, message: "长度在6到20位之间", trigger: "change" },
],
})
const { isLoading, error, execute } = login(form)
const msg = computed(() => error.value && "用户名或密码不正确")
async function submit() {
if (!loginRef.value) return
await loginRef.value.validate(async (valid) => {
const valid = await loginRef.value.validate()
if (valid) {
loading.value = true
errorMessage.value = ""
try {
await login(form)
await execute()
if (!error.value) {
loginStore.hide()
await userStore.getMyProfile()
} catch (err) {
errorMessage.value = "用户名或密码不正确"
userStore.getMyProfile()
}
loading.value = false
}
})
}
function goSignup() {
@@ -75,17 +69,12 @@ function goSignup() {
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="submit"
>登录</el-button
>
<el-button type="primary" :loading="isLoading" @click="submit">
登录
</el-button>
<el-button @click="goSignup">没有账号立即注册</el-button>
</el-form-item>
<el-alert
v-if="errorMessage"
:title="errorMessage"
show-icon
type="error"
/>
<el-alert v-if="msg" :title="msg" show-icon type="error" />
</el-form>
</el-dialog>
</template>

View File

@@ -196,8 +196,6 @@ export const LANGUAGE_LABEL = {
Golang: "Go",
}
export type LANGUAGE = keyof typeof LANGUAGE_LABEL
export const LANGUAGE_VALUE = {
C: "c",
"C++": "cpp",

View File

@@ -1,3 +1,4 @@
import { useDateFormat } from "@vueuse/core"
import { STORAGE_KEY } from "./constants"
export function getACRate(acCount: number, totalCount: number) {
@@ -32,3 +33,8 @@ export function getTagColor(tag: string) {
: "danger",
}[tag]
}
export function parseTime(utc: Date, format = "YYYY年M月D日") {
const time = useDateFormat(utc, format, { locales: "zh-CN" })
return time.value
}

51
src/utils/types.ts Normal file
View File

@@ -0,0 +1,51 @@
export type LANGUAGE =
| "C"
| "C++"
| "Python2"
| "Python3"
| "Java"
| "JavaScript"
| "Golang"
export interface Problem {
_id: string
id: number
tags: string[]
created_by: {
id: number
username: string
real_name: null
}
template: { [key in LANGUAGE]?: string }
title: string
description: string
input_description: string
output_description: string
samples: {
input: string
output: string
}[]
hint: string
languages: Array<LANGUAGE>
create_time: Date
last_update_time: null
time_limit: number
memory_limit: number
io_mode: {
input: string
output: string
io_mode: string
}
spj: boolean
spj_language: null
rule_type: string
difficulty: "Low" | "Mid" | "High"
source: string
total_score: number
submission_number: number
accepted_number: number
statistic_info: {}
share_submission: boolean
contest: null
my_status: number
}