修复
This commit is contained in:
1 parent
d16dbc69b4
commit
fc8c3ee3ad
2 files changed
+370
-113
No files matched your search
+272
-18
@@ -21,6 +21,7 @@ import (
|
|||||||
"server/models"
|
"server/models"
|
||||||
"server/pkg/identity"
|
"server/pkg/identity"
|
||||||
"server/pkg/jwtutil"
|
"server/pkg/jwtutil"
|
||||||
|
authsvc "server/services/auth"
|
||||||
"server/services/gueststore"
|
"server/services/gueststore"
|
||||||
"server/services/wechatmini"
|
"server/services/wechatmini"
|
||||||
|
|
||||||
@@ -83,12 +84,11 @@ func (c *GuestController) MiniLogin() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// openid 仅作溯源,拿不到不阻断游客签发
|
// openid 仅作溯源与身份识别(已绑定过正式身份时按 openid 认人),拿不到不阻断游客签发
|
||||||
var openID, unionID string
|
var openID, unionID string
|
||||||
if req.Code != "" {
|
session := guestMiniSession(cfg, req.Code)
|
||||||
if session, serr := wechatmini.Code2Session(cfg, req.Code); serr == nil && session != nil {
|
if session != nil {
|
||||||
openID, unionID = session.OpenID, session.UnionID
|
openID, unionID = session.OpenID, session.UnionID
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(req.PhoneCode) == "" {
|
if strings.TrimSpace(req.PhoneCode) == "" {
|
||||||
@@ -114,6 +114,12 @@ func (c *GuestController) MiniLogin() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手机号已注册云泽账号(或该微信已绑定正式身份):直接签发正式令牌按登录处理。
|
||||||
|
// (与 /auth/wechat/miniapp/login 同一套识别与签发逻辑)
|
||||||
|
if c.tryUserLogin(mobile, session) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guest, err := gueststore.Upsert(mobile, openID, unionID)
|
guest, err := gueststore.Upsert(mobile, openID, unionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Error("[guest] upsert 失败: %v", err)
|
logs.Error("[guest] upsert 失败: %v", err)
|
||||||
@@ -143,6 +149,258 @@ func (c *GuestController) MiniLogin() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// identityByPhone 按微信已验证手机号定位认证中心身份。
|
||||||
|
// 租户账号手机号可能落在三处,逐层兜底(先查认证中心,查不到再回退业务表):
|
||||||
|
// 1. yz_auth_identity.mobile/account/email(FindIdentityByAccount)
|
||||||
|
// 2. yz_auth_tenant_user.phone → identity_id
|
||||||
|
// 3. yz_system_tenant_user.phone → uid(全量迁移后 uid == identity_id)
|
||||||
|
func identityByPhone(mobile string) *models.AuthIdentity {
|
||||||
|
if ai, err := authsvc.FindIdentityByAccount(mobile); err == nil && ai != nil {
|
||||||
|
return ai
|
||||||
|
}
|
||||||
|
var identityID uint64
|
||||||
|
var tu models.AuthTenantUser
|
||||||
|
if err := models.Orm.QueryTable(new(models.AuthTenantUser)).
|
||||||
|
Filter("phone", mobile).Filter("delete_time__isnull", true).
|
||||||
|
OrderBy("-is_default", "id").One(&tu); err == nil {
|
||||||
|
identityID = tu.IdentityID
|
||||||
|
} else {
|
||||||
|
var su models.SystemTenantUser
|
||||||
|
if err2 := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||||
|
Filter("phone", mobile).Filter("delete_time__isnull", true).
|
||||||
|
OrderBy("-is_default", "id").One(&su); err2 == nil {
|
||||||
|
identityID = su.Uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if identityID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ai := &models.AuthIdentity{}
|
||||||
|
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
|
||||||
|
Filter("id", identityID).Filter("delete_time__isnull", true).One(ai); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ai
|
||||||
|
}
|
||||||
|
|
||||||
|
// phoneOfIdentity 取身份手机号:identity.mobile 为空时回退租户绑定/业务用户表。
|
||||||
|
func phoneOfIdentity(identityID uint64) string {
|
||||||
|
var tus []models.AuthTenantUser
|
||||||
|
if _, err := models.Orm.QueryTable(new(models.AuthTenantUser)).
|
||||||
|
Filter("identity_id", identityID).Filter("delete_time__isnull", true).
|
||||||
|
OrderBy("-is_default", "id").All(&tus); err == nil {
|
||||||
|
for _, tu := range tus {
|
||||||
|
if tu.Phone != nil {
|
||||||
|
if p := strings.TrimSpace(*tu.Phone); p != "" {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sus []models.SystemTenantUser
|
||||||
|
if _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||||
|
Filter("uid", identityID).Filter("delete_time__isnull", true).
|
||||||
|
OrderBy("-is_default", "id").All(&sus); err == nil {
|
||||||
|
for _, su := range sus {
|
||||||
|
if su.Phone != nil {
|
||||||
|
if p := strings.TrimSpace(*su.Phone); p != "" {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// guestMiniSession 拿 wx.login code 换 openid/unionid;失败不阻断(返回 nil)。
|
||||||
|
func guestMiniSession(cfg *wechatmini.Config, code string) *wechatmini.Session {
|
||||||
|
if strings.TrimSpace(code) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
session, err := wechatmini.Code2Session(cfg, code)
|
||||||
|
if err != nil {
|
||||||
|
logs.Warn("[guest] code 换取 openid 失败: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
// identityByWechat 按 openid/unionid 查已绑定的正式身份(登录页静默 bind 的落点)。
|
||||||
|
// 与小程序一键登录 resolveIdentity 同源:手机号查不到时的第二级认人方式。
|
||||||
|
func identityByWechat(openID, unionID string) *models.AuthIdentity {
|
||||||
|
var bind models.AuthIdentityThird
|
||||||
|
qs := models.Orm.QueryTable(new(models.AuthIdentityThird)).Filter("provider", "wechat_mini")
|
||||||
|
found := false
|
||||||
|
if openID != "" {
|
||||||
|
if err := qs.Filter("open_id", openID).One(&bind); err == nil {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found && unionID != "" {
|
||||||
|
if err := models.Orm.QueryTable(new(models.AuthIdentityThird)).
|
||||||
|
Filter("provider", "wechat_mini").Filter("union_id", unionID).One(&bind); err == nil {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ai := &models.AuthIdentity{}
|
||||||
|
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
|
||||||
|
Filter("id", bind.IdentityID).Filter("delete_time__isnull", true).One(ai); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ai
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindWechatMini 把当前小程序 openid 绑到该身份。手机号已经微信验证,
|
||||||
|
// 属于强证据,允许覆盖旧绑定(与 controllers/auth syncBinding 的 verified 分支一致)。
|
||||||
|
func bindWechatMini(session *wechatmini.Session, identityID uint64) {
|
||||||
|
var bind models.AuthIdentityThird
|
||||||
|
qs := models.Orm.QueryTable(new(models.AuthIdentityThird)).Filter("provider", "wechat_mini")
|
||||||
|
if err := qs.Filter("open_id", session.OpenID).One(&bind); err == nil {
|
||||||
|
if bind.IdentityID == identityID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = models.Orm.QueryTable(new(models.AuthIdentityThird)).Filter("id", bind.ID).Delete()
|
||||||
|
}
|
||||||
|
item := &models.AuthIdentityThird{IdentityID: identityID, Provider: "wechat_mini", OpenID: session.OpenID}
|
||||||
|
if session.UnionID != "" {
|
||||||
|
uid := session.UnionID
|
||||||
|
item.UnionID = &uid
|
||||||
|
}
|
||||||
|
_, _ = models.Orm.Insert(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryUserLogin 一键授权时的正式身份识别:手机号命中启用账号且该账号有企业,
|
||||||
|
// 则签发正式令牌并返回 type=user(对齐小程序一键登录:单企业直接进入,
|
||||||
|
// 多企业返回待选令牌由企业选择接口完成切换)。已处理返回 true。
|
||||||
|
// 未命中 / 账号无企业 / 签发失败 → 返回 false,回落到游客签发流程。
|
||||||
|
func (c *GuestController) tryUserLogin(mobile string, session *wechatmini.Session) bool {
|
||||||
|
var authIdentity *models.AuthIdentity
|
||||||
|
if session != nil {
|
||||||
|
authIdentity = identityByWechat(session.OpenID, session.UnionID)
|
||||||
|
}
|
||||||
|
if authIdentity == nil {
|
||||||
|
authIdentity = identityByPhone(mobile)
|
||||||
|
}
|
||||||
|
if authIdentity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 手机号已通过微信验证:把 openid 补绑/改绑到该身份(下次无手机号也能认人)
|
||||||
|
if session != nil && session.OpenID != "" {
|
||||||
|
bindWechatMini(session, authIdentity.ID)
|
||||||
|
}
|
||||||
|
if authIdentity.Status != models.AuthIdentityStatusEnabled {
|
||||||
|
c.gReply(403, "账号已被禁用或锁定,无法使用", nil)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
tenants, lerr := authsvc.ListTenantOptions(authIdentity.ID)
|
||||||
|
if lerr != nil || len(tenants) == 0 {
|
||||||
|
return false // 账号未加入任何企业:按游客模式继续使用
|
||||||
|
}
|
||||||
|
|
||||||
|
needChoose := len(tenants) > 1
|
||||||
|
tid := tenants[0].Tid
|
||||||
|
accessTTL := 0
|
||||||
|
if needChoose {
|
||||||
|
tid = authsvc.PendingTenantID
|
||||||
|
accessTTL = 600 // 待选状态令牌只够用户完成企业选择
|
||||||
|
}
|
||||||
|
|
||||||
|
username := "微信用户"
|
||||||
|
if authIdentity.Nickname != nil && strings.TrimSpace(*authIdentity.Nickname) != "" {
|
||||||
|
username = strings.TrimSpace(*authIdentity.Nickname)
|
||||||
|
} else if authIdentity.Mobile != nil && *authIdentity.Mobile != "" {
|
||||||
|
username = *authIdentity.Mobile
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, serr := authsvc.CreateSession(authsvc.SessionInfo{
|
||||||
|
IdentityID: authIdentity.ID,
|
||||||
|
Tid: tid,
|
||||||
|
ClientID: "yz-miniapp",
|
||||||
|
IP: c.Ctx.Input.IP(),
|
||||||
|
UserAgent: c.Ctx.Request.UserAgent(),
|
||||||
|
LoginType: "wechat_mini",
|
||||||
|
Amr: "wechat_mini",
|
||||||
|
})
|
||||||
|
if serr != nil {
|
||||||
|
logs.Error("[guest] 正式登录创建会话失败: %v", serr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tokens, terr := authsvc.IssueTokens(authsvc.TokenIssue{
|
||||||
|
IdentityID: authIdentity.ID,
|
||||||
|
Tid: tid,
|
||||||
|
ClientID: "yz-miniapp",
|
||||||
|
Sid: sess.Sid,
|
||||||
|
Username: username,
|
||||||
|
UserType: "app",
|
||||||
|
Amr: "wechat_mini",
|
||||||
|
AccessTTL: accessTTL,
|
||||||
|
})
|
||||||
|
if terr != nil {
|
||||||
|
logs.Error("[guest] 正式登录签发令牌失败: %v", terr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
identityIDCopy := authIdentity.ID
|
||||||
|
tidCopy := tid
|
||||||
|
amr := "wechat_mini"
|
||||||
|
_, _ = models.Orm.Insert(&models.AuthLoginLog{
|
||||||
|
Tid: &tidCopy,
|
||||||
|
IdentityID: &identityIDCopy,
|
||||||
|
UserName: username,
|
||||||
|
ClientID: "yz-miniapp",
|
||||||
|
LoginType: "wechat_mini",
|
||||||
|
Amr: &amr,
|
||||||
|
Status: 1,
|
||||||
|
Message: "小程序手机号一键登录成功(免登录入口识别为正式账号)",
|
||||||
|
IP: c.Ctx.Input.IP(),
|
||||||
|
UserAgent: c.Ctx.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
|
||||||
|
c.gReply(200, "success", map[string]interface{}{
|
||||||
|
"type": "user",
|
||||||
|
"tokens": tokens,
|
||||||
|
"tenants": tenants,
|
||||||
|
"need_choose_tenant": needChoose,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// findGuestForIdentity 找回当前身份对应的未合并游客记录:
|
||||||
|
// 优先按手机号,查不到再按该身份已绑定的微信 openid/unionid
|
||||||
|
// (微信验证号可能与账号注册号不一致,openid 是另一条可靠对应关系)。
|
||||||
|
func findGuestForIdentity(identityID uint64, mobile string) (models.GuestUser, bool) {
|
||||||
|
var guest models.GuestUser
|
||||||
|
if mobile != "" {
|
||||||
|
err := models.Orm.QueryTable(new(models.GuestUser)).
|
||||||
|
Filter("phone", mobile).Filter("merged_uid", 0).One(&guest)
|
||||||
|
if err == nil {
|
||||||
|
return guest, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var binds []models.AuthIdentityThird
|
||||||
|
if _, err := models.Orm.QueryTable(new(models.AuthIdentityThird)).
|
||||||
|
Filter("identity_id", identityID).Filter("provider", "wechat_mini").All(&binds); err == nil {
|
||||||
|
for _, b := range binds {
|
||||||
|
if b.OpenID != "" {
|
||||||
|
if err := models.Orm.QueryTable(new(models.GuestUser)).
|
||||||
|
Filter("openid", b.OpenID).Filter("merged_uid", 0).One(&guest); err == nil {
|
||||||
|
return guest, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.UnionID != nil && *b.UnionID != "" {
|
||||||
|
if err := models.Orm.QueryTable(new(models.GuestUser)).
|
||||||
|
Filter("unionid", *b.UnionID).Filter("merged_uid", 0).One(&guest); err == nil {
|
||||||
|
return guest, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return guest, false
|
||||||
|
}
|
||||||
|
|
||||||
// Merge POST /api/guest/merge(携带正式令牌)
|
// Merge POST /api/guest/merge(携带正式令牌)
|
||||||
// 按当前身份手机号找回游客记录,把 yz_guest_data 各模块数据并入正式账号。
|
// 按当前身份手机号找回游客记录,把 yz_guest_data 各模块数据并入正式账号。
|
||||||
func (c *GuestController) Merge() {
|
func (c *GuestController) Merge() {
|
||||||
@@ -161,24 +419,20 @@ func (c *GuestController) Merge() {
|
|||||||
c.gReply(500, "查询身份失败", nil)
|
c.gReply(500, "查询身份失败", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ai.Mobile == nil || strings.TrimSpace(*ai.Mobile) == "" {
|
// 回查手机号用于定位游客记录:identity.mobile 为空时回退租户绑定/业务用户表
|
||||||
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
var mobile string
|
||||||
return
|
if ai.Mobile != nil {
|
||||||
|
mobile = strings.TrimSpace(*ai.Mobile)
|
||||||
|
}
|
||||||
|
if mobile == "" {
|
||||||
|
mobile = phoneOfIdentity(ai.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var guest models.GuestUser
|
guest, found := findGuestForIdentity(ai.ID, mobile)
|
||||||
err = models.Orm.QueryTable(new(models.GuestUser)).
|
if !found {
|
||||||
Filter("phone", strings.TrimSpace(*ai.Mobile)).
|
|
||||||
Filter("merged_uid", 0).
|
|
||||||
One(&guest)
|
|
||||||
if err == orm.ErrNoRows {
|
|
||||||
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
c.gReply(500, "查询游客记录失败", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
counts, merr := mergeGuestToUser(&guest, uint64(claims.UserID), claims.TenantId, claims.Username)
|
counts, merr := mergeGuestToUser(&guest, uint64(claims.UserID), claims.TenantId, claims.Username)
|
||||||
if merr != nil {
|
if merr != nil {
|
||||||
|
|||||||
+98
-95
@@ -56,16 +56,11 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<!-- 游客冷启动授权引导:手机号授权后签发游客令牌,免登录模块数据才能保存 -->
|
<!-- 游客无感授权:无弹窗,全屏透明按钮——首次点按即拉起微信原生手机号确认层;
|
||||||
|
后端按手机号自动判定:绑定过租户→直接登录,否则→游客令牌 -->
|
||||||
<view v-if="guestAuthVisible" class="guest-auth-mask">
|
<view v-if="guestAuthVisible" class="guest-auth-mask">
|
||||||
<view class="guest-auth-card">
|
<button class="guest-auth-full" open-type="getPhoneNumber" :disabled="guestAuthLoading" @getphonenumber="onGuestPhone" />
|
||||||
<text class="guest-auth-title">开启免登录保存</text>
|
<text class="guest-auth-later" @tap="skipGuestAuth">暂不,先浏览</text>
|
||||||
<text class="guest-auth-desc">授权手机号后,记事本、日程提醒、密码库等免登录功能的数据即可保存;正式登录后会自动并入账号,不会丢失。</text>
|
|
||||||
<button class="guest-auth-btn" open-type="getPhoneNumber" :disabled="guestAuthLoading" @getphonenumber="onGuestPhone">
|
|
||||||
{{ guestAuthLoading ? '授权中…' : '立即授权' }}
|
|
||||||
</button>
|
|
||||||
<text class="guest-auth-skip" @tap="skipGuestAuth">暂不授权(使用将无法保存数据)</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
@@ -77,13 +72,13 @@ import { mpHeadPad } from '@/utils/mpHead.js'
|
|||||||
|
|
||||||
const headPad = mpHeadPad()
|
const headPad = mpHeadPad()
|
||||||
import { onShow } from '@dcloudio/uni-app'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import { isLoggedIn, getTenants, getTid, setAuthTokens, setTid, setTenantName, getUser, setUser } from '@/utils/auth.js'
|
import { isLoggedIn, getTenants, getTid, setAuthTokens, setTenants, setTid, setTenantName, getUser, setUser } from '@/utils/auth.js'
|
||||||
import { switchTenant } from '@/api/auth.js'
|
import { switchTenant } from '@/api/auth.js'
|
||||||
import { fetchHome, fetchPublicModules, fetchPublicMenus } from '@/api/workspace.js'
|
import { fetchHome, fetchPublicModules, fetchPublicMenus } from '@/api/workspace.js'
|
||||||
import { openMenu } from '@/utils/nav.js'
|
import { openMenu } from '@/utils/nav.js'
|
||||||
import { getSubsystemByModule, goSubsystem } from '@/config/subsystems.js'
|
import { getSubsystemByModule, goSubsystem } from '@/config/subsystems.js'
|
||||||
import { getGuestToken, setGuestToken } from '@/utils/guest.js'
|
import { getGuestToken, setGuestToken, clearGuestToken } from '@/utils/guest.js'
|
||||||
import { guestMiniLogin } from '@/api/guest.js'
|
import { guestMiniLogin, guestMerge } from '@/api/guest.js'
|
||||||
|
|
||||||
/* ==================== 游客手机号授权(免登录保存) ==================== */
|
/* ==================== 游客手机号授权(免登录保存) ==================== */
|
||||||
|
|
||||||
@@ -137,6 +132,12 @@ async function onGuestPhone(e) {
|
|||||||
appid = ''
|
appid = ''
|
||||||
}
|
}
|
||||||
const res = await guestMiniLogin({ code, phoneCode: detail.code, appid })
|
const res = await guestMiniLogin({ code, phoneCode: detail.code, appid })
|
||||||
|
// 已注册手机号:后端已签发正式令牌,直接按登录处理(不再当作游客)
|
||||||
|
if (res && res.type === 'user') {
|
||||||
|
guestAuthVisible.value = false
|
||||||
|
await completeUserLogin(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!res || !res.token) throw new Error('未返回游客令牌')
|
if (!res || !res.token) throw new Error('未返回游客令牌')
|
||||||
setGuestToken(res.token)
|
setGuestToken(res.token)
|
||||||
guestAuthVisible.value = false
|
guestAuthVisible.value = false
|
||||||
@@ -149,11 +150,74 @@ async function onGuestPhone(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 手机号识别为正式账号:落登录态;多企业先选企业再进入 */
|
||||||
|
async function completeUserLogin(res) {
|
||||||
|
const list = Array.isArray(res.tenants) ? res.tenants : []
|
||||||
|
setAuthTokens(res.tokens || {})
|
||||||
|
if (res.need_choose_tenant && list.length > 1) {
|
||||||
|
askChooseTenant(list)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const t0 = list[0] || {}
|
||||||
|
setTenants(list)
|
||||||
|
if (t0.tid) {
|
||||||
|
setTid(t0.tid)
|
||||||
|
setTenantName(t0.tenant_name || '')
|
||||||
|
}
|
||||||
|
// 先游客后转正:企业落定后把游客数据移植到正式账号
|
||||||
|
await mergeGuestIfAny()
|
||||||
|
await load(true)
|
||||||
|
uni.showToast({ title: '已登录,欢迎回来', icon: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 待选状态下必须选一个企业才能继续,关闭选择器后再次拉起 */
|
||||||
|
function askChooseTenant(list) {
|
||||||
|
uni.showActionSheet({
|
||||||
|
itemList: list.map((t) => t.tenant_name || `企业${t.tid}`),
|
||||||
|
success: async ({ tapIndex }) => {
|
||||||
|
const tenant = list[tapIndex]
|
||||||
|
if (!tenant) return
|
||||||
|
uni.showLoading({ title: '登录中...', mask: true })
|
||||||
|
try {
|
||||||
|
const sw = await switchTenant({ tid: tenant.tid })
|
||||||
|
if (!sw || !sw.tokens) throw new Error((sw && sw.msg) || '进入企业失败')
|
||||||
|
setAuthTokens(sw.tokens)
|
||||||
|
setTenants(list)
|
||||||
|
setTid(tenant.tid)
|
||||||
|
setTenantName(tenant.tenant_name || '')
|
||||||
|
// 先游客后转正:选定企业后移植游客数据
|
||||||
|
await mergeGuestIfAny()
|
||||||
|
await load(true)
|
||||||
|
uni.showToast({ title: '已登录,欢迎回来', icon: 'none' })
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: (e && e.message) || '进入失败,请重试', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: '请选择要进入的企业', icon: 'none' })
|
||||||
|
setTimeout(() => askChooseTenant(list), 800)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function skipGuestAuth() {
|
function skipGuestAuth() {
|
||||||
guestAuthDismissed.value = true
|
guestAuthDismissed.value = true
|
||||||
guestAuthVisible.value = false
|
guestAuthVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 存有游客令牌时静默移植数据(登录链路已带正式令牌);失败保留令牌下次登录重试 */
|
||||||
|
async function mergeGuestIfAny() {
|
||||||
|
if (!getGuestToken()) return
|
||||||
|
try {
|
||||||
|
await guestMerge()
|
||||||
|
clearGuestToken()
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[nav] guest merge failed:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 模块 code → 子系统 slug → 进入其概览页;未注册返回 false */
|
/** 模块 code → 子系统 slug → 进入其概览页;未注册返回 false */
|
||||||
function goSubsystemByModule(code) {
|
function goSubsystemByModule(code) {
|
||||||
const sub = getSubsystemByModule(code)
|
const sub = getSubsystemByModule(code)
|
||||||
@@ -273,7 +337,6 @@ async function load(force = false) {
|
|||||||
const data = await fetchHome(force)
|
const data = await fetchHome(force)
|
||||||
user.value = data.user || {}
|
user.value = data.user || {}
|
||||||
tenants.value = getTenants()
|
tenants.value = getTenants()
|
||||||
currentTid.value = getTid()
|
|
||||||
modules.value = Array.isArray(data.modules) ? data.modules : []
|
modules.value = Array.isArray(data.modules) ? data.modules : []
|
||||||
menus.value = Array.isArray(data.menus) ? data.menus : []
|
menus.value = Array.isArray(data.menus) ? data.menus : []
|
||||||
loadError.value = ''
|
loadError.value = ''
|
||||||
@@ -356,43 +419,6 @@ function goLogin(name = '') {
|
|||||||
uni.navigateTo({ url: '/pages/login/login' })
|
uni.navigateTo({ url: '/pages/login/login' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTenantPicker() {
|
|
||||||
if (tenants.value.length < 2) return
|
|
||||||
tenantPickerVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeTenantPicker() {
|
|
||||||
if (!switchingTenant.value) tenantPickerVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function changeTenant(item) {
|
|
||||||
const tid = Number(item && item.tid)
|
|
||||||
if (!tid || tid === currentTid.value || switchingTenant.value) {
|
|
||||||
tenantPickerVisible.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switchingTenant.value = true
|
|
||||||
uni.showLoading({ title: '正在切换...', mask: true })
|
|
||||||
try {
|
|
||||||
const result = await switchTenant({ tid })
|
|
||||||
if (!result || !result.tokens) throw new Error((result && result.msg) || '切换公司失败')
|
|
||||||
setAuthTokens(result.tokens)
|
|
||||||
setTid(tid)
|
|
||||||
const tenantName = item.tenant_name || `企业${tid}`
|
|
||||||
setTenantName(tenantName)
|
|
||||||
user.value = Object.assign({}, user.value, { tenant: tenantName, tid })
|
|
||||||
currentTid.value = tid
|
|
||||||
tenantPickerVisible.value = false
|
|
||||||
await load(true)
|
|
||||||
uni.showToast({ title: '已切换公司', icon: 'success' })
|
|
||||||
} catch (err) {
|
|
||||||
uni.showToast({ title: (err && err.message) || '切换公司失败', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
uni.hideLoading()
|
|
||||||
switchingTenant.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== 生命周期 ==================== */
|
/* ==================== 生命周期 ==================== */
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -627,7 +653,7 @@ onShow(() => {
|
|||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============ 游客授权引导弹窗 ============ */
|
/* ============ 游客无感授权层(无弹窗,首次点按即拉起微信原生确认) ============ */
|
||||||
.guest-auth-mask {
|
.guest-auth-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -635,58 +661,35 @@ onShow(() => {
|
|||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
background: rgba(15, 23, 42, 0.55);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 56rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.guest-auth-card {
|
.guest-auth-full {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 560rpx;
|
height: 100%;
|
||||||
background: #ffffff;
|
padding: 0;
|
||||||
border-radius: 28rpx;
|
margin: 0;
|
||||||
padding: 44rpx 36rpx 32rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20rpx;
|
|
||||||
box-shadow: 0 24rpx 64rpx rgba(15, 23, 42, 0.22);
|
|
||||||
}
|
|
||||||
|
|
||||||
.guest-auth-title {
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #17345f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guest-auth-desc {
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 1.65;
|
|
||||||
color: #64748b;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guest-auth-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 8rpx;
|
|
||||||
background: linear-gradient(145deg, #69a5ff, #3478f6);
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 44rpx;
|
|
||||||
line-height: 2.6;
|
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
&[disabled] {
|
&::after {
|
||||||
opacity: 0.6;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.guest-auth-skip {
|
.guest-auth-later {
|
||||||
font-size: 22rpx;
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 64rpx;
|
||||||
|
z-index: 100;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24rpx;
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
padding: 8rpx 16rpx;
|
padding: 16rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user