feat: Phase 3 - API layer + Pinia stores + app integration

- HTTP client (axios interceptors, token mgmt, typed APIs)
- Pinia stores: token, user (login/logout), app (dark mode, sidebar)
- globalConfig TS types + Window augmentation
- Vue Router (hash history, auth guard)
- Login/Home/Mine pages (Vant UI)
- Vant integration + globalConfig dev script
- Build passes (vue-tsc + vite)
This commit is contained in:
Ubuntu
2026-06-15 20:56:05 +08:00
parent ffbdb093a9
commit ed9eedc519
19 changed files with 2080 additions and 8 deletions

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

@@ -0,0 +1,125 @@
<script setup lang="ts">
/**
* 首页 (placeholder)
*
* 使用 Vant Tabbar 实现底部导航切换,
* 当前仅展示占位内容,后续集成地图、管网等功能模块。
*/
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
/** 当前激活的 Tab */
const active = ref(0)
/**
* Tab 切换处理
*/
function onTabChange(index: number): void {
active.value = index
if (index === 0) {
router.replace('/home')
} else if (index === 1) {
router.replace('/mine')
}
}
</script>
<template>
<div class="home-page">
<!-- 顶部导航栏 -->
<van-nav-bar title="舆图智慧水务" fixed placeholder />
<!-- 页面主体区域 -->
<div class="home-content">
<div class="welcome-card">
<h2>欢迎使用智慧水务</h2>
<p>移动端管理平台</p>
</div>
<div class="feature-grid">
<div class="feature-item" v-for="i in 4" :key="i">
<div class="feature-icon"></div>
<span class="feature-label">功能模块 {{ i }}</span>
</div>
</div>
</div>
<!-- 底部导航栏 -->
<van-tabbar v-model="active" :fixed="true" :placeholder="true" @change="onTabChange">
<van-tabbar-item icon="home-o" name="首页">首页</van-tabbar-item>
<van-tabbar-item icon="user-o" name="我的">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<style lang="scss" scoped>
.home-page {
min-height: 100vh;
background: var(--color-bg-page);
:deep(.van-nav-bar) {
background: var(--color-primary);
--van-nav-bar-title-text-color: #fff;
}
}
.home-content {
padding: 16px;
}
.welcome-card {
background: var(--color-bg-card);
border-radius: 12px;
padding: 32px 24px;
text-align: center;
box-shadow: var(--shadow-sm);
margin-bottom: 16px;
h2 {
font-size: 20px;
color: var(--color-text-primary);
margin-bottom: 8px;
}
p {
font-size: 14px;
color: var(--color-text-secondary);
margin: 0;
}
}
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.feature-item {
background: var(--color-bg-card);
border-radius: 12px;
padding: 24px 16px;
text-align: center;
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: background-color var(--transition-fast);
&:active {
background: var(--color-border-light);
}
.feature-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--color-primary-bg);
margin: 0 auto 8px;
}
.feature-label {
font-size: 14px;
color: var(--color-text-regular);
}
}
</style>

153
src/views/login/index.vue Normal file
View File

@@ -0,0 +1,153 @@
<script setup lang="ts">
/**
* 登录页面 (placeholder)
*
* 包含用户名、密码输入表单,调用 userStore.login 完成登录。
* 登录成功后跳转至首页(或 redirect 参数指定的页面)。
*/
import { ref, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showSuccessToast, showFailToast } from 'vant'
import { useUserStore } from '@/stores/user'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
/** 表单加载状态 */
const loading = ref(false)
/** 登录表单数据 */
const loginForm = reactive({
username: '',
password: '',
})
/** 表单校验规则 */
const rules = {
username: [{ required: true, message: '请输入用户名' }],
password: [{ required: true, message: '请输入密码' }],
}
/**
* 处理登录提交
*/
async function handleSubmit(): Promise<void> {
loading.value = true
try {
await userStore.login({
username: loginForm.username,
password: loginForm.password,
})
showSuccessToast('登录成功')
// 跳转到 redirect 指定的页面或首页
const redirect = (route.query.redirect as string) || '/home'
router.replace(redirect)
} catch (err) {
const msg = err instanceof Error ? err.message : '登录失败,请重试'
showFailToast(msg)
} finally {
loading.value = false
}
}
</script>
<template>
<div class="login-page">
<!-- 头部 Logo 区域 -->
<div class="login-header">
<h1 class="login-title">舆图智慧水务</h1>
<p class="login-subtitle">移动端管理平台</p>
</div>
<!-- 登录表单 -->
<div class="login-form-wrapper">
<van-form @submit="handleSubmit">
<van-field
v-model="loginForm.username"
name="username"
label="用户名"
placeholder="请输入用户名"
:rules="rules.username"
clearable
left-icon="user-o"
/>
<van-field
v-model="loginForm.password"
name="password"
label="密码"
placeholder="请输入密码"
type="password"
:rules="rules.password"
left-icon="lock"
/>
<div class="login-button-wrapper">
<van-button
round
block
type="primary"
native-type="submit"
:loading="loading"
loading-text="登录中..."
>
</van-button>
</div>
</van-form>
</div>
<!-- 底部版权 -->
<div class="login-footer">
<span>v2.0.0</span>
</div>
</div>
</template>
<style lang="scss" scoped>
.login-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
background: var(--color-bg-page);
padding: 0 24px;
}
.login-header {
text-align: center;
margin-bottom: 48px;
}
.login-title {
font-size: 28px;
font-weight: 600;
color: var(--color-primary);
margin-bottom: 8px;
}
.login-subtitle {
font-size: 14px;
color: var(--color-text-secondary);
}
.login-form-wrapper {
width: 100%;
background: var(--color-bg-card);
border-radius: 12px;
padding: 24px 16px;
box-shadow: var(--shadow-md);
}
.login-button-wrapper {
margin-top: 24px;
padding: 0 16px;
}
.login-footer {
position: fixed;
bottom: 32px;
font-size: 12px;
color: var(--color-text-placeholder);
}
</style>

77
src/views/mine/index.vue Normal file
View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
/**
* 个人中心页面 (placeholder)
*
* 展示用户基本信息,提供退出登录等功能入口。
*/
import { useRouter } from 'vue-router'
const router = useRouter()
</script>
<template>
<div class="mine-page">
<van-nav-bar title="我的" fixed placeholder />
<div class="mine-content">
<div class="user-card">
<div class="avatar-placeholder"></div>
<div class="user-info">
<span class="user-name">未登录</span>
</div>
</div>
<van-cell-group inset>
<van-cell title="个人信息" is-link />
<van-cell title="系统设置" is-link />
<van-cell title="关于我们" is-link />
</van-cell-group>
<div class="logout-wrapper">
<van-button round block type="danger" @click="router.replace('/login')">
退出登录
</van-button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.mine-page {
min-height: 100vh;
background: var(--color-bg-page);
}
.mine-content {
padding: 16px;
}
.user-card {
display: flex;
align-items: center;
gap: 12px;
background: var(--color-bg-card);
border-radius: 12px;
padding: 24px 16px;
margin-bottom: 16px;
box-shadow: var(--shadow-sm);
}
.avatar-placeholder {
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--color-primary-bg);
}
.user-name {
font-size: 16px;
font-weight: 500;
color: var(--color-text-primary);
}
.logout-wrapper {
margin-top: 24px;
padding: 0 16px;
}
</style>