add user homepage.

This commit is contained in:
2023-02-15 22:24:36 +08:00
parent b5e1b3db3a
commit 2ea6cc7385
8 changed files with 141 additions and 36 deletions

48
package-lock.json generated
View File

@@ -11,8 +11,8 @@
"@element-plus/icons-vue": "^2.0.10",
"@monaco-editor/loader": "^1.3.2",
"@vueuse/core": "^9.12.0",
"axios": "1.3.2",
"chart.js": "^4.2.0",
"axios": "1.3.3",
"chart.js": "^4.2.1",
"copy-text-to-clipboard": "^3.0.1",
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",
@@ -24,7 +24,7 @@
"vue-router": "^4.1.6"
},
"devDependencies": {
"@iconify-json/ep": "^1.1.8",
"@iconify-json/ep": "^1.1.9",
"@types/node": "^18.13.0",
"@vitejs/plugin-vue": "^4.0.0",
"markdown-it-shiki": "^0.7.2",
@@ -32,7 +32,7 @@
"prettier": "^2.8.4",
"typescript": "^4.9.5",
"unplugin-auto-import": "^0.14.2",
"unplugin-icons": "^0.15.2",
"unplugin-icons": "^0.15.3",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.1.1",
"vite-plugin-vue-markdown": "^0.22.4",
@@ -790,9 +790,9 @@
}
},
"node_modules/@iconify-json/ep": {
"version": "1.1.8",
"resolved": "https://registry.npmmirror.com/@iconify-json/ep/-/ep-1.1.8.tgz",
"integrity": "sha512-pHCrsWU1R9/pTDU+Fps4+mjqOQFLtpGdXWegkhQ1P1DlgQAlCPyICtl6E1s8b7VwJMeZXaK84HA02UF6WD0o/Q==",
"version": "1.1.9",
"resolved": "https://registry.npmmirror.com/@iconify-json/ep/-/ep-1.1.9.tgz",
"integrity": "sha512-vhrCvikS/uRsEaM8eMyH7Fj13TSbkOuXqn0W/hqj79C9mtlUZMXPq31f+Mr5Cw4ag6sBPt8uY5WMhbwAR0vOMA==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
@@ -805,17 +805,17 @@
"dev": true
},
"node_modules/@iconify/utils": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-2.1.1.tgz",
"integrity": "sha512-H8xz74JDzDw8f0qLxwIaxFMnFkbXTZNWEufOk3WxaLFHV4h0A2FjIDgNk5LzC0am4jssnjdeJJdRs3UFu3582Q==",
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-2.1.3.tgz",
"integrity": "sha512-4rnzpZ2AWztPKDyWtw+DwJ9uko24it6YS+cnVpZveOrvLErwg22eXcGnIfuMFyECvsfbFhMqZW5YYWHe3CyEEg==",
"dev": true,
"dependencies": {
"@antfu/install-pkg": "^0.1.1",
"@antfu/utils": "^0.7.2",
"@iconify/types": "^2.0.0",
"debug": "^4.3.4",
"kolorist": "^1.6.0",
"local-pkg": "^0.4.2"
"kolorist": "^1.7.0",
"local-pkg": "^0.4.3"
}
},
"node_modules/@jridgewell/gen-mapping": {
@@ -1442,9 +1442,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.3.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.2.tgz",
"integrity": "sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==",
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.3.tgz",
"integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -1551,9 +1551,9 @@
}
},
"node_modules/chart.js": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.2.0.tgz",
"integrity": "sha512-wbtcV+QKeH0F7gQZaCJEIpsNriFheacouJQTVIjITi3eQA8bTlIBoknz0+dgV79aeKLNMAX+nDslIVE/nJ3rzA==",
"version": "4.2.1",
"resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.2.1.tgz",
"integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -3485,17 +3485,17 @@
}
},
"node_modules/unplugin-icons": {
"version": "0.15.2",
"resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-0.15.2.tgz",
"integrity": "sha512-oWTTdLMuqfEYfZcko+KZHDEOIsqT4OeyJB1e4U7luCOo9gto/JLyHkqfbqjmjkjdQqA3DNHS18WOKh5esqQM5g==",
"version": "0.15.3",
"resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-0.15.3.tgz",
"integrity": "sha512-YWgJqv5AahrokeOnta8uX/m1damZA6Rf6zPClgHg2Fa/45iyOe3Lj+Wn/Ba+CSsq9yBffn17YfKfJNyWCNZPvw==",
"dev": true,
"dependencies": {
"@antfu/install-pkg": "^0.1.1",
"@antfu/utils": "^0.7.2",
"@iconify/utils": "^2.1.0",
"@iconify/utils": "^2.1.2",
"debug": "^4.3.4",
"kolorist": "^1.6.0",
"local-pkg": "^0.4.2",
"kolorist": "^1.7.0",
"local-pkg": "^0.4.3",
"unplugin": "^1.0.1"
},
"peerDependencies": {

View File

@@ -13,8 +13,8 @@
"@element-plus/icons-vue": "^2.0.10",
"@monaco-editor/loader": "^1.3.2",
"@vueuse/core": "^9.12.0",
"axios": "1.3.2",
"chart.js": "^4.2.0",
"axios": "1.3.3",
"chart.js": "^4.2.1",
"copy-text-to-clipboard": "^3.0.1",
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",
@@ -26,7 +26,7 @@
"vue-router": "^4.1.6"
},
"devDependencies": {
"@iconify-json/ep": "^1.1.8",
"@iconify-json/ep": "^1.1.9",
"@types/node": "^18.13.0",
"@vitejs/plugin-vue": "^4.0.0",
"markdown-it-shiki": "^0.7.2",
@@ -34,7 +34,7 @@
"prettier": "^2.8.4",
"typescript": "^4.9.5",
"unplugin-auto-import": "^0.14.2",
"unplugin-icons": "^0.15.2",
"unplugin-icons": "^0.15.3",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.1.1",
"vite-plugin-vue-markdown": "^0.22.4",

5
src/components.d.ts vendored
View File

@@ -16,20 +16,23 @@ declare module '@vue/runtime-core' {
IEpMoreFilled: typeof import('~icons/ep/more-filled')['default']
IEpSunny: typeof import('~icons/ep/sunny')['default']
NAlert: typeof import('naive-ui')['NAlert']
NAvatar: typeof import('naive-ui')['NAvatar']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCode: typeof import('naive-ui')['NCode']
NCode: typeof import("naive-ui")["NCode"]
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NCountdown: typeof import("naive-ui")["NCountdown"]
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDropdown: typeof import('naive-ui')['NDropdown']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import("naive-ui")["NImage"]
NInput: typeof import('naive-ui')['NInput']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutContent: typeof import('naive-ui')['NLayoutContent']

View File

@@ -40,7 +40,15 @@ const columns = ref<DataTableColumn<ContestRank>[]>([
fixed: "left",
align: "center",
render: (row) =>
h(NButton, { text: true, type: "info" }, () => row.user.username),
h(
NButton,
{
text: true,
type: "info",
onClick: () => router.push("/user?name=" + row.user.username),
},
() => row.user.username
),
},
{
title: "正确数/总提交",

View File

@@ -6,6 +6,7 @@ import { Rank } from "utils/types"
import { getRank } from "oj/api"
import { getACRate } from "utils/functions"
const router = useRouter()
const data = ref<Rank[]>([])
const total = ref(0)
const query = reactive({
@@ -39,7 +40,11 @@ const columns: DataTableColumn<Rank>[] = [
render: (row) =>
h(
NButton,
{ text: true, type: "info", onClick: () => {} },
{
text: true,
type: "info",
onClick: () => router.push("/user?name=" + row.user.username),
},
() => row.user.username
),
},

View File

@@ -61,7 +61,7 @@ onMounted(init)
>
<n-space>
<span>提交时间{{ parseTime(submission.create_time) }}</span>
<span>语言{{ submission.language }}</span>
<span>编程语言{{ submission.language }}</span>
<span>用户{{ submission.username }}</span>
</n-space>
</n-alert>

View File

@@ -196,7 +196,15 @@ const columns = computed(() => {
key: "username",
minWidth: 120,
render: (row) =>
h(NButton, { text: true, type: "info" }, () => row.username),
h(
NButton,
{
text: true,
type: "info",
onClick: () => router.push("/user?name=" + row.username),
},
() => row.username
),
},
]
if (!route.params.contestID && userStore.isSuperAdmin) {

View File

@@ -1,3 +1,84 @@
<script setup lang="ts"></script>
<template></template>
<style scoped></style>
<script setup lang="ts">
import { getProfile } from "~/shared/api"
import { Profile } from "~/utils/types"
const route = useRoute()
const profile = ref<Profile | null>(null)
const problems = ref<string[]>([])
const [loading, toggle] = useToggle()
async function init() {
toggle(true)
try {
const res = await getProfile(route.query.name as string)
profile.value = res.data
const acm = res.data.acm_problems_status.problems || {}
const oi = res.data.oi_problems_status.problems || {}
const ac: string[] = []
for (let problems of [acm, oi]) {
Object.keys(problems).forEach((problemID) => {
if (problems[problemID]["status"] === 0) {
ac.push(problems[problemID]["_id"])
}
})
}
ac.sort()
problems.value = ac
} finally {
toggle(false)
}
}
onMounted(init)
</script>
<template>
<n-space vertical justify="center" align="center" v-if="!loading && profile">
<n-avatar round :size="140" :src="profile.avatar" />
<h2>{{ profile.user.username }}</h2>
<p class="desc">{{ profile.mood }}</p>
</n-space>
<n-descriptions
v-if="!loading && profile"
class="wrapper"
bordered
:column="2"
label-style="width: 50%"
>
<n-descriptions-item label="已解决的问题数量">
{{ profile.accepted_number }}
</n-descriptions-item>
<n-descriptions-item label="总提交数">
{{ profile.submission_number }}
</n-descriptions-item>
<n-descriptions-item v-if="problems.length" label="已解决的问题" :span="2">
<n-space>
<n-button
v-for="id in problems"
key="id"
@click="$router.push('/problem/' + id)"
>
{{ id }}
</n-button>
</n-space>
</n-descriptions-item>
</n-descriptions>
<n-empty v-if="!loading && !profile" description="该用户不存在">
<template #extra>
<n-button @click="$router.push('/')">返回主页</n-button>
</template>
</n-empty>
</template>
<style scoped>
.wrapper {
max-width: 600px;
margin: 16px auto 0;
}
h2 {
margin: 0;
}
.desc {
margin: 0;
}
</style>