更新
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
const { request } = require("../utils/request");
|
||||
/**
|
||||
* @description 小程序 auth 模块 API 封装;login/refreshToken/getAuthCurrentUser 与后端 /api/auth/* 路径一一对应
|
||||
* @filename auth.js
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
const { request } = require("/utils/request");
|
||||
|
||||
function login(data) {
|
||||
return request({
|
||||
@@ -8,6 +15,23 @@ function login(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function refreshToken(refreshToken) {
|
||||
return request({
|
||||
url: "/api/auth/tokens/refresh",
|
||||
method: "POST",
|
||||
data: { refreshToken }
|
||||
});
|
||||
}
|
||||
|
||||
function getAuthCurrentUser() {
|
||||
return request({
|
||||
url: "/api/auth/users/current",
|
||||
method: "GET"
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
login
|
||||
login,
|
||||
refreshToken,
|
||||
getAuthCurrentUser
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { request } = require("../utils/request");
|
||||
const { request } = require("/utils/request");
|
||||
|
||||
function getRouteMeta() {
|
||||
return request({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/login/index",
|
||||
"pages/home/index",
|
||||
"pages/profile/index"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,64 @@
|
||||
/**
|
||||
* @description 学生端首页 Page;并发拉取 upms + auth 当前用户,客户端二次校验 roleCodes 必含 STUDENT 防御服务端异常放行
|
||||
* @filename index.js
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
const { getCurrentUser } = require("/api/upms");
|
||||
const { getAuthCurrentUser } = require("/api/auth");
|
||||
const { clearTokens, getAccessToken } = require("/utils/session");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
title: "K12Study 小程序骨架",
|
||||
description: "这里先放首页占位,后续可扩展为家长端或学生端入口。"
|
||||
loading: false,
|
||||
error: "",
|
||||
currentUser: null
|
||||
},
|
||||
onShow() {
|
||||
const accessToken = getAccessToken();
|
||||
if (!accessToken) {
|
||||
wx.reLaunch({ url: "/pages/login/index" });
|
||||
return;
|
||||
}
|
||||
this.loadCurrentUser();
|
||||
},
|
||||
async loadCurrentUser() {
|
||||
this.setData({ loading: true, error: "" });
|
||||
try {
|
||||
const [upmsResp, authResp] = await Promise.all([
|
||||
getCurrentUser(),
|
||||
getAuthCurrentUser()
|
||||
]);
|
||||
const upmsUser = upmsResp.data || {};
|
||||
const authUser = authResp.data || {};
|
||||
const roleCodes = Array.isArray(authUser.roleCodes) ? authUser.roleCodes : [];
|
||||
const isStudent = roleCodes.some((code) => String(code).toUpperCase() === "STUDENT");
|
||||
if (!isStudent) {
|
||||
clearTokens();
|
||||
this.setData({ error: "小程序仅允许学生账号登录" });
|
||||
wx.reLaunch({ url: "/pages/login/index" });
|
||||
return;
|
||||
}
|
||||
this.setData({
|
||||
currentUser: {
|
||||
username: upmsUser.username || authUser.username || "-",
|
||||
displayName: upmsUser.displayName || authUser.displayName || "-",
|
||||
tenantId: upmsUser.tenantId || authUser.tenantId || "-",
|
||||
deptId: upmsUser.deptId || authUser.deptId || "-",
|
||||
roleCodesText: roleCodes.join(", ") || "-"
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
error: error && error.message ? error.message : "加载用户信息失败"
|
||||
});
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
handleLogout() {
|
||||
clearTokens();
|
||||
wx.reLaunch({ url: "/pages/login/index" });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
<view class="page">
|
||||
<view class="card">
|
||||
<view>{{title}}</view>
|
||||
<view style="margin-top: 16rpx; color: #64748b;">{{description}}</view>
|
||||
<view class="title">学生端首页</view>
|
||||
<view wx:if="{{loading}}" class="hint">加载用户信息中...</view>
|
||||
<view wx:elif="{{error}}" class="error">{{error}}</view>
|
||||
<view wx:elif="{{currentUser}}" class="profile">
|
||||
<view class="item">账号:{{currentUser.username}}</view>
|
||||
<view class="item">姓名:{{currentUser.displayName}}</view>
|
||||
<view class="item">租户:{{currentUser.tenantId}}</view>
|
||||
<view class="item">部门:{{currentUser.deptId}}</view>
|
||||
<view class="item">角色:{{currentUser.roleCodesText}}</view>
|
||||
</view>
|
||||
<button class="logout-btn" bindtap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
view {
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #64748b;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc2626;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.profile {
|
||||
display: grid;
|
||||
gap: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
50
app/src/pages/login/index.js
Normal file
50
app/src/pages/login/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const { login } = require("/api/auth");
|
||||
const { getAccessToken, setTokens } = require("/utils/session");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
username: "student01",
|
||||
password: "stud123",
|
||||
tenantId: "SCH-HQ",
|
||||
loading: false,
|
||||
error: ""
|
||||
},
|
||||
onShow() {
|
||||
if (getAccessToken()) {
|
||||
wx.reLaunch({ url: "/pages/home/index" });
|
||||
}
|
||||
},
|
||||
onUsernameInput(event) {
|
||||
this.setData({ username: event.detail.value });
|
||||
},
|
||||
onPasswordInput(event) {
|
||||
this.setData({ password: event.detail.value });
|
||||
},
|
||||
onTenantInput(event) {
|
||||
this.setData({ tenantId: event.detail.value });
|
||||
},
|
||||
async handleLogin() {
|
||||
if (this.data.loading) {
|
||||
return;
|
||||
}
|
||||
this.setData({ loading: true, error: "" });
|
||||
try {
|
||||
const response = await login({
|
||||
username: this.data.username,
|
||||
password: this.data.password,
|
||||
provinceCode: "330000",
|
||||
areaCode: "330100",
|
||||
tenantId: this.data.tenantId,
|
||||
clientType: "MINI"
|
||||
});
|
||||
setTokens(response.data.accessToken, response.data.refreshToken);
|
||||
wx.reLaunch({ url: "/pages/home/index" });
|
||||
} catch (error) {
|
||||
this.setData({
|
||||
error: error && error.message ? error.message : "登录失败,请稍后重试"
|
||||
});
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
3
app/src/pages/login/index.json
Normal file
3
app/src/pages/login/index.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "学生登录"
|
||||
}
|
||||
12
app/src/pages/login/index.wxml
Normal file
12
app/src/pages/login/index.wxml
Normal file
@@ -0,0 +1,12 @@
|
||||
<view class="page">
|
||||
<view class="card login-card">
|
||||
<view class="login-title">K12Study 学生端登录</view>
|
||||
<input class="login-input" placeholder="用户名" value="{{username}}" bindinput="onUsernameInput" />
|
||||
<input class="login-input" password placeholder="密码" value="{{password}}" bindinput="onPasswordInput" />
|
||||
<input class="login-input" placeholder="租户ID" value="{{tenantId}}" bindinput="onTenantInput" />
|
||||
<view wx:if="{{error}}" class="login-error">{{error}}</view>
|
||||
<button class="login-btn" loading="{{loading}}" disabled="{{loading}}" bindtap="handleLogin">
|
||||
{{loading ? '登录中...' : '登录'}}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
27
app/src/pages/login/index.wxss
Normal file
27
app/src/pages/login/index.wxss
Normal file
@@ -0,0 +1,27 @@
|
||||
.login-card {
|
||||
display: grid;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.login-input {
|
||||
border: 1px solid #dbe3f0;
|
||||
border-radius: 12rpx;
|
||||
height: 72rpx;
|
||||
padding: 0 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
color: #dc2626;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
const { getAccessToken } = require("/utils/session");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
title: "我的",
|
||||
description: "这里预留账号中心、学校切换、消息入口等能力。"
|
||||
},
|
||||
onShow() {
|
||||
if (!getAccessToken()) {
|
||||
wx.reLaunch({ url: "/pages/login/index" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
const BASE_URL = "http://localhost:8088";
|
||||
const { clearTokens, getAccessToken, getRefreshToken, setTokens } = require("./session");
|
||||
|
||||
let refreshPromise = null;
|
||||
|
||||
function isApiResponse(payload) {
|
||||
return (
|
||||
@@ -12,16 +15,29 @@ function isApiResponse(payload) {
|
||||
|
||||
function request(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const accessToken = getAccessToken();
|
||||
wx.request({
|
||||
url: `${BASE_URL}${options.url}`,
|
||||
method: options.method || "GET",
|
||||
data: options.data,
|
||||
header: {
|
||||
"Content-Type": "application/json",
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||
...(options.header || {})
|
||||
},
|
||||
success: (response) => {
|
||||
success: async (response) => {
|
||||
const payload = response.data;
|
||||
if (response.statusCode === 401 && !options.skipAuthRefresh && !isAuthTokenPath(options.url)) {
|
||||
const nextAccessToken = await tryRefreshAccessToken();
|
||||
if (nextAccessToken) {
|
||||
request({ ...options, skipAuthRefresh: true }).then(resolve).catch(reject);
|
||||
return;
|
||||
}
|
||||
clearTokens();
|
||||
redirectToLogin();
|
||||
reject(new Error("登录已过期,请重新登录"));
|
||||
return;
|
||||
}
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
const message =
|
||||
payload && typeof payload === "object" && payload.message
|
||||
@@ -47,6 +63,61 @@ function request(options) {
|
||||
});
|
||||
}
|
||||
|
||||
function isAuthTokenPath(url) {
|
||||
return url === "/api/auth/tokens" || url === "/api/auth/tokens/refresh";
|
||||
}
|
||||
|
||||
function redirectToLogin() {
|
||||
const pages = getCurrentPages();
|
||||
const currentRoute = pages.length > 0 ? pages[pages.length - 1].route : "";
|
||||
if (currentRoute !== "pages/login/index") {
|
||||
wx.reLaunch({ url: "/pages/login/index" });
|
||||
}
|
||||
}
|
||||
|
||||
function tryRefreshAccessToken() {
|
||||
if (refreshPromise) {
|
||||
return refreshPromise;
|
||||
}
|
||||
|
||||
const refreshToken = getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
refreshPromise = new Promise((resolve) => {
|
||||
wx.request({
|
||||
url: `${BASE_URL}/api/auth/tokens/refresh`,
|
||||
method: "POST",
|
||||
data: { refreshToken },
|
||||
header: { "Content-Type": "application/json" },
|
||||
success: (response) => {
|
||||
const payload = response.data;
|
||||
if (
|
||||
response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
isApiResponse(payload) &&
|
||||
payload.code === 0 &&
|
||||
payload.data &&
|
||||
payload.data.accessToken &&
|
||||
payload.data.refreshToken
|
||||
) {
|
||||
setTokens(payload.data.accessToken, payload.data.refreshToken);
|
||||
resolve(payload.data.accessToken);
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
},
|
||||
fail: () => resolve(null),
|
||||
complete: () => {
|
||||
refreshPromise = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return refreshPromise;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
request
|
||||
};
|
||||
|
||||
27
app/src/utils/session.js
Normal file
27
app/src/utils/session.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const ACCESS_TOKEN_KEY = "k12study.access-token";
|
||||
const REFRESH_TOKEN_KEY = "k12study.refresh-token";
|
||||
|
||||
function getAccessToken() {
|
||||
return wx.getStorageSync(ACCESS_TOKEN_KEY);
|
||||
}
|
||||
|
||||
function getRefreshToken() {
|
||||
return wx.getStorageSync(REFRESH_TOKEN_KEY);
|
||||
}
|
||||
|
||||
function setTokens(accessToken, refreshToken) {
|
||||
wx.setStorageSync(ACCESS_TOKEN_KEY, accessToken);
|
||||
wx.setStorageSync(REFRESH_TOKEN_KEY, refreshToken);
|
||||
}
|
||||
|
||||
function clearTokens() {
|
||||
wx.removeStorageSync(ACCESS_TOKEN_KEY);
|
||||
wx.removeStorageSync(REFRESH_TOKEN_KEY);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAccessToken,
|
||||
getRefreshToken,
|
||||
setTokens,
|
||||
clearTokens
|
||||
};
|
||||
Reference in New Issue
Block a user