refactor: ♻️ 我的页面重构
@@ -1,6 +1,6 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
class AuthAPI {
|
||||
const AuthAPI = {
|
||||
/**
|
||||
* 登录接口
|
||||
*
|
||||
@@ -8,37 +8,40 @@ class AuthAPI {
|
||||
* @param password 密码
|
||||
* @returns 返回 token
|
||||
*/
|
||||
static login(username: string, password: string): Promise<LoginResult> {
|
||||
login(data: LoginFormData): Promise<LoginResult> {
|
||||
console.log("data", data);
|
||||
return request<LoginResult>({
|
||||
url: "/api/v1/auth/login",
|
||||
method: "POST",
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
data: data,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 登出接口
|
||||
*/
|
||||
static logout(): Promise<void> {
|
||||
return request<void>({
|
||||
logout(): Promise<void> {
|
||||
return request({
|
||||
url: "/api/v1/auth/logout",
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default AuthAPI;
|
||||
|
||||
/** 登录响应 */
|
||||
export interface LoginResult {
|
||||
/** 访问token */
|
||||
accessToken?: string;
|
||||
accessToken: string;
|
||||
/** token 类型 */
|
||||
tokenType?: string;
|
||||
}
|
||||
|
||||
export interface LoginFormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ import request from "@/utils/request";
|
||||
|
||||
const USER_BASE_URL = "/api/v1/users";
|
||||
|
||||
class UserAPI {
|
||||
const UserAPI = {
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @returns 登录用户昵称、头像信息,包括角色和权限
|
||||
*/
|
||||
static getUserInfo(): Promise<UserInfo> {
|
||||
getUserInfo(): Promise<UserInfo> {
|
||||
return request<UserInfo>({
|
||||
url: `${USER_BASE_URL}/me`,
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
export default UserAPI;
|
||||
|
||||
/** 登录用户信息 */
|
||||
|
||||
2
src/env.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workbench/index",
|
||||
"path": "pages/work/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "工作台"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/profile/index",
|
||||
"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
@@ -50,16 +50,16 @@
|
||||
"selectedIconPath": "static/tabbar/home-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/workbench/index",
|
||||
"pagePath": "pages/work/index",
|
||||
"text": "工作台",
|
||||
"iconPath": "static/tabbar/workbench.png",
|
||||
"selectedIconPath": "static/tabbar/workbench-active.png"
|
||||
"iconPath": "static/tabbar/work.png",
|
||||
"selectedIconPath": "static/tabbar/work-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/index",
|
||||
"pagePath": "pages/mine/index",
|
||||
"text": "我的",
|
||||
"iconPath": "static/tabbar/profile.png",
|
||||
"selectedIconPath": "static/tabbar/profile-active.png"
|
||||
"iconPath": "static/tabbar/mine.png",
|
||||
"selectedIconPath": "static/tabbar/mine-active.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,40 +1,78 @@
|
||||
<template>
|
||||
<view class="flex-col items-center">
|
||||
<input v-model="username" placeholder="请输入用户名" />
|
||||
<input v-model="password" placeholder="请输入密码" type="password" />
|
||||
<button class="mt-5" @click="handleLogin">登录</button>
|
||||
<view class="login">
|
||||
<image class="logo" src="@/static/logo.png" style="width: 100px; height: 100px" />
|
||||
<view class="mt-10 flex-center">
|
||||
<wd-form ref="loginFormRef" :model="loginFormData">
|
||||
<wd-cell-group border>
|
||||
<wd-input
|
||||
v-model="loginFormData.username"
|
||||
label="用户名"
|
||||
label-width="100px"
|
||||
prop="username"
|
||||
clearable
|
||||
placeholder="请输入用户名"
|
||||
:rules="[{ required: true, message: '请填写用户名' }]"
|
||||
/>
|
||||
<wd-input
|
||||
v-model="loginFormData.password"
|
||||
label="密码"
|
||||
label-width="100px"
|
||||
prop="password"
|
||||
show-password
|
||||
clearable
|
||||
placeholder="请输入密码"
|
||||
:rules="[{ required: true, message: '请填写密码' }]"
|
||||
/>
|
||||
</wd-cell-group>
|
||||
<view class="footer">
|
||||
<wd-button size="large" type="primary" block @click="handleLogin">提交</wd-button>
|
||||
</view>
|
||||
</wd-form>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type LoginFormData } from "@/api/auth";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
|
||||
// 登录表单
|
||||
const username = ref("admin");
|
||||
const password = ref("123456");
|
||||
const loginFormRef = ref();
|
||||
|
||||
const loginFormData = ref<LoginFormData>({
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
});
|
||||
|
||||
// 使用 pinia
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
await userStore.login(username.value, password.value);
|
||||
|
||||
if (userStore.token) {
|
||||
await userStore.getUserInfo(); // 登录成功后获取用户信息
|
||||
uni.showToast({ title: "登录成功", icon: "success" });
|
||||
uni.navigateBack(); // 登录成功后返回上一页
|
||||
} else {
|
||||
uni.showToast({ title: "登录失败", icon: "none" });
|
||||
}
|
||||
const handleLogin = () => {
|
||||
loginFormRef.value
|
||||
.validate()
|
||||
.then(async ({ valid }: { valid: boolean }) => {
|
||||
if (valid) {
|
||||
try {
|
||||
userStore.login(loginFormData.value).then(() => {
|
||||
uni.showToast({ title: "登录成功", icon: "success" });
|
||||
uni.navigateBack();
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log("登录失败", error.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
.error(({ errors }: { errors: any }) => {
|
||||
console.log("errors", errors);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input {
|
||||
width: 80%;
|
||||
padding: 10px;
|
||||
margin-top: 16px;
|
||||
border: 1px solid #ccc;
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
</style>
|
||||
|
||||
103
src/pages/mine/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view class="mine">
|
||||
<!-- 个人资料 -->
|
||||
<view class="header">
|
||||
<view class="flex items-center">
|
||||
<image
|
||||
class="w-100rpx h-100rpx rounded-full"
|
||||
:src="isLogin ? userInfo!.avatar : defaultAvatar"
|
||||
/>
|
||||
<text class="ml-20rpx text-32rpx">{{
|
||||
isLogin ? userInfo!.nickname : "您还未登录,请先登录"
|
||||
}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text v-if="isLogin" @click="goToLoginPage"> 个人信息 <wd-icon name="arrow-right" /> </text>
|
||||
<text v-else @click="goToLoginPage"> 去登录 <wd-icon name="arrow-right" /> </text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="relative -top-[50px] p-[40rpx]">
|
||||
<!-- 功能操作 -->
|
||||
<wd-grid clickable class="rounded-[10rpx] bg-white">
|
||||
<wd-grid-item :to="{ path: '/group' }" icon="setting" text="交流群" />
|
||||
<wd-grid-item :to="{ path: '/customer-service' }" icon="setting" text="在线客服" />
|
||||
<wd-grid-item :to="{ path: '/feedback' }" icon="setting" text="反馈社区" />
|
||||
<wd-grid-item :to="{ path: '/like-us' }" icon="setting" text="点赞我们" />
|
||||
</wd-grid>
|
||||
<!-- 菜单列表 -->
|
||||
<view class="mt50rpx">
|
||||
<wd-cell-group border>
|
||||
<wd-cell title="编辑资料" icon="setting" @click="goToEditProfile">
|
||||
<wd-icon name="arrow-right" />
|
||||
</wd-cell>
|
||||
<wd-cell title="常见问题" icon="setting" @click="goToFAQ">
|
||||
<wd-icon name="arrow-right" />
|
||||
</wd-cell>
|
||||
<wd-cell title="关于我们" icon="setting" @click="goToAbout">
|
||||
<wd-icon name="arrow-right" />
|
||||
</wd-cell>
|
||||
<wd-cell title="应用设置" icon="setting" @click="goToSettings">
|
||||
<wd-icon name="arrow-right" />
|
||||
</wd-cell>
|
||||
|
||||
<wd-cell title="退出登录" icon="setting" clickable @click="handleLogout">
|
||||
<wd-icon name="arrow-right" />
|
||||
</wd-cell>
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo);
|
||||
|
||||
const isLogin = computed(() => !!userInfo.value);
|
||||
|
||||
const defaultAvatar = "/static/images/default-avatar.png";
|
||||
|
||||
const goToLoginPage = () => {
|
||||
uni.navigateTo({ url: "/pages/login/index" });
|
||||
};
|
||||
|
||||
const goToProfile = () => {
|
||||
uni.navigateTo({ url: "/pages/profile/index" });
|
||||
};
|
||||
const goToEditProfile = () => {
|
||||
uni.navigateTo({ url: "/pages/edit-profile/index" });
|
||||
};
|
||||
const goToFAQ = () => {
|
||||
uni.navigateTo({ url: "/pages/faq/index" });
|
||||
};
|
||||
const goToAbout = () => {
|
||||
uni.navigateTo({ url: "/pages/about/index" });
|
||||
};
|
||||
const goToSettings = () => {
|
||||
uni.navigateTo({ url: "/pages/settings/index" });
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
userStore.logout().then(() => {
|
||||
uni.showToast({ title: "已退出登录", icon: "success" });
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mine {
|
||||
height: 100%;
|
||||
background: #f5f2f2;
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 50rpx 20rpx 100rpx 20rpx;
|
||||
color: #fff;
|
||||
background: linear-gradient(60deg, #165dff, #6aa1ff);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<view class="flex-center flex-col">
|
||||
<text class="text-blue font-bold text-lg">我的</text>
|
||||
|
||||
<!-- 判断是否已登录 -->
|
||||
<template v-if="isLoggedIn">
|
||||
<image :src="userInfo?.avatar" class="w100 h100 mb-5 rounded-full" />
|
||||
<text class="text-lg font-bold">{{ userInfo?.nickname }}</text>
|
||||
<button @click="handleLogout" class="mt-5">退出登录</button>
|
||||
</template>
|
||||
|
||||
<!-- 未登录时显示去登录按钮 -->
|
||||
<template v-else>
|
||||
<text>您还未登录,请先登录</text>
|
||||
<button @click="goToLoginPage" class="mt-5">去登录</button>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
|
||||
// 使用 pinia
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isLoggedIn = computed(() => userStore.token);
|
||||
const userInfo = computed(() => userStore.userInfo);
|
||||
|
||||
// 跳转到登录页面
|
||||
const goToLoginPage = () => {
|
||||
uni.navigateTo({ url: "/pages/login/index" });
|
||||
};
|
||||
|
||||
// 退出登录处理
|
||||
const handleLogout = async () => {
|
||||
await userStore.logout();
|
||||
uni.showToast({ title: "已退出登录", icon: "success" });
|
||||
};
|
||||
</script>
|
||||
BIN
src/static/images/default-avatar.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -1,48 +1,48 @@
|
||||
import { defineStore } from "pinia";
|
||||
import AuthAPI from "@/api/auth";
|
||||
import UserAPI, { UserInfo } from "@/api/user";
|
||||
import { setToken, clearToken } from "@/utils/auth";
|
||||
import AuthAPI, { type LoginFormData } from "@/api/auth";
|
||||
import UserAPI, { type UserInfo } from "@/api/user";
|
||||
import { setToken, getUserInfo, setUserInfo, clearAll } from "@/utils/cache";
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
// 确保 token 是响应式的
|
||||
const token = ref<string>(uni.getStorageSync("token") || "");
|
||||
const userInfo = ref<UserInfo | null>(null);
|
||||
const userInfo = ref<UserInfo | null>(getUserInfo());
|
||||
|
||||
// 登录
|
||||
const login = (username: string, password: string) => {
|
||||
AuthAPI.login(username, password)
|
||||
.then((res) => {
|
||||
token.value = ` ${res.accessToken}`; // Bearer token
|
||||
setToken(token.value);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.action === "TOKEN_INVALID") {
|
||||
// 处理 Token 失效,比如弹出登录提示框,而不是直接跳转
|
||||
console.log("Token 已失效,请重新登录");
|
||||
// 例如:uni.navigateTo({ url: '/pages/login/index' });
|
||||
} else {
|
||||
// 处理其他错误
|
||||
console.error("请求失败", error.message);
|
||||
}
|
||||
});
|
||||
const login = (data: LoginFormData) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
AuthAPI.login(data)
|
||||
.then((data) => {
|
||||
setToken(data.accessToken);
|
||||
getInfo();
|
||||
resolve(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("登录失败", error);
|
||||
reject(error); // 将错误抛出
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 获取用户信息
|
||||
const getUserInfo = async () => {
|
||||
const info = await UserAPI.getUserInfo();
|
||||
userInfo.value = info;
|
||||
const getInfo = () => {
|
||||
UserAPI.getUserInfo().then((data) => {
|
||||
setUserInfo(data);
|
||||
userInfo.value = data;
|
||||
});
|
||||
};
|
||||
|
||||
// 登出
|
||||
const logout = async () => {
|
||||
await AuthAPI.logout();
|
||||
userInfo.value = null;
|
||||
token.value = ""; // 清空 token
|
||||
clearToken(); // 从本地缓存移除 token
|
||||
try {
|
||||
await AuthAPI.logout(); // 调用后台注销接口
|
||||
} catch (error) {
|
||||
console.error("登出失败", error);
|
||||
} finally {
|
||||
clearAll(); // 清除本地的 token 和用户信息缓存
|
||||
userInfo.value = null; // 清空用户信息
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
token,
|
||||
userInfo,
|
||||
login,
|
||||
logout,
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
const TOKEN_KEY = "app-token";
|
||||
|
||||
function getToken(): string {
|
||||
return uni.getStorageSync(TOKEN_KEY) || "";
|
||||
}
|
||||
|
||||
function setToken(token: string) {
|
||||
return uni.setStorageSync(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
return uni.removeStorageSync(TOKEN_KEY);
|
||||
}
|
||||
|
||||
function isLogin(): boolean {
|
||||
return !!getToken();
|
||||
}
|
||||
|
||||
export { getToken, setToken, clearToken, isLogin };
|
||||
38
src/utils/cache.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
const TOKEN_KEY = "app-token";
|
||||
const USER_INFO_KEY = "user-info";
|
||||
|
||||
// 设置 token
|
||||
export function setToken(token: string) {
|
||||
uni.setStorageSync(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
// 获取 token
|
||||
export function getToken(): string {
|
||||
return uni.getStorageSync(TOKEN_KEY) || "";
|
||||
}
|
||||
|
||||
// 清除 token
|
||||
export function clearToken() {
|
||||
uni.removeStorageSync(TOKEN_KEY);
|
||||
}
|
||||
|
||||
// 设置用户信息
|
||||
export function setUserInfo(userInfo: any) {
|
||||
uni.setStorageSync(USER_INFO_KEY, userInfo);
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getUserInfo(): any {
|
||||
return uni.getStorageSync(USER_INFO_KEY) || null;
|
||||
}
|
||||
|
||||
// 清除用户信息
|
||||
export function clearUserInfo() {
|
||||
uni.removeStorageSync(USER_INFO_KEY);
|
||||
}
|
||||
|
||||
// 清除所有缓存信息
|
||||
export function clearAll() {
|
||||
clearToken();
|
||||
clearUserInfo();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getToken, clearToken } from "@/utils/auth";
|
||||
import { getToken } from "@/utils/cache";
|
||||
import { ResultCodeEnum } from "@/enums/ResultCodeEnum";
|
||||
|
||||
export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
|
||||
@@ -14,7 +14,7 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
|
||||
url: `${baseApi}${options.url}`,
|
||||
header: {
|
||||
...options.header,
|
||||
Authorization: getToken() ? `Bearer ${getToken()}` : "", // 添加 Bearer 前缀
|
||||
Authorization: getToken() ? `Bearer ${getToken()}` : "",
|
||||
},
|
||||
success: (response) => {
|
||||
console.log("success response", response);
|
||||
@@ -32,8 +32,6 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
clearToken();
|
||||
|
||||
// 此处不强制跳转到登录页,可以让调用方决定下一步操作
|
||||
reject({
|
||||
message: resData.msg || "令牌无效或过期",
|
||||
@@ -46,7 +44,6 @@ export default function request<T>(options: UniApp.RequestOptions): Promise<T> {
|
||||
uni.showToast({
|
||||
title: resData.msg || "业务处理失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
reject({
|
||||
message: resData.msg || "业务处理失败",
|
||||
|
||||