wip: 临时提交
This commit is contained in:
@@ -7,6 +7,7 @@ VITE_APP_PORT = 4096
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# API 服务器的 URL
|
||||
VITE_APP_API_URL = https://api.youlai.tech
|
||||
VITE_APP_API_URL = http://localhost:8989
|
||||
|
||||
VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
|
||||
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
|
||||
#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
|
||||
10
components.d.ts
vendored
10
components.d.ts
vendored
@@ -19,16 +19,13 @@ declare module 'vue' {
|
||||
QiunDataCharts: typeof import('./src/components/qiun-data-charts/qiun-data-charts.vue')['default']
|
||||
QiunError: typeof import('./src/components/qiun-error/qiun-error.vue')['default']
|
||||
QiunLoading: typeof import('./src/components/qiun-loading/qiun-loading.vue')['default']
|
||||
TodoItem: (typeof import("./src/components/todo/TodoItem.vue"))["default"]
|
||||
TodoList: (typeof import("./src/components/todo/TodoList.vue"))["default"]
|
||||
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
|
||||
WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default']
|
||||
WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default']
|
||||
WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default']
|
||||
WdCollapse: (typeof import("wot-design-uni/components/wd-collapse/wd-collapse.vue"))["default"]
|
||||
WdCollapseItem: (typeof import("wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue"))["default"]
|
||||
WdCollapse: typeof import('wot-design-uni/components/wd-collapse/wd-collapse.vue')['default']
|
||||
WdCollapseItem: typeof import('wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue')['default']
|
||||
WdConfigProvider: typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue')['default']
|
||||
WdDivider: (typeof import("wot-design-uni/components/wd-divider/wd-divider.vue"))["default"]
|
||||
WdForm: typeof import('wot-design-uni/components/wd-form/wd-form.vue')['default']
|
||||
WdGrid: typeof import('wot-design-uni/components/wd-grid/wd-grid.vue')['default']
|
||||
WdGridItem: typeof import('wot-design-uni/components/wd-grid-item/wd-grid-item.vue')['default']
|
||||
@@ -37,7 +34,6 @@ declare module 'vue' {
|
||||
WdImgCropper: typeof import('wot-design-uni/components/wd-img-cropper/wd-img-cropper.vue')['default']
|
||||
WdInput: typeof import('wot-design-uni/components/wd-input/wd-input.vue')['default']
|
||||
WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
|
||||
WdNavbar: typeof import('wot-design-uni/components/wd-navbar/wd-navbar.vue')['default']
|
||||
WdNoticeBar: typeof import('wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue')['default']
|
||||
WdNotify: typeof import('wot-design-uni/components/wd-notify/wd-notify.vue')['default']
|
||||
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
|
||||
@@ -48,7 +44,9 @@ declare module 'vue' {
|
||||
WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']
|
||||
WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default']
|
||||
WdTag: typeof import('wot-design-uni/components/wd-tag/wd-tag.vue')['default']
|
||||
WdTextarea: typeof import('wot-design-uni/components/wd-textarea/wd-textarea.vue')['default']
|
||||
WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default']
|
||||
WdUpload: typeof import('wot-design-uni/components/wd-upload/wd-upload.vue')['default']
|
||||
WechatProfile: typeof import('./src/components/business/WechatProfile.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,18 @@ const AuthAPI = {
|
||||
* @returns 登录结果
|
||||
*/
|
||||
login(data: LoginData): Promise<LoginResult> {
|
||||
const formData = {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
};
|
||||
|
||||
return request<LoginResult>({
|
||||
url: `${AUTH_BASE_URL}/login`,
|
||||
method: "POST",
|
||||
data,
|
||||
data: formData,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -42,7 +50,7 @@ const AuthAPI = {
|
||||
* @param code 微信登录凭证
|
||||
* @returns 登录结果
|
||||
*/
|
||||
wechatLogin(code: string): Promise<LoginResult> {
|
||||
loginByWxMiniAppCode(code: string): Promise<LoginResult> {
|
||||
return request<LoginResult>({
|
||||
url: `${AUTH_BASE_URL}/wx/miniapp/code-login`,
|
||||
method: "POST",
|
||||
@@ -55,7 +63,7 @@ const AuthAPI = {
|
||||
* @param data 包含code、encryptedData、iv等手机号相关数据
|
||||
* @returns 登录结果
|
||||
*/
|
||||
wechatPhoneLogin(data: WxLoginData): Promise<LoginResult> {
|
||||
loginByWxMiniAppPhone(data: WxLoginData): Promise<LoginResult> {
|
||||
return request<LoginResult>({
|
||||
url: `${AUTH_BASE_URL}/wx/miniapp/phone-login`,
|
||||
method: "POST",
|
||||
|
||||
@@ -1,57 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTheme } from "@/composables/useTheme";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
|
||||
const { theme, themeVars } = useTheme();
|
||||
|
||||
// 页面标题
|
||||
const pageTitle = ref("");
|
||||
// 是否显示返回按钮
|
||||
const showBackButton = ref(false);
|
||||
// 是否显示导航栏
|
||||
const showNavbar = ref(true);
|
||||
|
||||
// 获取当前页面路由信息
|
||||
const getCurrentPageRoute = () => {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
return currentPage.route || "";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
// 处理返回按钮点击
|
||||
const handleBack = () => {
|
||||
uni.navigateBack({
|
||||
fail: () => {
|
||||
uni.switchTab({ url: "/pages/index/index" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 页面加载时处理导航栏配置
|
||||
onLoad(() => {
|
||||
const route = getCurrentPageRoute();
|
||||
|
||||
// 获取当前页面实例
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
|
||||
// 尝试获取页面的navigationBarTitleText
|
||||
const navigationBarTitleText = currentPage.$page?.style?.navigationBarTitleText || "";
|
||||
pageTitle.value = navigationBarTitleText;
|
||||
|
||||
// 如果是首页或tabbar页面,不显示返回按钮
|
||||
showBackButton.value = pages.length > 1;
|
||||
|
||||
// 某些页面可能不需要显示导航栏
|
||||
if (route.includes("/index/index") || route.includes("/login")) {
|
||||
showNavbar.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -71,16 +21,7 @@ export default {
|
||||
custom-style="background-color: #f5f5f5;min-height: 100vh"
|
||||
:class="{ 'wot-theme-dark': theme === 'dark' }"
|
||||
>
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar :title="pageTitle" :border="false" @click-left="handleBack">
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="20" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<slot />
|
||||
|
||||
<wd-notify />
|
||||
<wd-toast />
|
||||
<wd-message-box />
|
||||
|
||||
@@ -143,8 +143,8 @@ const isDarkMode = computed(() => theme.value === "dark");
|
||||
|
||||
// 登录表单数据
|
||||
const loginFormData = ref<LoginData>({
|
||||
username: "",
|
||||
password: "",
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
});
|
||||
|
||||
// 获取重定向参数
|
||||
|
||||
@@ -27,12 +27,12 @@ export const useUserStore = defineStore("user", () => {
|
||||
// 微信基础授权登录
|
||||
const loginWithWxCode = (code: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
AuthAPI.wechatLogin(code)
|
||||
AuthAPI.loginByWxMiniAppCode(code)
|
||||
.then((data) => {
|
||||
setAccessToken(data.accessToken);
|
||||
resolve(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((error: any) => {
|
||||
console.error("微信授权登录失败", error);
|
||||
reject(error);
|
||||
});
|
||||
@@ -42,12 +42,12 @@ export const useUserStore = defineStore("user", () => {
|
||||
// 微信手机号授权登录
|
||||
const loginWithWxPhone = (data: WxLoginData): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
AuthAPI.wechatPhoneLogin(data)
|
||||
.then((result) => {
|
||||
AuthAPI.loginByWxMiniAppPhone(data)
|
||||
.then((result: any) => {
|
||||
setAccessToken(result.accessToken);
|
||||
resolve(result);
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((error: any) => {
|
||||
console.error("微信手机号登录失败", error);
|
||||
reject(error);
|
||||
});
|
||||
@@ -93,6 +93,11 @@ export const useUserStore = defineStore("user", () => {
|
||||
clearTokens(); // 清除本地的 token
|
||||
Storage.remove(USER_INFO_KEY); // 清除用户信息缓存
|
||||
userInfo.value = undefined; // 清空用户信息
|
||||
|
||||
// 跳转到登录页面
|
||||
uni.reLaunch({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,3 +15,15 @@ html {
|
||||
html.dark {
|
||||
background: #121212;
|
||||
}
|
||||
|
||||
/* 微信小程序顶部安全区域适配 */
|
||||
page {
|
||||
padding-top: constant(safe-area-inset-top); /* iOS < 11.2 */
|
||||
padding-top: env(safe-area-inset-top); /* iOS >= 11.2 */
|
||||
}
|
||||
|
||||
/* 顶部导航栏适配 */
|
||||
.nav-bar {
|
||||
padding-top: constant(safe-area-inset-top); /* iOS < 11.2 */
|
||||
padding-top: env(safe-area-inset-top); /* iOS >= 11.2 */
|
||||
}
|
||||
|
||||
17
src/types/env.d.ts
vendored
17
src/types/env.d.ts
vendored
@@ -1,22 +1,21 @@
|
||||
// src/types/env.d.ts
|
||||
/**
|
||||
* 环境变量类型声明
|
||||
*/
|
||||
interface ImportMetaEnv {
|
||||
/**
|
||||
* 应用端口
|
||||
*/
|
||||
VITE_APP_PORT: number;
|
||||
/**
|
||||
* API 基础路径
|
||||
*/
|
||||
VITE_APP_BASE_API: string;
|
||||
/**
|
||||
* API 服务器的 URL
|
||||
*/
|
||||
VITE_APP_API_URL: string;
|
||||
/** API基础路径 */
|
||||
readonly VITE_APP_BASE_API: string;
|
||||
/** API服务器URL */
|
||||
readonly VITE_APP_API_URL: string;
|
||||
|
||||
/**
|
||||
* WebSocket 端点
|
||||
*/
|
||||
VITE_APP_WS_ENDPOINT: string;
|
||||
readonly VITE_APP_WS_ENDPOINT?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@@ -1,54 +1,4 @@
|
||||
import { getAccessToken, getRefreshToken, setAccessToken } from "./auth";
|
||||
|
||||
// 刷新令牌的锁,防止多个请求同时刷新令牌
|
||||
let isRefreshing = false;
|
||||
// 请求队列,存储需要等待令牌刷新的请求
|
||||
let refreshSubscribers: Array<(token: string) => void> = [];
|
||||
|
||||
// 添加订阅者
|
||||
const subscribeTokenRefresh = (callback: (token: string) => void) => {
|
||||
refreshSubscribers.push(callback);
|
||||
};
|
||||
|
||||
// 执行所有订阅者
|
||||
const onRefreshed = (token: string) => {
|
||||
refreshSubscribers.forEach((callback) => callback(token));
|
||||
refreshSubscribers = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
* @returns 新的访问令牌
|
||||
*/
|
||||
const refreshToken = async (): Promise<string> => {
|
||||
try {
|
||||
const refreshToken = getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
throw new Error("刷新令牌不存在");
|
||||
}
|
||||
|
||||
const response = await uni.request({
|
||||
url: "/api/v1/auth/refresh-token",
|
||||
method: "POST",
|
||||
data: { refreshToken },
|
||||
header: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = response.data as any;
|
||||
if (data.code !== 200 || !data.data.accessToken) {
|
||||
throw new Error("刷新令牌失败");
|
||||
}
|
||||
|
||||
const newToken = data.data.accessToken;
|
||||
setAccessToken(newToken);
|
||||
return newToken;
|
||||
} catch (error) {
|
||||
console.error("刷新令牌失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
import { getAccessToken } from "./auth";
|
||||
|
||||
// 请求配置
|
||||
interface RequestOptions<T = any> {
|
||||
@@ -71,104 +21,49 @@ function request<T = any>(options: RequestOptions): Promise<T> {
|
||||
header["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 根据平台决定URL前缀
|
||||
let requestUrl = options.url;
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序环境,使用完整URL
|
||||
requestUrl = `${import.meta.env.VITE_APP_API_URL}${options.url}`;
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// 非微信小程序环境,使用代理前缀
|
||||
requestUrl = `${import.meta.env.VITE_APP_BASE_API}${options.url}`;
|
||||
// #endif
|
||||
|
||||
// 统一处理请求
|
||||
const handleRequest = () => {
|
||||
uni.request({
|
||||
url: options.url,
|
||||
method: options.method,
|
||||
data: options.data,
|
||||
header,
|
||||
timeout: options.timeout || 30000,
|
||||
responseType: options.responseType,
|
||||
success: (res: any) => {
|
||||
// 请求成功
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res.data.data);
|
||||
}
|
||||
// 未授权错误
|
||||
else if (res.statusCode === 401) {
|
||||
// 尝试刷新令牌
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
|
||||
refreshToken()
|
||||
.then((newToken) => {
|
||||
// 令牌刷新成功,通知所有等待的请求
|
||||
onRefreshed(newToken);
|
||||
// 重新发起请求
|
||||
header["Authorization"] = `Bearer ${newToken}`;
|
||||
handleRequest();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("令牌刷新失败:", err);
|
||||
// 刷新失败,清除订阅者
|
||||
refreshSubscribers = [];
|
||||
// 重定向到登录页
|
||||
uni.redirectTo({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
reject(err);
|
||||
})
|
||||
.finally(() => {
|
||||
isRefreshing = false;
|
||||
});
|
||||
} else {
|
||||
// 当前已有刷新令牌的请求,将此请求加入队列
|
||||
subscribeTokenRefresh((newToken) => {
|
||||
header["Authorization"] = `Bearer ${newToken}`;
|
||||
handleRequest();
|
||||
});
|
||||
}
|
||||
}
|
||||
// 其他错误
|
||||
else {
|
||||
const errorMsg = res.data.message || `请求失败: ${res.statusCode}`;
|
||||
reject(new Error(errorMsg));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || "网络请求失败"));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 检查令牌是否过期
|
||||
if (token) {
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
|
||||
refreshToken()
|
||||
.then((newToken) => {
|
||||
// 令牌刷新成功,通知所有等待的请求
|
||||
onRefreshed(newToken);
|
||||
// 重新发起请求
|
||||
header["Authorization"] = `Bearer ${newToken}`;
|
||||
handleRequest();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("令牌刷新失败:", err);
|
||||
// 刷新失败,清除订阅者
|
||||
refreshSubscribers = [];
|
||||
// 重定向到登录页
|
||||
uni.redirectTo({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
reject(err);
|
||||
})
|
||||
.finally(() => {
|
||||
isRefreshing = false;
|
||||
uni.request({
|
||||
url: requestUrl,
|
||||
method: options.method,
|
||||
data: options.data,
|
||||
header,
|
||||
timeout: options.timeout || 30000,
|
||||
responseType: options.responseType,
|
||||
success: (res: any) => {
|
||||
// 请求成功
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res.data.data);
|
||||
}
|
||||
// 未授权错误
|
||||
else if (res.statusCode === 401) {
|
||||
// 直接跳转到登录页
|
||||
uni.redirectTo({
|
||||
url: "/pages/login/index",
|
||||
});
|
||||
} else {
|
||||
// 当前已有刷新令牌的请求,将此请求加入队列
|
||||
subscribeTokenRefresh((newToken) => {
|
||||
header["Authorization"] = `Bearer ${newToken}`;
|
||||
handleRequest();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 令牌有效或无需令牌,直接发起请求
|
||||
handleRequest();
|
||||
}
|
||||
reject(new Error("未授权,请重新登录"));
|
||||
}
|
||||
// 其他错误
|
||||
else {
|
||||
const errorMsg = res.data.message || `请求失败: ${res.statusCode}`;
|
||||
reject(new Error(errorMsg));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || "网络请求失败"));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
|
||||
host: "0.0.0.0",
|
||||
port: +env.VITE_APP_PORT,
|
||||
open: true,
|
||||
// 代理配置只在 H5(浏览器)开发时生效。 其他平台(如小程序、App)在开发时不使用 Vite 的开发服务器,它们直接运行在各自的环境中。
|
||||
proxy: {
|
||||
[env.VITE_APP_BASE_API]: {
|
||||
changeOrigin: true,
|
||||
|
||||
Reference in New Issue
Block a user