feat: 完成核心页面开发

This commit is contained in:
UGREEN USER
2026-01-28 14:43:18 +08:00
parent b657c3d8aa
commit c635b5e494
19 changed files with 747 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>创建预约</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>${file}</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

142
src/views/auth/Login.vue Normal file
View File

@@ -0,0 +1,142 @@
<template>
<div class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1 class="logo">GameGroup</h1>
<p class="subtitle">游戏社群管理平台</p>
</div>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
class="auth-form"
@submit.prevent="handleLogin"
>
<el-form-item prop="account">
<el-input
v-model="formData.account"
placeholder="用户名/邮箱/手机号"
size="large"
prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="formData.password"
type="password"
placeholder="密码"
size="large"
prefix-icon="Lock"
show-password
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
:loading="loading"
native-type="submit"
class="submit-btn"
>
登录
</el-button>
</el-form-item>
</el-form>
<div class="auth-footer">
<p>还没有账号?<router-link to="/register" class="link">立即注册</router-link></p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
const formData = reactive({
account: '',
password: ''
})
const formRules: FormRules = {
account: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6位', trigger: 'blur' }
]
}
const handleLogin = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
await authStore.login(formData.account, formData.password)
ElMessage.success('登录成功')
const redirect = (route.query.redirect as string) || '/'
router.push(redirect)
} catch (error) {
console.error('登录失败:', error)
} finally {
loading.value = false
}
})
}
</script>
<style scoped lang="scss">
.auth-page {
@apply min-h-screen bg-gradient-to-br from-primary-100 to-accent-100 flex items-center justify-center p-4;
}
.auth-container {
@apply bg-white rounded-2xl shadow-2xl p-8 w-full max-w-md;
}
.auth-header {
@apply text-center mb-8;
}
.logo {
@apply text-3xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent;
}
.subtitle {
@apply text-gray-500 mt-2;
}
.auth-form {
@apply space-y-4;
}
.submit-btn {
@apply w-full;
}
.auth-footer {
@apply text-center mt-6 text-sm text-gray-500;
.link {
@apply text-primary-500 hover:text-primary-600 font-medium;
}
}
</style>

196
src/views/auth/Register.vue Normal file
View File

@@ -0,0 +1,196 @@
<template>
<div class="auth-page">
<div class="auth-container">
<div class="auth-header">
<h1 class="logo">GameGroup</h1>
<p class="subtitle">创建新账号</p>
</div>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
class="auth-form"
@submit.prevent="handleRegister"
>
<el-form-item prop="username">
<el-input
v-model="formData.username"
placeholder="用户名"
size="large"
prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="email">
<el-input
v-model="formData.email"
placeholder="邮箱(可选)"
size="large"
prefix-icon="Message"
/>
</el-form-item>
<el-form-item prop="phone">
<el-input
v-model="formData.phone"
placeholder="手机号(可选)"
size="large"
prefix-icon="Phone"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="formData.password"
type="password"
placeholder="密码"
size="large"
prefix-icon="Lock"
show-password
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="formData.confirmPassword"
type="password"
placeholder="确认密码"
size="large"
prefix-icon="Lock"
show-password
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
:loading="loading"
native-type="submit"
class="submit-btn"
>
注册
</el-button>
</el-form-item>
</el-form>
<div class="auth-footer">
<p>已有账号?<router-link to="/login" class="link">立即登录</router-link></p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { useAuthStore } from '@/stores/auth'
const router = useRouter()
const authStore = useAuthStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
const formData = reactive({
username: '',
email: '',
phone: '',
password: '',
confirmPassword: ''
})
const validateConfirmPassword = (rule: any, value: string, callback: any) => {
if (value !== formData.password) {
callback(new Error('两次密码不一致'))
} else {
callback()
}
}
const formRules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度为3-20个字符', trigger: 'blur' }
],
email: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度为6-20个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' }
]
}
const handleRegister = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
await authStore.register({
username: formData.username,
email: formData.email || undefined,
phone: formData.phone || undefined,
password: formData.password
})
ElMessage.success('注册成功')
router.push('/')
} catch (error) {
console.error('注册失败:', error)
} finally {
loading.value = false
}
})
}
</script>
<style scoped lang="scss">
// 使用与登录页相同的样式
.auth-page {
@apply min-h-screen bg-gradient-to-br from-primary-100 to-accent-100 flex items-center justify-center p-4;
}
.auth-container {
@apply bg-white rounded-2xl shadow-2xl p-8 w-full max-w-md;
}
.auth-header {
@apply text-center mb-8;
}
.logo {
@apply text-3xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent;
}
.subtitle {
@apply text-gray-500 mt-2;
}
.auth-form {
@apply space-y-4;
}
.submit-btn {
@apply w-full;
}
.auth-footer {
@apply text-center mt-6 text-sm text-gray-500;
.link {
@apply text-primary-500 hover:text-primary-600 font-medium;
}
}
</style>

12
src/views/game/Create.vue Normal file
View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>${file}</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>游戏详情</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

9
src/views/game/List.vue Normal file
View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>游戏列表</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>创建小组</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>小组详情</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

85
src/views/group/List.vue Normal file
View File

