WXL
11 小时以前 05c363fdd7ab04e3bd9a753e2c5d5bfff04d681c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
<template>
  <view class="login-container">
    <view v-if="isAutoLogining" class="sso-mask">
      <uni-load-more
        status="loading"
        :content-text="{ contentdown: '自动登录中...' }"
      ></uni-load-more>
    </view>
    <view class="header">
      <image src="/static/avatar/logo.png" class="logo" />
      <text class="hospital-name">青大附院OPO管理平台</text>
    </view>
    <view class="form-container">
      <view class="input-group">
        <uni-icons type="contact" size="24" color="#409EFF" />
        <input v-model="username" placeholder="请输入账号" class="input" />
      </view>
      <view class="input-group">
        <uni-icons type="locked" size="24" color="#409EFF" />
        <input
          v-model="password"
          :password="!showPassword"
          placeholder="请输入密码"
          class="input"
        />
        <uni-icons
          :type="showPassword ? 'eye' : 'eye-slash'"
          size="22"
          color="#999"
          @click="showPassword = !showPassword"
        />
      </view>
      <button
        class="login-btn"
        :class="{ active: username && password }"
        @click="handleLogin"
        hover-class="button-hover"
      >
        登录
      </button>
    </view>
  </view>
</template>
 
<script setup>
import { ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { useUserStore } from "@/stores/user";
import { getToken, setToken } from "@/utils/auth";
import { encrypt } from "@/utils/crypto";
 
let isAutoLogining = false;
let hasAutoLogin = false;
 
const username = ref("");
const password = ref("");
const showPassword = ref(false);
const redirect = ref("");
 
// tabBar 页面列表(与 pages.json 保持一致)
const tabBarPages = [
  "/pages/index/index",
  "/pages/appointment/index",
  "/pages/consultation/index",
  "/pages/my/index",
];
 
onLoad(async (options) => {
  console.log("登录页onLoad,原始参数:", options);
  const launchQuery = uni.getLaunchOptionsSync().query || {};
  const allParams = { ...launchQuery, ...options };
  console.log("合并后参数:", allParams);
 
  // 提取 redirect
  if (allParams.redirect) {
    redirect.value = decodeURIComponent(allParams.redirect);
  }
 
  // ✅ 优先处理 code 免登
  const code = allParams.code;
  if (code) {
    console.log("检测到免登码,开始解析:", code);
    // 显示 loading
    uni.showLoading({ title: "解析免登信息...", mask: true });
    try {
      // 调用解析接口
      const res = await uni.$uapi.get("/GiLink/getCode", { code });
      uni.hideLoading();
      console.log(res.data);
      res.data.passWord = res.data.userPhone;
      if (!res.data || !res.data.userName || !res.data.passWord) {
        throw new Error("解析免登码失败");
      }
      let obj = JSON.parse(res.data.extContent);
      // const { userName, passWord, ...otherParams } = res;
      console.log(res.data.userName);
 
      const userName = res.data.userName;
      const passWord = res.data.passWord;
      const otherParams = {
        id: obj.infoId,
        type: obj.type,
        status: obj.status,
        fcid: obj.id,
      };
      console.log("解析结果:", { userName, otherParams });
      // 清除可能存在的旧 token
      if (getToken()) uni.removeStorageSync("token");
      // 执行 SSO 登录,并将其他业务参数传递下去
      await handleSSOLogin(userName, passWord, otherParams);
    } catch (err) {
      uni.hideLoading();
      console.error("免登码解析失败:", err);
      uni.showToast({ title: "免登录失败,请手动登录", icon: "none" });
    }
    return;
  }
 
  // 原有逻辑:无 code 且已有 token 跳转目标页
  if (getToken()) {
    setTimeout(() => navigateToTarget(), 100);
    return;
  }
 
  // 普通登录,清空表单
  username.value = "";
  password.value = "";
});
 
onShow(() => {
  if (hasAutoLogin) hasAutoLogin = false;
});
 
/**
 * SSO 免登
 */
const handleSSOLogin = async (userName, passWord, pageParams = {}) => {
  if (isAutoLogining) return;
  isAutoLogining = true;
  console.log("开始SSO免登:", { userName, pageParams });
 
  uni.showLoading({ title: "自动登录中...", mask: true });
 
  try {
    const tokenRes = await uni.$uapi.post("/getToken", { userName, passWord });
    uni.hideLoading();
 
    if (!tokenRes.data?.token) throw new Error("获取token失败");
 
    setToken(tokenRes.data.token);
 
    const userStore = useUserStore();
    const userInfo = await uni.$uapi.get("/getInfo");
    if (userInfo && userInfo.user) {
      userStore.setUserInfo(userInfo.user);
      if (userInfo.roles) userStore.setroleKey(userInfo.roles);
    }
 
    // 确定目标页面
    let targetPage = redirect.value;
    if (!targetPage) {
      const launchOptions = uni.getLaunchOptionsSync();
      const originalPath = launchOptions.path;
      if (originalPath && !originalPath.includes("login/Login")) {
        targetPage = "/" + originalPath;
      }
    }
    if (!targetPage) targetPage = "/pages/index/index";
 
    hasAutoLogin = true;
    // 跳转时携带 pageParams(业务参数)
    await navigateToTargetPage(targetPage, pageParams);
  } catch (err) {
    uni.hideLoading();
    console.error("SSO免登失败:", err);
    uni.showToast({
      title: err.message || "自动登录失败,请手动登录",
      icon: "none",
      duration: 3000,
    });
    uni.removeStorageSync("token");
  } finally {
    isAutoLogining = false;
  }
};
 
/**
 * 普通登录
 */
const handleLogin = async () => {
  try {
    if (!username.value || !password.value) {
      uni.showToast({ title: "请输入账号密码", icon: "none" });
      return;
    }
    uni.showLoading({ title: "登录中...", mask: true });
 
    const userStore = useUserStore();
    const encryptedPassword = encrypt(password.value);
    const encryptedUsername = encrypt(username.value);
 
    const loginRes = await uni.$uapi.post("/login", {
      username: encryptedUsername,
      password: encryptedPassword,
    });
 
    if (!loginRes || !loginRes.token) throw new Error("登录失败");
    setToken(loginRes.token);
 
    const userInfo = await uni.$uapi.get("/getInfo");
    if (userInfo && userInfo.user) {
      userStore.setUserInfo(userInfo.user);
 
      if (userInfo.roles) userStore.setroleKey(userInfo.roles);
    }
 
    uni.hideLoading();
    await navigateToTarget();
  } catch (err) {
    uni.hideLoading();
    console.error("登录失败:", err);
    uni.showToast({
      title: err.message || "登录失败",
      icon: "none",
      duration: 3000,
    });
  }
};
 
/**
 * 根据 redirect 跳转
 */
const navigateToTarget = () => {
  if (redirect.value) {
    return navigateToTargetPage(redirect.value, {});
  } else {
    console.log("没有目标页面,跳转首页");
    uni.switchTab({ url: "/pages/index/index" });
    return Promise.resolve(true);
  }
};
 
/**
 * 通用跳转函数
 * @param {string} targetPage 目标页面路径
 * @param {object} pageParams 附加参数
 */
const navigateToTargetPage = (targetPage, pageParams = {}) => {
  return new Promise((resolve) => {
    if (!targetPage) {
      console.log("目标页为空,跳转首页");
      uni.switchTab({ url: "/pages/index/index" });
      resolve(true);
      return;
    }
 
    let finalUrl = targetPage;
    const paramKeys = Object.keys(pageParams).filter(
      (key) => pageParams[key] !== undefined && pageParams[key] !== null,
    );
    if (paramKeys.length > 0) {
      const queryStr = paramKeys
        .map((key) => `${key}=${encodeURIComponent(pageParams[key])}`)
        .join("&");
      finalUrl += (finalUrl.includes("?") ? "&" : "?") + queryStr + "&sso=1";
    }
 
    console.log("最终跳转目标URL:", finalUrl);
 
    const baseUrl = finalUrl.split("?")[0];
    const isTabBar = tabBarPages.includes(baseUrl);
 
    setTimeout(() => {
      if (isTabBar) {
        uni.switchTab({
          url: baseUrl,
          success: () => resolve(true),
          fail: (err) => {
            console.error("switchTab失败:", err);
            uni.switchTab({ url: "/pages/index/index" });
            resolve(false);
          },
        });
      } else {
        uni.redirectTo({
          url: finalUrl,
          success: () => resolve(true),
          fail: (err) => {
            console.error("redirectTo失败:", err);
            uni.switchTab({ url: "/pages/index/index" });
            resolve(false);
          },
        });
      }
    }, 300);
  });
};
</script>
 
<style scoped>
.login-container {
  padding: 40rpx;
  background: linear-gradient(to bottom, #e6f7ff, #ffffff);
  height: 100vh;
  box-sizing: border-box;
  position: relative;
}
 
.sso-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}
 
.header {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 100rpx 0 60rpx;
}
 
.logo {
  width: 160rpx;
  height: 160rpx;
  border-radius: 50%;
  border: 3rpx solid #d6ecff;
  box-shadow: 0 6rpx 18rpx rgba(24, 144, 255, 0.3);
}
 
.hospital-name {
  font-size: 38rpx;
  font-weight: bold;
  color: #1890ff;
  margin-top: 24rpx;
  letter-spacing: 3rpx;
}
 
.form-container {
  background: #ffffff;
  border-radius: 24rpx;
  padding: 50rpx;
  box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.1);
}
 
.input-group {
  display: flex;
  align-items: center;
  flex-direction: row;
  padding: 28rpx 0;
  border-bottom: 1rpx solid #f0f0f0;
  flex-wrap: nowrap;
}
 
.input {
  flex: 1;
  margin: 0 20rpx;
  font-size: 30rpx;
  color: #333;
  background: transparent;
}
 
.login-btn {
  margin-top: 60rpx;
  height: 90rpx;
  line-height: 90rpx;
  border-radius: 50rpx;
  font-size: 34rpx;
  border: none;
  color: white;
  background: #c0dfff;
  transition: all 0.3s ease;
}
 
.login-btn.active {
  background: linear-gradient(to right, #40a9ff, #1890ff);
  box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.4);
}
 
.button-hover {
  opacity: 0.8;
}
</style>