refactor: ♻️ 我的页面重构
@@ -1,7 +1,7 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
|
||||
# 项目运行的端口号
|
||||
VITE_APP_PORT = 5173
|
||||
VITE_APP_PORT = 4396
|
||||
|
||||
# API 基础路径,开发环境下的请求前缀
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
|
||||
@@ -1,42 +1,23 @@
|
||||
import globals from "globals"; // 全局变量配置
|
||||
import pluginJs from "@eslint/js"; // JavaScript 的推荐配置
|
||||
import tseslint from "typescript-eslint"; // TypeScript 配置
|
||||
import pluginVue from "eslint-plugin-vue"; // Vue 配置
|
||||
import globals from "globals";
|
||||
import js from "@eslint/js";
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, resolve } from "path";
|
||||
// ESLint 核心插件
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
import pluginTypeScript from "@typescript-eslint/eslint-plugin";
|
||||
|
||||
// 动态读取 .eslintrc-auto-import.json 文件内容
|
||||
const autoImportConfig = JSON.parse(
|
||||
readFileSync(
|
||||
resolve(dirname(fileURLToPath(import.meta.url)), ".eslintrc-auto-import.json"),
|
||||
"utf-8",
|
||||
),
|
||||
);
|
||||
// Prettier 插件及配置
|
||||
import configPrettier from "eslint-config-prettier";
|
||||
import pluginPrettier from "eslint-plugin-prettier";
|
||||
|
||||
// 解析器
|
||||
import * as parserVue from "vue-eslint-parser";
|
||||
import * as parserTypeScript from "@typescript-eslint/parser";
|
||||
|
||||
// 定义 ESLint 配置
|
||||
export default [
|
||||
{ files: ["**/*.{js,mjs,cjs,ts,vue}"] }, // 校验的文件类型
|
||||
{
|
||||
// 语言选项配置,定义全局变量
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser, // 浏览器变量 (window, document 等)
|
||||
...globals.node, // Node.js 变量 (process, require 等)
|
||||
...autoImportConfig.globals, // 自动导入的全局变量
|
||||
...{
|
||||
uni: "readonly", // uni-app 全局对象
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginJs.configs.recommended, // JavaScript 推荐配置
|
||||
...tseslint.configs.recommended, // TypeScript 推荐配置
|
||||
...pluginVue.configs["flat/essential"], // Vue 推荐配置
|
||||
{ files: ["**/*.vue"], languageOptions: { parserOptions: { parser: tseslint.parser } } }, // 对 .vue 文件使用 TypeScript 解析器
|
||||
|
||||
// 添加忽略的文件或目录
|
||||
// 通用 JavaScript 配置
|
||||
{
|
||||
...js.configs.recommended,
|
||||
ignores: [
|
||||
"/dist",
|
||||
"/public",
|
||||
@@ -46,28 +27,123 @@ export default [
|
||||
"**/*.tsbuildinfo",
|
||||
"/src/manifest.json",
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser, // 浏览器变量 (window, document 等)
|
||||
...globals.node, // Node.js 变量 (process, require 等)
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
prettier: pluginPrettier,
|
||||
},
|
||||
rules: {
|
||||
...configPrettier.rules,
|
||||
...pluginPrettier.configs.recommended.rules,
|
||||
"no-debug": "off", // 禁止 debugger
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
], // 允许未使用的变量,以 _ 开头的变量不检查
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
endOfLine: "auto", // 自动识别换行符
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// 自定义规则
|
||||
// TypeScript 配置
|
||||
{
|
||||
files: ["**/*.?([cm])ts"],
|
||||
languageOptions: {
|
||||
parser: parserTypeScript,
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": pluginTypeScript,
|
||||
},
|
||||
rules: {
|
||||
quotes: ["error", "double"], // 强制使用双引号
|
||||
"quote-props": ["error", "always"], // 强制对象的属性名使用引号
|
||||
semi: ["error", "always"], // 要求使用分号
|
||||
indent: ["error", 2], // 使用两个空格进行缩进
|
||||
"no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
|
||||
"no-trailing-spaces": "error", // 不允许行尾有空格
|
||||
...pluginTypeScript.configs.strict.rules,
|
||||
"@typescript-eslint/ban-types": "off", // 禁止特定类型
|
||||
"@typescript-eslint/no-redeclare": "error", // 禁止重复声明
|
||||
"@typescript-eslint/ban-ts-comment": "off", // 禁止特定注释
|
||||
"@typescript-eslint/no-explicit-any": "off", // 禁止使用 any
|
||||
"@typescript-eslint/prefer-as-const": "warn", // 使用 as const 替代 as 'const'
|
||||
"@typescript-eslint/no-empty-function": "off", // 禁止空函数
|
||||
"@typescript-eslint/no-non-null-assertion": "off", // 禁止非空断言
|
||||
"@typescript-eslint/no-import-type-side-effects": "error", // 禁止导入类型产生副作用
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off", // 显式函数返回类型
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{ disallowTypeAnnotations: false, fixStyle: "inline-type-imports" },
|
||||
], // 一致的类型导入
|
||||
"@typescript-eslint/prefer-literal-enum-member": ["error", { allowBitwiseExpressions: true }], // 使用字面量枚举成员
|
||||
"@typescript-eslint/no-empty-object-type": "off", // 禁止空对象类型
|
||||
},
|
||||
},
|
||||
|
||||
// TypeScript 规则
|
||||
"@typescript-eslint/no-explicit-any": "off", // 禁用 no-explicit-any 规则,允许使用 any 类型
|
||||
"@typescript-eslint/explicit-function-return-type": "off", // 不强制要求函数必须明确返回类型
|
||||
"@typescript-eslint/no-empty-interface": "off", // 禁用 no-empty-interface 规则,允许空接口声明
|
||||
"@typescript-eslint/no-empty-object-type": "off", // 允许空对象类型
|
||||
// TypeScript 声明文件的特殊配置
|
||||
{
|
||||
files: ["**/*.d.ts"],
|
||||
rules: {
|
||||
"eslint-comments/no-unlimited-disable": "off",
|
||||
"unused-imports/no-unused-vars": "off",
|
||||
},
|
||||
},
|
||||
|
||||
// Vue 规则
|
||||
"vue/multi-word-component-names": "off", // 关闭多单词组件名称的限制
|
||||
"vue/html-indent": ["error", 2], // Vue 模板中的 HTML 缩进使用两个空格
|
||||
"vue/no-v-html": "off", // 允许使用 v-html (根据实际项目需要)
|
||||
// JavaScript 配置(包含 commonjs)
|
||||
{
|
||||
files: ["**/*.?([cm])js"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-require-imports": "off", // 禁止 require
|
||||
"@typescript-eslint/no-var-requires": "off", // 禁止 require
|
||||
},
|
||||
},
|
||||
|
||||
// Vue 文件配置
|
||||
{
|
||||
files: ["**/*.vue"],
|
||||
languageOptions: {
|
||||
parser: parserVue,
|
||||
parserOptions: {
|
||||
extraFileExtensions: [".vue"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
vue: pluginVue,
|
||||
},
|
||||
processor: pluginVue.processors[".vue"],
|
||||
rules: {
|
||||
...pluginVue.configs.base.rules, // Vue 基础配置
|
||||
...pluginVue.configs["vue3-essential"].rules, // Vue3 基础配置
|
||||
...pluginVue.configs["vue3-recommended"].rules, // Vue3 推荐配置
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"vue/no-v-html": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/require-explicit-emits": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-setup-props-reactivity-loss": "off",
|
||||
"vue/html-self-closing": [
|
||||
"error",
|
||||
{
|
||||
html: {
|
||||
void: "always",
|
||||
normal: "always",
|
||||
component: "always",
|
||||
},
|
||||
svg: "always",
|
||||
math: "always",
|
||||
},
|
||||
], // 自闭合标签
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -131,6 +131,8 @@
|
||||
"unocss-preset-weapp": "^0.62.2",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"vite": "5.2.8",
|
||||
"vue-eslint-parser": "^9.4.3",
|
||||
"vue-tsc": "^1.0.24"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.2.0+sha512.98a80fd11c2e7096747762304106432b3ddc67dcf54b5a8c01c93f68a2cd5e05e6821849522a06fb76284d41a2660d5e334f2ee3bbf29183bf2e739b1dafa771"
|
||||
}
|
||||
|
||||
11618
pnpm-lock.yaml
generated
@@ -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 || "业务处理失败",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineConfig, UserConfig, ConfigEnv, loadEnv } from "vite";
|
||||
import { defineConfig, type UserConfig, type ConfigEnv, loadEnv } from "vite";
|
||||
import uni from "@dcloudio/vite-plugin-uni";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
|
||||
|
||||