@@ -0,0 +1,85 @@
<template>
<div class="group-list-page">
<div class="page-header">
<h1>小组管理</h1>
<el-button type="primary" @click="router.push('/groups/create')">
<el-icon><Plus /></el-icon>
创建小组
</el-button>
</div>
<el-tabs v-model="activeTab" class="group-tabs">
<el-tab-pane label="全部" name="all" />
<el-tab-pane label="我创建的" name="created" />
<el-tab-pane label="我加入的" name="joined" />
</el-tabs>
<div v-loading="loading" class="group-grid">
<GroupCard
v-for="group in filteredGroups"
:key="group.id"
:group="group"
@click="router.push(`/groups/${group.id}`)"
/>
</div>
<el-empty v-if="!loading && !filteredGroups.length" description="暂无小组" />
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Plus } from '@element-plus/icons-vue'
import GroupCard from '@/components/business/GroupCard/GroupCard.vue'
import { groupApi } from '@/api/group'
import type { GroupInfo } from '@/types/group'
const router = useRouter()
const loading = ref(false)
const activeTab = ref('all')
const groups = ref<GroupInfo[]>([])
const filteredGroups = computed(() => {
if (activeTab.value === 'all') {
return groups.value
} else if (activeTab.value === 'created') {
return groups.value.filter(g => g.myRole === 'owner')
} else {
return groups.value.filter(g => g.myRole && g.myRole !== 'owner')
}
})
onMounted(async () => {
await loadGroups()
})
async function loadGroups() {
loading.value = true
try {
groups.value = await groupApi.getMyGroups()
} catch (error) {
console.error('加载小组失败:', error)
} finally {
loading.value = false
}
}
</script>
<style scoped lang="scss">
.group-list-page {
@apply space-y-6;
}
.page-header {
@apply flex items-center justify-between;
}
.group-tabs {
@apply bg-white rounded-xl p-4;
}
.group-grid {
@apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4;
}
</style>

165
src/views/home/index.vue Normal file
View File

@@ -0,0 +1,165 @@
<template>
<div class="home-page">
<!-- 欢迎卡片 -->
<Card class="welcome-card">
<h2>欢迎{{ userInfo?.nickname || userInfo?.username }}</h2>
<p class="welcome-text">今天有 {{ todayAppointments }} 个预约待参加</p>
</Card>
<!-- 我的小组 -->
<section class="section">
<div class="section-header">
<h3>我的小组</h3>
<el-button text type="primary" @click="router.push('/groups')">
查看全部
</el-button>
</div>
<div v-if="groups.length" class="group-grid">
<GroupCard
v-for="group in groups.slice(0, 3)"
:key="group.id"
:group="group"
@click="router.push(`/groups/${group.id}`)"
/>
</div>
<el-empty v-else description="暂无小组" />
</section>
<!-- 即将开始的预约 -->
<section class="section">
<div class="section-header">
<h3>即将开始的活动</h3>
<el-button text type="primary" @click="router.push('/appointments')">
查看全部
</el-button>
</div>
<div v-if="upcomingAppointments.length" class="appointment-list">
<AppointmentCard
v-for="appointment in upcomingAppointments"
:key="appointment.id"
:appointment="appointment"
@view-detail="router.push(`/appointments/${appointment.id}`)"
/>
</div>
<el-empty v-else description="暂无即将开始的预约" />
</section>
<!-- 热门游戏 -->
<section class="section">
<div class="section-header">
<h3>热门游戏</h3>
<el-button text type="primary" @click="router.push('/games')">
查看全部
</el-button>
</div>
<div v-if="popularGames.length" class="game-grid">
<GameCard
v-for="game in popularGames"
:key="game.id"
:game="game"
@click="router.push(`/games/${game.id}`)"
/>
</div>
<el-empty v-else description="暂无游戏" />
</section>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import Card from '@/components/common/Card/Card.vue'
import GroupCard from '@/components/business/GroupCard/GroupCard.vue'
import GameCard from '@/components/business/GameCard/GameCard.vue'
import AppointmentCard from '@/components/business/AppointmentCard/AppointmentCard.vue'
import { groupApi } from '@/api/group'
import { gameApi } from '@/api/game'
import { appointmentApi } from '@/api/appointment'
import type { GroupInfo } from '@/types/group'
import type { GameInfo } from '@/types/game'
import type { AppointmentInfo } from '@/types/appointment'
const router = useRouter()
const authStore = useAuthStore()
const userInfo = computed(() => authStore.userInfo)
const groups = ref<GroupInfo[]>([])
const popularGames = ref<GameInfo[]>([])
const upcomingAppointments = ref<AppointmentInfo[]>([])
const todayAppointments = computed(() => {
return upcomingAppointments.value.length
})
onMounted(async () => {
await loadGroups()
await loadPopularGames()
await loadUpcomingAppointments()
})
async function loadGroups() {
try {
groups.value = await groupApi.getMyGroups()
} catch (error) {
console.error('加载小组失败:', error)
}
}
async function loadPopularGames() {
try {
popularGames.value = await gameApi.getPopularGames(6)
} catch (error) {
console.error('加载游戏失败:', error)
}
}
async function loadUpcomingAppointments() {
try {
upcomingAppointments.value = await appointmentApi.getMyAppointments({
status: 'open'
})
} catch (error) {
console.error('加载预约失败:', error)
}
}
</script>
<style scoped lang="scss">
.home-page {
@apply space-y-6;
}
.welcome-card {
@apply bg-gradient-to-r from-primary-500 to-accent-500 text-white;
h2 {
@apply text-2xl font-bold mb-2;
}
.welcome-text {
@apply text-white/90;
}
}
.section {
@apply bg-white rounded-xl p-6;
}
.section-header {
@apply flex items-center justify-between mb-4;
h3 {
@apply text-lg font-semibold text-gray-900;
}
}
.group-grid,
.game-grid {
@apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4;
}
.appointment-list {
@apply space-y-4;
}
</style>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>${file}</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>${file}</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>荣誉墙</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>${file}</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>${file}</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<div class="page">
<h1>积分排行榜</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>个人资料</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,12 @@
<template>
<div class="page">
<h1>设置</h1>
<p>功能开发中...</p>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>