搜索

📄 文章 📚 合集
热门搜索
🐘 PHP ⚡ Laravel 🎨 Vue.js ⚛️ React 📦 Yii 📘 JavaScript 🗄️ MySQL 🐳 Docker
返回合集

[板块3:Vue 3 + Element Plus 后台(含认证+核心功能)Element Plus 后台] - 11 - 实现前后端登录联调

代码示例
# 前后端联调登录

## 1. 创建认证 Store

### 1.1 创建 stores 目录

**执行命令**:

```bash
mkdir resources\js\admin\stores
```

**说明**:Windows 下需要使用反斜杠 `\`,正斜杠 `/` 会报错「命令语法不正确」。

### 1.2 创建 auth.ts

**文件路径**:`resources\js\admin\stores\auth.ts`

**完整代码**:

```typescript
import { defineStore } from 'pinia';
import axios from 'axios';

interface User {
    id: number;
    name: string;
    email: string;
}

export const useAuthStore = defineStore('auth', {
    state: () => ({
        user: null as User | null,
        token: localStorage.getItem('token'),
    }),
    getters: {
        isAuthenticated: (state) => !!state.token,
    },
    actions: {
        async login(email: string, password: string) {
            try {
                const response = await axios.post('/api/admin/login', {
                    email,
                    password,
                });
                this.token = response.data.token;
                this.user = response.data.user;
                localStorage.setItem('token', this.token);
                axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}`;
                return true;
            } catch (error) {
                return false;
            }
        },
        async logout() {
            this.token = null;
            this.user = null;
            localStorage.removeItem('token');
            delete axios.defaults.headers.common['Authorization'];
        },
        async checkAuth() {
            if (!this.token) return;
            axios.defaults.headers.common['Authorization'] = `Bearer ${this.token}`;
            try {
                const response = await axios.get('/api/admin/user');
                this.user = response.data;
            } catch {
                this.logout();
            }
        },
    },
});
```

### 1.3 代码说明

| 代码 | 作用 |
|------|------|
| `interface User` | 定义用户数据类型(id、name、email) |
| `state: () => ({...})` | 定义状态:用户信息、token |
| `isAuthenticated` | 计算属性,判断是否已登录 |
| `login()` | 发送登录请求,保存 token 到 localStorage |
| `logout()` | 清除 token 和用户信息 |
| `checkAuth()` | 页面刷新时验证 token 有效性 |

---

## 2. 修改 Login.vue 对接 Store

### 2.1 文件路径

`resources\js\admin\views\Login.vue`

### 2.2 完整代码

```vue
<template>
    <div>
        <el-card>
            <template #header>
                <div>
                    <h2>好站站后台管理</h2>
                    <p>登录开始建站</p>
                </div>
            </template>

            <el-form :model="form" :rules="rules" ref="formRef">
                <el-form-item prop="email">
                    <el-input
                        v-model="form.email"
                        placeholder="邮箱"
                        :prefix-icon="User"
                        size="large"
                    />
                </el-form-item>

                <el-form-item prop="password">
                    <el-input
                        v-model="form.password"
                        type="password"
                        placeholder="密码"
                        :prefix-icon="Lock"
                        size="large"
                        show-password
                    />
                </el-form-item>

                <el-form-item>
                    <el-button
                        type="primary"
                        size="large"
                        :loading="loading"
                        @click="handleLogin"
                        style="width: 100%"
                    >
                        登录
                    </el-button>
                </el-form-item>
            </el-form>
        </el-card>
    </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { User, Lock } from '@element-plus/icons-vue';
import { useAuthStore } from '../stores/auth';

const router = useRouter();
const authStore = useAuthStore();
const formRef = ref();
const loading = ref(false);

const form = reactive({
    email: '',
    password: '',
});

const rules = {
    email: [
        { required: true, message: '请输入邮箱', trigger: 'blur' },
        { type: 'email' as const, 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: boolean) => {
        if (!valid) return;

        loading.value = true;
        const success = await authStore.login(form.email, form.password);
        loading.value = false;

        if (success) {
            ElMessage.success('登录成功');
            router.push('/admin');
        } else {
            ElMessage.error('邮箱或密码错误');
        }
    });
};
</script>

<style scoped>
.login-container {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.login-card {
    width: 400px;
}

.login-header {
    text-align: center;
}
</style>
```

### 2.3 代码说明

| 代码 | 作用 |
|------|------|
| `import { useAuthStore } from '../stores/auth'` | 导入认证 Store |
| `const authStore = useAuthStore()` | 实例化 Store |
| `await authStore.login(...)` | 调用登录方法 |
| `router.push('/admin')` | 登录成功后跳转仪表盘 |
| `ElMessage.success()` | 成功提示 |
| `ElMessage.error()` | 错误提示 |

### 2.4 登录流程

```
用户填写邮箱和密码
    ↓
点击登录按钮
    ↓
前端表单验证(邮箱格式、密码长度)
    ↓ 验证通过
调用 authStore.login(email, password)
    ↓
发送 POST 请求到 /api/admin/login
    ↓ 成功
保存 token 到 localStorage
设置 axios 默认 Authorization Header
    ↓
显示「登录成功」提示
    ↓
跳转到 /admin 仪表盘
    ↓ 失败
显示「邮箱或密码错误」提示
```

---

## 3. 在 main.ts 中引入并使用 Pinia

### 3.1 文件路径

`resources\js\admin\main.ts`

### 3.2 完整代码

```typescript
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);

// 注册所有 Element Plus 图标组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component);
}

app.use(createPinia());
app.use(ElementPlus);
app.use(router);

app.mount('#app');
```

### 3.3 修改说明

| 新增代码 | 位置 | 作用 |
|---------|------|------|
| `import { createPinia } from 'pinia'` | 文件顶部 | 引入 Pinia |
| `app.use(createPinia())` | `app.use(router)` 之前 | 安装 Pinia 插件 |

### 3.4 说明

- Pinia 依赖包在阶段1已通过 `npm install pinia` 安装
- 阶段5需要在 `main.ts` 中**引入并使用** Pinia
- 如果不安装 Pinia,`useAuthStore()` 会返回 undefined,导致页面报错

---

## 4. 编译验证

### 4.1 执行命令

```bash
npm run build
```

### 4.2 预期结果

编译成功,无报错。

---

## 5. 测试登录

### 5.1 访问登录页面

浏览器打开:`http://engine-api.test/admin/login`

### 5.2 输入账号密码

| 字段 | 值 |
|------|-----|
| 邮箱 | `admin@haozhanzhan.com` |
| 密码 | `123456` |

### 5.3 点击登录

**预期结果**:
- 显示「登录成功」提示
- 自动跳转到 `/admin`
- 显示仪表盘页面

---

## 6. 遇到的问题及解决

### 6.1 问题:mkdir 命令语法错误

| 项目 | 内容 |
|------|------|
| **现象** | 执行 `mkdir resources/js/admin/stores` 报错「命令语法不正确」 |
| **原因** | Windows 的 `mkdir` 不支持正斜杠 `/` |
| **解决方案** | 使用反斜杠:`mkdir resources\js\admin\stores` |

### 6.2 问题:页面报错 `Cannot read properties of undefined (reading '_s')`

| 项目 | 内容 |
|------|------|
| **现象** | 访问 `/admin/login` 页面空白,控制台报错 |
| **原因** | `main.ts` 中没有引入和使用 Pinia,导致 `useAuthStore()` 返回 undefined |
| **解决方案** | 在 `main.ts` 中添加 `import { createPinia } from 'pinia'` 和 `app.use(createPinia())` |

---

## 7. 验证结果

| 验证项 | 预期结果 | 实际结果 |
|--------|---------|---------|
| 编译 | 无报错 | ✅ 通过 |
| 登录页面访问 | 显示登录表单 | ✅ 通过 |
| 正确账号密码 | 登录成功,跳转仪表盘 | ✅ 通过 |
| 错误密码 | 提示「邮箱或密码错误」 | ✅ 通过 |
| 刷新页面 | 保持登录状态 | ✅ 通过 |

🧸 adorable code

专注 PHP、JavaScript、Laravel、Vue.js、React、Yii 全栈开发。记录技术探索过程中的灵感与经验,分享工程实践洞见。

hello@adorablecode.com