| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- import {
- DEMO_ACCOUNT,
- DEMO_PASSWORD,
- getAuthCredentials,
- logout,
- saveCurrentUser,
- saveLoginSession,
- savePermissionData,
- saveRememberedCredentials,
- } from "./storage";
- import { clearAttendanceCache } from "@/api/attendance";
- const SERVER_KEY = "apiInfo";
- const DEFAULT_SERVER = {
- mode: "demo",
- protocol: "http://",
- hostname: "",
- port: "",
- };
- export function getServerConfig() {
- const saved = uni.getStorageSync(SERVER_KEY) || {};
- return {
- ...DEFAULT_SERVER,
- ...saved,
- protocol: saved.protocol || saved.protocal || DEFAULT_SERVER.protocol,
- };
- }
- export function saveServerConfig(config) {
- const normalized = {
- mode: config.mode === "server" ? "server" : "demo",
- protocol: config.protocol === "https://" ? "https://" : "http://",
- hostname: String(config.hostname || "")
- .trim()
- .replace(/^https?:\/\//, "")
- .replace(/\/$/, ""),
- port: String(config.port || "").trim(),
- };
- uni.setStorageSync(SERVER_KEY, normalized);
- return normalized;
- }
- // H5 dev(pnpm dev:h5)走 Vite 代理避开浏览器 CORS;其他场景仍用配置里的真实地址
- const useDevProxy =
- typeof window !== "undefined" &&
- typeof process !== "undefined" &&
- process.env?.NODE_ENV !== "production";
- export function getApiBaseUrl(config = getServerConfig()) {
- if (config.mode !== "server" || !config.hostname) return "";
- if (useDevProxy) return "/api";
- const port = config.port ? `:${config.port}` : "";
- return `${config.protocol}${config.hostname}${port}/api`;
- }
- export function request({
- url,
- method = "GET",
- data,
- token,
- sessionId,
- timeout = 15000,
- }) {
- return new Promise((resolve, reject) => {
- // 未显式传 token 时自动从登录态读取,登录后所有请求自动带上凭证
- let effectiveToken = token;
- let effectiveSessionId = sessionId;
- if (!effectiveToken) {
- const stored = getAuthCredentials();
- effectiveToken = stored.token;
- if (!effectiveSessionId) effectiveSessionId = stored.sessionId;
- }
- const header = effectiveToken
- ? {
- "content-type": "application/json",
- "zoomwin-token": effectiveToken,
- Authorization: effectiveToken,
- "zoomwin-sid": effectiveSessionId || "",
- }
- : { "content-type": "application/json", platform: "wxapp" };
- uni.request({
- url,
- method,
- data,
- header,
- timeout,
- success: (response) => {
- if (response.statusCode === 401) {
- // 服务端认为凭证过期:清本地会话 + 清考勤缓存 + 跳登录页
- logout();
- clearAttendanceCache();
- uni.showToast({
- title: "登录已过期,请重新登录",
- icon: "none",
- duration: 1500,
- });
- setTimeout(() => uni.reLaunch({ url: "/pages/login/index" }), 1200);
- reject(new Error("身份验证已过期,请重新登录"));
- return;
- }
- const body = response.data || {};
- if (response.statusCode < 200 || response.statusCode >= 300) {
- reject(
- new Error(
- body.message || `服务器响应异常(${response.statusCode})`,
- ),
- );
- return;
- }
- if (body.code !== undefined && Number(body.code) !== 0) {
- reject(new Error(body.message || "请求失败"));
- return;
- }
- resolve(body);
- },
- fail: (error) => reject(new Error(error.errMsg || "无法连接服务器")),
- });
- });
- }
- /**
- * multipart/form-data 上传封装,对应 /main/file/uploadFile 等需要走 uni.uploadFile 的接口。
- * 与 request() 的差异:
- * - 不写 content-type(uni-app 会自动设 multipart/form-data + boundary)
- * - 走 uni.uploadFile,文件路径用 filePath,表单字段名用 name(默认 multiPartFile)
- * - H5 端响应体可能是 JSON 字符串,需要 typeof === 'string' 时 JSON.parse
- * 401、code !== 0 等失败语义与 request() 完全一致,调用方可统一处理。
- */
- export function uploadFile({
- url,
- filePath,
- name = "multiPartFile",
- formData,
- timeout = 30000,
- }) {
- return new Promise((resolve, reject) => {
- if (!filePath) {
- reject(new Error("缺少文件路径"));
- return;
- }
- const { token, sessionId } = getAuthCredentials();
- const header = token
- ? {
- "zoomwin-token": token,
- Authorization: token,
- "zoomwin-sid": sessionId || "",
- }
- : { platform: "wxapp" };
- uni.uploadFile({
- url,
- filePath,
- name,
- formData,
- header,
- timeout,
- success: (response) => {
- if (response.statusCode === 401) {
- logout();
- clearAttendanceCache();
- uni.showToast({
- title: "登录已过期,请重新登录",
- icon: "none",
- duration: 1500,
- });
- setTimeout(() => uni.reLaunch({ url: "/pages/login/index" }), 1200);
- reject(new Error("身份验证已过期,请重新登录"));
- return;
- }
- let body = response.data;
- if (typeof body === "string") {
- try {
- body = JSON.parse(body);
- } catch {
- // 不是 JSON:保持原字符串,下方按非 2xx/message 处理
- }
- }
- const payload = body && typeof body === "object" ? body : {};
- if (
- response.statusCode < 200 ||
- response.statusCode >= 300
- ) {
- reject(
- new Error(
- payload.message || `服务器响应异常(${response.statusCode})`,
- ),
- );
- return;
- }
- if (payload.code !== undefined && Number(payload.code) !== 0) {
- reject(new Error(payload.message || "请求失败"));
- return;
- }
- resolve(payload);
- },
- fail: (error) => reject(new Error(error.errMsg || "文件上传失败")),
- });
- });
- }
- export async function testServerConnection(config) {
- const normalized = {
- mode: "server",
- protocol: config.protocol === "https://" ? "https://" : "http://",
- hostname: String(config.hostname || "")
- .trim()
- .replace(/^https?:\/\//, "")
- .replace(/\/$/, ""),
- port: String(config.port || "").trim(),
- };
- const baseUrl = getApiBaseUrl(normalized);
- if (!baseUrl) throw new Error("请填写服务器地址");
- await request({
- url: `${baseUrl}/main/connection/getConnectionTest`,
- timeout: 5000,
- });
- return true;
- }
- function collectAuthorities(tree) {
- const authorities = [];
- const walk = (list) => {
- for (const item of Array.isArray(list) ? list : []) {
- if (item && item.menuType === 2) authorities.push(item);
- if (item?.children?.length) walk(item.children);
- }
- };
- walk(tree);
- return authorities;
- }
- async function loadPermissionTree(baseUrl) {
- const response = await request({
- url: `${baseUrl}/system/resources/getResourcesTreePDA`,
- });
- const tree = Array.isArray(response.data) ? response.data : [];
- savePermissionData(tree, collectAuthorities(tree));
- return tree;
- }
- // 拉取当前登录人的扩展资料(部门、岗位、角色等),登录后调用一次写入本地
- export async function fetchCurrentUser(baseUrl) {
- if (!baseUrl) throw new Error("缺少 baseUrl");
- const response = await request({
- url: `${baseUrl}/system/account/getLoginUser`,
- });
- return response.data || null;
- }
- // 拉取员工完整档案(含 deptName / postId / secondLinkName / secondLinkPhone),
- // 与 getLoginUser 合并补全部门岗位与紧急联系人
- // profile 响应把用户数据放在 user 子对象里,内部摊平到顶层
- // 岗位名称不在 profile 直接返回,而是 postId(逗号分隔的 id 串),
- // 内部再调 /hr/position/page 用 id 匹配出 positionName 后回填到返回对象的 position 字段
- export async function fetchUserDetail(baseUrl, userId) {
- if (!baseUrl) throw new Error("缺少 baseUrl");
- if (!userId && userId !== 0) throw new Error("缺少用户ID");
- const response = await request({
- url: `${baseUrl}/main/users/${userId}/profile`,
- });
- const data = response.data || null;
- if (!data) return null;
- // 嵌套 user 包装时,把 user 的字段提到顶层(deptName / secondLinkName 等),
- // 同时保留顶层其他字段(postId 等);mapCurrentUser 不必关心嵌套结构
- const profile = data.user && typeof data.user === "object"
- ? { ...data, ...data.user }
- : data;
- try {
- const names = await resolvePostIds(baseUrl, profile.postId);
- if (names) profile.position = names;
- } catch (error) {
- // 岗位解析失败不影响其他字段
- }
- return profile;
- }
- // 把 profile.postId("1111,2222")解析成岗位名称字符串,用 / 拼接多个岗位
- async function resolvePostIds(baseUrl, postIdString) {
- if (!postIdString) return "";
- const ids = String(postIdString)
- .split(",")
- .map((s) => s.trim())
- .filter(Boolean);
- if (!ids.length) return "";
- const response = await request({
- url: `${baseUrl}/hr/position/page`,
- method: "POST",
- data: { pageNum: 1, size: 1000, status: 1 },
- });
- const pageData = response.data || {};
- const records = Array.isArray(pageData.list)
- ? pageData.list
- : Array.isArray(pageData)
- ? pageData
- : [];
- const names = ids
- .map((id) => {
- const match = records.find((r) => String(r.id) === id);
- return match ? String(match.positionName || match.name || "") : "";
- })
- .filter(Boolean);
- return names.join("、");
- }
- export async function performLogin({ account, password, rememberPassword }) {
- const config = getServerConfig();
- const loginName = String(account || "").trim();
- const loginPwd = String(password || "");
- if (config.mode !== "server") {
- await new Promise((resolve) => setTimeout(resolve, 450));
- if (
- loginName.toUpperCase() !== DEMO_ACCOUNT ||
- loginPwd !== DEMO_PASSWORD
- ) {
- throw new Error("工号或密码错误");
- }
- const data = {
- token: `demo-token-${Date.now()}`,
- sessionId: "demo-session",
- userId: DEMO_ACCOUNT,
- loginName: DEMO_ACCOUNT,
- userName: "陈晓雨",
- };
- saveLoginSession(data, loginName);
- savePermissionData([], []);
- saveRememberedCredentials({
- account: loginName,
- password: loginPwd,
- rememberPassword,
- });
- return data;
- }
- const baseUrl = getApiBaseUrl(config);
- if (!baseUrl) throw new Error("请先配置服务器地址");
- const response = await request({
- url: `${baseUrl}/main/user/login`,
- method: "POST",
- data: { loginName, loginPwd },
- });
- const data = response.data || {};
- if (!data.token) throw new Error("登录响应中缺少 Token");
- saveLoginSession(data, loginName);
- saveRememberedCredentials({
- account: loginName,
- password: loginPwd,
- rememberPassword,
- });
- try {
- await loadPermissionTree(baseUrl);
- } catch (error) {
- savePermissionData([], []);
- uni.showToast({ title: error.message || "权限加载失败", icon: "none" });
- }
- try {
- // 部门/岗位在 getLoginUser 里没有,单独调 getById 拿,再合并
- const [basic, detail] = await Promise.allSettled([
- fetchCurrentUser(baseUrl),
- fetchUserDetail(baseUrl, data.userId),
- ]);
- const basicData = basic.status === "fulfilled" ? basic.value : null;
- const detailData = detail.status === "fulfilled" ? detail.value : null;
- // getById 字段覆盖基础信息(deptName / postName / joinDate 等)
- const merged = detailData
- ? { ...(basicData || {}), ...detailData }
- : basicData;
- if (merged) saveCurrentUser(merged);
- if (detail.status === "rejected") {
- uni.showToast({
- title: detail.reason?.message || "员工档案加载失败",
- icon: "none",
- });
- }
- } catch (error) {
- uni.showToast({ title: error.message || "用户信息加载失败", icon: "none" });
- }
- return data;
- }
- export async function getCompanyBranding(config = getServerConfig()) {
- if (config.mode !== "server") return null;
- const baseUrl = getApiBaseUrl(config);
- if (!baseUrl) return null;
- const response = await request({
- url: `${baseUrl}/pda/mes/us/indexName`,
- timeout: 5000,
- });
- return response.data || null;
- }
- // 服务器模式下退出登录:通知后端作废 token,再由页面清理本地 storage 并跳登录页
- // 演示模式或未配置服务器时直接返回(不抛错),由调用方继续清理本地
- export async function serverLogout() {
- if (getServerConfig().mode !== "server") return;
- const baseUrl = getApiBaseUrl();
- if (!baseUrl) return;
- try {
- await request({
- url: `${baseUrl}/main/user/logout`,
- method: "POST",
- timeout: 5000,
- });
- } catch (error) {
- // 服务器登出失败(网络/401/token 过期等)不阻塞本地清理
- console.warn("[auth] server logout failed:", error?.message || error);
- }
- }
|