修复
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/pkg/identity"
|
||||
"server/pkg/jwtutil"
|
||||
authsvc "server/services/auth"
|
||||
"server/services/gueststore"
|
||||
"server/services/wechatmini"
|
||||
|
||||
@@ -83,12 +84,11 @@ func (c *GuestController) MiniLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
// openid 仅作溯源,拿不到不阻断游客签发
|
||||
// openid 仅作溯源与身份识别(已绑定过正式身份时按 openid 认人),拿不到不阻断游客签发
|
||||
var openID, unionID string
|
||||
if req.Code != "" {
|
||||
if session, serr := wechatmini.Code2Session(cfg, req.Code); serr == nil && session != nil {
|
||||
openID, unionID = session.OpenID, session.UnionID
|
||||
}
|
||||
session := guestMiniSession(cfg, req.Code)
|
||||
if session != nil {
|
||||
openID, unionID = session.OpenID, session.UnionID
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.PhoneCode) == "" {
|
||||
@@ -114,6 +114,12 @@ func (c *GuestController) MiniLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
// 手机号已注册云泽账号(或该微信已绑定正式身份):直接签发正式令牌按登录处理。
|
||||
// (与 /auth/wechat/miniapp/login 同一套识别与签发逻辑)
|
||||
if c.tryUserLogin(mobile, session) {
|
||||
return
|
||||
}
|
||||
|
||||
guest, err := gueststore.Upsert(mobile, openID, unionID)
|
||||
if err != nil {
|
||||
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(携带正式令牌)
|
||||
// 按当前身份手机号找回游客记录,把 yz_guest_data 各模块数据并入正式账号。
|
||||
func (c *GuestController) Merge() {
|
||||
@@ -161,24 +419,20 @@ func (c *GuestController) Merge() {
|
||||
c.gReply(500, "查询身份失败", nil)
|
||||
return
|
||||
}
|
||||
if ai.Mobile == nil || strings.TrimSpace(*ai.Mobile) == "" {
|
||||
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
||||
return
|
||||
// 回查手机号用于定位游客记录:identity.mobile 为空时回退租户绑定/业务用户表
|
||||
var mobile string
|
||||
if ai.Mobile != nil {
|
||||
mobile = strings.TrimSpace(*ai.Mobile)
|
||||
}
|
||||
if mobile == "" {
|
||||
mobile = phoneOfIdentity(ai.ID)
|
||||
}
|
||||
|
||||
var guest models.GuestUser
|
||||
err = models.Orm.QueryTable(new(models.GuestUser)).
|
||||
Filter("phone", strings.TrimSpace(*ai.Mobile)).
|
||||
Filter("merged_uid", 0).
|
||||
One(&guest)
|
||||
if err == orm.ErrNoRows {
|
||||
guest, found := findGuestForIdentity(ai.ID, mobile)
|
||||
if !found {
|
||||
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.gReply(500, "查询游客记录失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
counts, merr := mergeGuestToUser(&guest, uint64(claims.UserID), claims.TenantId, claims.Username)
|
||||
if merr != nil {
|
||||
|
||||
+98
-95
@@ -56,16 +56,11 @@
|
||||
</view>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- 游客冷启动授权引导:手机号授权后签发游客令牌,免登录模块数据才能保存 -->
|
||||
<!-- 游客无感授权:无弹窗,全屏透明按钮——首次点按即拉起微信原生手机号确认层;
|
||||
后端按手机号自动判定:绑定过租户→直接登录,否则→游客令牌 -->
|
||||
<view v-if="guestAuthVisible" class="guest-auth-mask">
|
||||
<view class="guest-auth-card">
|
||||
<text class="guest-auth-title">开启免登录保存</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>
|
||||
<button class="guest-auth-full" open-type="getPhoneNumber" :disabled="guestAuthLoading" @getphonenumber="onGuestPhone" />
|
||||
<text class="guest-auth-later" @tap="skipGuestAuth">暂不,先浏览</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
@@ -77,13 +72,13 @@ import { mpHeadPad } from '@/utils/mpHead.js'
|
||||
|
||||
const headPad = mpHeadPad()
|
||||
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 { fetchHome, fetchPublicModules, fetchPublicMenus } from '@/api/workspace.js'
|
||||
import { openMenu } from '@/utils/nav.js'
|
||||
import { getSubsystemByModule, goSubsystem } from '@/config/subsystems.js'
|
||||
import { getGuestToken, setGuestToken } from '@/utils/guest.js'
|
||||
import { guestMiniLogin } from '@/api/guest.js'
|
||||
import { getGuestToken, setGuestToken, clearGuestToken } from '@/utils/guest.js'
|
||||
import { guestMiniLogin, guestMerge } from '@/api/guest.js'
|
||||
|
||||
/* ==================== 游客手机号授权(免登录保存) ==================== */
|
||||
|
||||
@@ -137,6 +132,12 @@ async function onGuestPhone(e) {
|
||||
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('未返回游客令牌')
|
||||
setGuestToken(res.token)
|
||||
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() {
|
||||
guestAuthDismissed.value = true
|
||||
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 */
|
||||
function goSubsystemByModule(code) {
|
||||
const sub = getSubsystemByModule(code)
|
||||
@@ -273,7 +337,6 @@ async function load(force = false) {
|
||||
const data = await fetchHome(force)
|
||||
user.value = data.user || {}
|
||||
tenants.value = getTenants()
|
||||
currentTid.value = getTid()
|
||||
modules.value = Array.isArray(data.modules) ? data.modules : []
|
||||
menus.value = Array.isArray(data.menus) ? data.menus : []
|
||||
loadError.value = ''
|
||||
@@ -356,43 +419,6 @@ function goLogin(name = '') {
|
||||
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(() => {
|
||||
@@ -627,7 +653,7 @@ onShow(() => {
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
/* ============ 游客授权引导弹窗 ============ */
|
||||
/* ============ 游客无感授权层(无弹窗,首次点按即拉起微信原生确认) ============ */
|
||||
.guest-auth-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
@@ -635,58 +661,35 @@ onShow(() => {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
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%;
|
||||
max-width: 560rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 28rpx;
|
||||
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;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
opacity: 0;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.guest-auth-skip {
|
||||
font-size: 22rpx;
|
||||
.guest-auth-later {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 64rpx;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #94a3b8;
|
||||
padding: 8rpx 16rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user