feat(frontend): add account and community page previews
Add profiles, discussions, team creation, authentication forms and the about page. Validate preview forms and display the available platform health endpoint.
This commit is contained in:
@@ -54,6 +54,39 @@ export const router = createRouter({
|
||||
meta: { title: '排名' },
|
||||
component: () => import('../views/RankingsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/discussions',
|
||||
meta: { title: '讨论' },
|
||||
component: () => import('../views/DiscussionsView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/discussions/:id',
|
||||
meta: { title: '讨论详情' },
|
||||
component: () => import('../views/DiscussionDetailView.vue'),
|
||||
},
|
||||
{ path: '/teams', meta: { title: '团队' }, component: () => import('../views/TeamsView.vue') },
|
||||
{
|
||||
path: '/teams/create',
|
||||
meta: { title: '创建团队' },
|
||||
component: () => import('../views/CreateTeamView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/users/:username',
|
||||
meta: { title: '个人主页' },
|
||||
component: () => import('../views/ProfileView.vue'),
|
||||
},
|
||||
{ path: '/login', meta: { title: '登录' }, component: () => import('../views/AuthView.vue') },
|
||||
{
|
||||
path: '/register',
|
||||
meta: { title: '注册' },
|
||||
component: () => import('../views/AuthView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/logout',
|
||||
meta: { title: '退出登录' },
|
||||
component: () => import('../views/LogoutView.vue'),
|
||||
},
|
||||
{ path: '/about', meta: { title: '关于' }, component: () => import('../views/AboutView.vue') },
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { usePlatformHealth } from '../composables/usePlatformHealth'
|
||||
import PageHeading from '../components/PageHeading.vue'
|
||||
import PanelCard from '../components/PanelCard.vue'
|
||||
import UiIcon from '../components/UiIcon.vue'
|
||||
const { state, version, refresh } = usePlatformHealth()
|
||||
const status = computed(
|
||||
() =>
|
||||
({ loading: '正在检查', connected: 'API 进程已连接', unavailable: 'API 暂未连接' })[
|
||||
state.value
|
||||
],
|
||||
)
|
||||
const features = [
|
||||
{ icon: 'problems', title: '练习与训练', text: '从基础题目到算法专题,通过题单组织日常练习。' },
|
||||
{ icon: 'contests', title: '竞赛与评测', text: '围绕提交、评测和比赛,逐步完成在线评测流程。' },
|
||||
{
|
||||
icon: 'discussions',
|
||||
title: '交流与成长',
|
||||
text: '分享解题思路,结合练习记录,探索更合适的学习路径。',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
<template>
|
||||
<PageHeading
|
||||
title="关于 KF OnlineJudge"
|
||||
eyebrow="ABOUT"
|
||||
description="从一道题开始,积累每一次进步。"
|
||||
/>
|
||||
<div class="intro-banner">
|
||||
<div>
|
||||
<p class="eyebrow">CODE · PRACTICE · GROW</p>
|
||||
<h2>让思路在代码里落地。</h2>
|
||||
<p>
|
||||
KF OnlineJudge
|
||||
是一个正在开发的在线评测项目,关注判题、沙箱、分布式执行与故障恢复,并逐步提供辅助答疑和个性化训练。
|
||||
</p>
|
||||
</div>
|
||||
<span class="intro-number">KF.</span>
|
||||
</div>
|
||||
<div class="stack">
|
||||
<PanelCard title="在这里,你可以" icon="reading"
|
||||
><div class="about-grid">
|
||||
<div v-for="feature in features" :key="feature.title" class="about-feature">
|
||||
<UiIcon :name="feature.icon" />
|
||||
<h3>{{ feature.title }}</h3>
|
||||
<p>{{ feature.text }}</p>
|
||||
</div>
|
||||
</div></PanelCard
|
||||
><PanelCard id="system" title="系统状态" icon="monitor"
|
||||
><div class="service-row">
|
||||
<div>
|
||||
<h3>
|
||||
平台 API
|
||||
<el-tag
|
||||
:type="state === 'connected' ? 'success' : 'info'"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>{{ status }}</el-tag
|
||||
>
|
||||
</h3>
|
||||
<p>
|
||||
这是实际的 API 存活检查。{{ version && state === 'connected' ? '版本 ' + version : '' }}
|
||||
</p>
|
||||
<p>数据库、题库、评测与学习服务将分别接入。</p>
|
||||
</div>
|
||||
<el-button :loading="state === 'loading'" @click="refresh">重新检查</el-button>
|
||||
</div></PanelCard
|
||||
><PanelCard title="使用说明" icon="about"
|
||||
><div class="detail-prose">
|
||||
<p>
|
||||
当前为前端预览版本。题目、比赛、排名和用户信息使用示例数据;页面筛选、导航和本地代码草稿已可使用。
|
||||
</p>
|
||||
<p style="margin-top: 12px">
|
||||
题目页可以选择语言、切换编辑器主题、保存草稿和填写自测输入。实际评测、账号和团队创建将在服务接入后开放。
|
||||
</p>
|
||||
<p style="margin-top: 12px">备案信息待填写,平台规则和联系方式将在上线前补充。</p>
|
||||
</div></PanelCard
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
const route = useRoute()
|
||||
const registering = computed(() => route.path === '/register')
|
||||
const form = ref<FormInstance>()
|
||||
const model = reactive({ username: '', email: '', password: '', confirmation: '' })
|
||||
const rules = computed<FormRules<typeof model>>(() => ({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
...(registering.value
|
||||
? [
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_]{3,24}$/,
|
||||
message: '使用 3 至 24 位字母、数字或下划线',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请填写有效邮箱', trigger: 'blur' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
...(registering.value ? [{ min: 8, message: '密码至少包含 8 个字符', trigger: 'blur' }] : []),
|
||||
],
|
||||
confirmation: [
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (_rule, value, callback) =>
|
||||
value === model.password ? callback() : callback(new Error('两次输入的密码不一致')),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}))
|
||||
async function submit() {
|
||||
if (await form.value?.validate().catch(() => false))
|
||||
ElMessage.info('账号服务尚未接入,暂时无法' + (registering.value ? '注册' : '登录'))
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="auth-shell">
|
||||
<aside class="auth-art">
|
||||
<RouterLink to="/" class="brand"
|
||||
><span class="brand-mark">KF.</span><span class="brand-name">OnlineJudge</span></RouterLink
|
||||
>
|
||||
<h1>写下思路,<br />开始这一题。</h1>
|
||||
<p>练习、竞赛、讨论。<br />每一步,都算数。</p>
|
||||
</aside>
|
||||
<section class="auth-form">
|
||||
<p class="eyebrow">WELCOME TO KF ONLINEJUDGE</p>
|
||||
<h2>{{ registering ? '创建你的账号' : '欢迎回来' }}</h2>
|
||||
<p>{{ registering ? '准备好开始新的练习了吗?' : '继续你的算法练习之旅。' }}</p>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="model"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
@submit.prevent="submit"
|
||||
><el-form-item label="用户名" prop="username"
|
||||
><el-input
|
||||
v-model="model.username"
|
||||
placeholder="请输入用户名"
|
||||
autocomplete="username" /></el-form-item
|
||||
><el-form-item v-if="registering" label="邮箱" prop="email"
|
||||
><el-input
|
||||
v-model="model.email"
|
||||
placeholder="your@email.com"
|
||||
autocomplete="email" /></el-form-item
|
||||
><el-form-item label="密码" prop="password"
|
||||
><el-input
|
||||
v-model="model.password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入密码"
|
||||
:autocomplete="registering ? 'new-password' : 'current-password'" /></el-form-item
|
||||
><el-form-item v-if="registering" label="确认密码" prop="confirmation"
|
||||
><el-input
|
||||
v-model="model.confirmation"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="再次输入密码"
|
||||
autocomplete="new-password" /></el-form-item
|
||||
><el-button type="primary" native-type="submit">{{
|
||||
registering ? '注册' : '登录'
|
||||
}}</el-button></el-form
|
||||
>
|
||||
<p class="form-help" style="margin-top: 14px">账号功能待接入,当前不会创建账号或保存密码。</p>
|
||||
<div class="auth-links">
|
||||
<RouterLink to="/">← 返回首页</RouterLink
|
||||
><RouterLink :to="registering ? '/login' : '/register'">{{
|
||||
registering ? '已有账号?去登录' : '还没有账号?去注册'
|
||||
}}</RouterLink>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import PageHeading from '../components/PageHeading.vue'
|
||||
import PanelCard from '../components/PanelCard.vue'
|
||||
const form = ref<FormInstance>()
|
||||
const model = reactive({ name: '', description: '', visibility: 'public', joinMode: 'approval' })
|
||||
const rules: FormRules<typeof model> = {
|
||||
name: [
|
||||
{ required: true, message: '请填写团队名称', trigger: 'blur' },
|
||||
{ min: 2, max: 30, message: '名称长度为 2 到 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请填写团队介绍', trigger: 'blur' },
|
||||
{ max: 500, message: '介绍最多 500 个字符', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
async function validate() {
|
||||
if (await form.value?.validate().catch(() => false))
|
||||
ElMessage.info('填写已通过检查,团队创建功能待服务接入')
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<PageHeading
|
||||
title="创建团队"
|
||||
eyebrow="CREATE A TEAM"
|
||||
description="为团队起一个名字,写下你们想一起完成的事情。"
|
||||
><RouterLink to="/teams"><el-button>返回团队列表</el-button></RouterLink></PageHeading
|
||||
><PanelCard title="团队资料" icon="teams" class="form-panel"
|
||||
><el-form
|
||||
ref="form"
|
||||
:model="model"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
@submit.prevent="validate"
|
||||
><el-form-item label="团队名称" prop="name"
|
||||
><el-input
|
||||
v-model="model.name"
|
||||
placeholder="例如:KF 算法研习组"
|
||||
maxlength="30"
|
||||
show-word-limit /></el-form-item
|
||||
><el-form-item label="团队介绍" prop="description"
|
||||
><el-input
|
||||
v-model="model.description"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
placeholder="介绍团队的练习方向、交流方式和目标"
|
||||
maxlength="500"
|
||||
show-word-limit /></el-form-item
|
||||
><el-form-item label="可见性"
|
||||
><el-radio-group v-model="model.visibility"
|
||||
><el-radio value="public">公开团队</el-radio
|
||||
><el-radio value="private">私有团队</el-radio></el-radio-group
|
||||
>
|
||||
<p class="form-help">公开团队可在团队列表中发现,私有团队通过邀请加入。</p></el-form-item
|
||||
><el-form-item label="加入方式"
|
||||
><el-radio-group v-model="model.joinMode"
|
||||
><el-radio value="approval">需要审核</el-radio
|
||||
><el-radio value="invite">仅接受邀请</el-radio></el-radio-group
|
||||
></el-form-item
|
||||
>
|
||||
<div class="form-actions">
|
||||
<el-button native-type="submit">检查填写</el-button
|
||||
><el-button type="primary" disabled>创建团队</el-button
|
||||
><span class="muted">团队服务待接入</span>
|
||||
</div></el-form
|
||||
></PanelCard
|
||||
>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { discussions } from '../data/preview'
|
||||
import PageHeading from '../components/PageHeading.vue'
|
||||
import PanelCard from '../components/PanelCard.vue'
|
||||
import EmptyState from '../components/EmptyState.vue'
|
||||
const route = useRoute()
|
||||
const discussion = computed(() => discussions.find((d) => d.id === route.params.id))
|
||||
</script>
|
||||
<template>
|
||||
<template v-if="discussion"
|
||||
><div class="breadcrumbs">
|
||||
<RouterLink to="/discussions">讨论</RouterLink><span>/</span
|
||||
><span>{{ discussion.category }}</span>
|
||||
</div>
|
||||
<PageHeading :title="discussion.title"
|
||||
><el-tag effect="plain">{{ discussion.category }}</el-tag></PageHeading
|
||||
><PanelCard title="正文" icon="reading" subtitle="示例文章"
|
||||
><div class="detail-meta">
|
||||
<RouterLink :to="'/users/' + discussion.author">{{ discussion.author }}</RouterLink
|
||||
><time>{{ discussion.date }}</time
|
||||
><span>{{ discussion.views }} 次浏览</span>
|
||||
</div>
|
||||
<div class="detail-prose">
|
||||
<p>{{ discussion.content }}</p>
|
||||
</div>
|
||||
<RouterLink
|
||||
v-if="discussion.problemId"
|
||||
class="text-link"
|
||||
:to="'/problems/' + discussion.problemId"
|
||||
style="display: inline-block; margin-top: 24px"
|
||||
>查看相关题目 →</RouterLink
|
||||
>
|
||||
<div class="comment-placeholder">评论功能将在社区服务接入后开放。</div></PanelCard
|
||||
></template
|
||||
><EmptyState v-else title="讨论不存在"
|
||||
><RouterLink to="/discussions"><el-button>返回讨论列表</el-button></RouterLink></EmptyState
|
||||
>
|
||||
</template>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { discussions, rankings } from '../data/preview'
|
||||
import PageHeading from '../components/PageHeading.vue'
|
||||
import PanelCard from '../components/PanelCard.vue'
|
||||
const category = ref('全部')
|
||||
const query = ref('')
|
||||
const items = computed(() =>
|
||||
discussions.filter(
|
||||
(d) =>
|
||||
(category.value === '全部' || d.category === category.value) && d.title.includes(query.value),
|
||||
),
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<PageHeading
|
||||
title="讨论社区"
|
||||
eyebrow="COMMUNITY"
|
||||
description="分享思路,提出问题,一起把代码写得更好。"
|
||||
><el-tooltip content="讨论发布将在账号服务接入后开放"
|
||||
><el-button type="primary" disabled>发起讨论</el-button></el-tooltip
|
||||
></PageHeading
|
||||
>
|
||||
<div class="content-with-aside">
|
||||
<PanelCard title="最新讨论" icon="discussions" subtitle="示例文章"
|
||||
><div class="filters">
|
||||
<el-radio-group v-model="category"
|
||||
><el-radio-button
|
||||
v-for="c in ['全部', '交流', '问答', '题解', '反馈']"
|
||||
:key="c"
|
||||
:value="c"
|
||||
>{{ c }}</el-radio-button
|
||||
></el-radio-group
|
||||
><el-input v-model="query" placeholder="搜索讨论" aria-label="搜索讨论" clearable />
|
||||
</div>
|
||||
<article v-for="discussion in items" :key="discussion.id" class="discussion-item">
|
||||
<RouterLink
|
||||
:to="'/users/' + discussion.author"
|
||||
class="avatar"
|
||||
:style="{ background: rankings.find((r) => r.username === discussion.author)?.color }"
|
||||
>{{ discussion.author[0] }}</RouterLink
|
||||
>
|
||||
<div>
|
||||
<h2>
|
||||
<el-tag v-if="discussion.pinned" size="small" effect="plain">置顶</el-tag
|
||||
><RouterLink :to="'/discussions/' + discussion.id">{{ discussion.title }}</RouterLink>
|
||||
</h2>
|
||||
<p>
|
||||
<span class="tag">{{ discussion.category }}</span
|
||||
><RouterLink :to="'/users/' + discussion.author">{{ discussion.author }}</RouterLink
|
||||
><span>{{ discussion.date }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="discussion-numbers">
|
||||
<span
|
||||
><strong>{{ discussion.replies }}</strong> 回复</span
|
||||
><span>{{ discussion.views }} 浏览</span>
|
||||
</div>
|
||||
</article>
|
||||
<el-empty v-if="!items.length" description="没有符合条件的讨论"
|
||||
/></PanelCard>
|
||||
<div class="stack">
|
||||
<PanelCard title="欢迎交流" icon="reading"
|
||||
><p class="small muted">
|
||||
提问时写清楚题号、语言、报错和已经尝试过的思路。分享题解时,也可以记录从哪里开始卡住,以及后来怎样找到解法。
|
||||
</p></PanelCard
|
||||
><PanelCard title="社区分区" icon="folder"
|
||||
><dl class="details-list">
|
||||
<div v-for="c in ['交流', '问答', '题解', '反馈']" :key="c">
|
||||
<dt>{{ c }}</dt>
|
||||
<dd>{{ discussions.filter((d) => d.category === c).length }} 篇示例文章</dd>
|
||||
</div>
|
||||
</dl></PanelCard
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import EmptyState from '../components/EmptyState.vue'
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel" style="max-width: 650px; margin: 50px auto">
|
||||
<EmptyState title="当前没有登录会话" description="账号服务接入后,这里会清除会话并退出登录。"
|
||||
><RouterLink to="/"><el-button type="primary">返回首页</el-button></RouterLink
|
||||
><RouterLink to="/login" style="margin-left: 12px"
|
||||
><el-button>去登录</el-button></RouterLink
|
||||
></EmptyState
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { rankings, submissions, problems } from '../data/preview'
|
||||
import PanelCard from '../components/PanelCard.vue'
|
||||
import SubmissionTable from '../components/SubmissionTable.vue'
|
||||
import ProblemTable from '../components/ProblemTable.vue'
|
||||
import EmptyState from '../components/EmptyState.vue'
|
||||
const route = useRoute()
|
||||
const user = computed(() => rankings.find((u) => u.username === route.params.username))
|
||||
const tab = ref('activity')
|
||||
const records = computed(() => submissions.filter((s) => s.username === user.value?.username))
|
||||
const solved = computed(() =>
|
||||
problems.filter((p) => records.value.some((s) => s.problemId === p.id && s.verdict === 'AC')),
|
||||
)
|
||||
const activity = Array.from({ length: 140 }, (_, index) =>
|
||||
index % 11 === 0 ? 4 : index % 7 === 0 ? 3 : index % 3 === 0 ? 2 : index % 2 === 0 ? 1 : 0,
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<template v-if="user"
|
||||
><div class="profile-banner">
|
||||
<span class="avatar" :style="{ background: user.color }">{{ user.username[0] }}</span>
|
||||
<div>
|
||||
<p class="eyebrow">PERSONAL PROFILE · 示例用户</p>
|
||||
<h1>{{ user.username }}</h1>
|
||||
<p>{{ user.motto }}</p>
|
||||
<div class="profile-tags">
|
||||
<span class="tag">算法爱好者</span><span class="tag">持续练习</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-actions">
|
||||
<RouterLink :to="{ path: '/submissions', query: { user: user.username } }"
|
||||
><el-button>查看提交</el-button></RouterLink
|
||||
><el-button disabled>关注</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card">
|
||||
<p>已通过题目</p>
|
||||
<strong>{{ user.solved }}</strong
|
||||
><small>示例统计</small>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<p>总提交数</p>
|
||||
<strong>{{ user.submissions }}</strong
|
||||
><small>示例统计</small>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<p>通过率</p>
|
||||
<strong>{{ Math.round((user.solved / user.submissions) * 100) }}%</strong
|
||||
><small>示例统计</small>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<p>竞赛积分</p>
|
||||
<strong>{{ user.rating }}</strong
|
||||
><small>示例积分</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-with-aside">
|
||||
<PanelCard title="练习记录" icon="rankings"
|
||||
><el-tabs v-model="tab"
|
||||
><el-tab-pane label="提交活动" name="activity"
|
||||
><p class="small muted">提交活动热力图 · 布局示例</p>
|
||||
<div class="heatmap" role="img" aria-label="示例提交活动热力图">
|
||||
<span
|
||||
v-for="(level, index) in activity"
|
||||
:key="index"
|
||||
class="heat-cell"
|
||||
:data-level="level"
|
||||
/>
|
||||
</div>
|
||||
<div class="detail-meta"><span>少</span><span>← 提交活跃度 →</span><span>多</span></div>
|
||||
<SubmissionTable :items="records" compact /></el-tab-pane
|
||||
><el-tab-pane label="已通过题目" name="problems"
|
||||
><p class="tab-summary">以下题目来自当前示例提交记录。</p>
|
||||
<ProblemTable :items="solved" compact /></el-tab-pane></el-tabs
|
||||
></PanelCard>
|
||||
<div class="stack">
|
||||
<PanelCard title="个人介绍" icon="about"
|
||||
><p class="small muted">{{ user.motto }}</p>
|
||||
<dl class="details-list" style="margin-top: 20px">
|
||||
<div>
|
||||
<dt>用户名</dt>
|
||||
<dd>{{ user.username }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>榜单位置</dt>
|
||||
<dd>#{{ rankings.indexOf(user) + 1 }}</dd>
|
||||
</div>
|
||||
</dl></PanelCard
|
||||
><PanelCard title="学习分析" icon="reading"
|
||||
><p class="small muted">
|
||||
后续结合历史代码、提交结果和题目标签,分析练习情况并生成个性化训练建议。
|
||||
</p>
|
||||
<el-button disabled style="margin-top: 18px; width: 100%"
|
||||
>分析服务待接入</el-button
|
||||
></PanelCard
|
||||
>
|
||||
</div>
|
||||
</div></template
|
||||
><EmptyState v-else title="用户不存在"
|
||||
><RouterLink to="/rankings"><el-button>查看排行榜</el-button></RouterLink></EmptyState
|
||||
>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import PageHeading from '../components/PageHeading.vue'
|
||||
const query = ref('')
|
||||
const teams = [
|
||||
{
|
||||
name: 'KF 算法研习组',
|
||||
initials: 'KF',
|
||||
description: '一起练习基础算法,分享解题笔记。',
|
||||
members: 12,
|
||||
tags: ['基础训练', '算法交流'],
|
||||
},
|
||||
{
|
||||
name: 'Rust 解题小队',
|
||||
initials: 'RS',
|
||||
description: '用 Rust 完成题目,交流语言与算法的使用经验。',
|
||||
members: 8,
|
||||
tags: ['Rust', '专题练习'],
|
||||
},
|
||||
{
|
||||
name: '周末练习伙伴',
|
||||
initials: 'AC',
|
||||
description: '每周留一点时间,认真做几道题。',
|
||||
members: 16,
|
||||
tags: ['练习赛', '学习打卡'],
|
||||
},
|
||||
]
|
||||
const items = computed(() =>
|
||||
teams.filter((t) => t.name.toLowerCase().includes(query.value.toLowerCase())),
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<PageHeading title="团队" eyebrow="TEAMS" description="找到一起练习和交流的伙伴。"
|
||||
><RouterLink to="/teams/create"
|
||||
><el-button type="primary">创建团队</el-button></RouterLink
|
||||
></PageHeading
|
||||
>
|
||||
<div class="filters">
|
||||
<el-input v-model="query" placeholder="搜索团队" aria-label="搜索团队" clearable /><span
|
||||
class="filter-label"
|
||||
>示例团队 · 加入功能待接入</span
|
||||
>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
<article v-for="team in items" :key="team.name" class="panel team-card">
|
||||
<span class="team-logo">{{ team.initials }}</span>
|
||||
<div style="flex: 1">
|
||||
<h2>{{ team.name }}</h2>
|
||||
<p>{{ team.description }}</p>
|
||||
<div class="tag-list">
|
||||
<span v-for="tag in team.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
<div class="card-meta" style="margin-top: 20px">
|
||||
<span>{{ team.members }} 名成员 · 示例</span
|
||||
><el-button disabled size="small" style="margin-left: auto">申请加入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<el-empty v-if="!items.length" description="没有找到团队" />
|
||||
</template>
|
||||
Reference in New Issue
Block a user