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:
125
src/views/home/index.vue
Normal file
125
src/views/home/index.vue
Normal 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
153
src/views/login/index.vue
Normal 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
77
src/views/mine/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user