优化uniapp
This commit is contained in:
1 parent
4f1acaa451
commit
8fc52c574f
49 files changed
+1952
-197
No files matched your search
+454
-107
@@ -4,18 +4,18 @@
|
||||
<view class="head" :style="headPad">
|
||||
<view class="head-row">
|
||||
<view>
|
||||
<text class="head-eyebrow">YUNZE WORKSPACE</text>
|
||||
<text class="head-eyebrow">WENXIN WORKSPACE</text>
|
||||
<text class="head-title">工作台</text>
|
||||
</view>
|
||||
<view v-if="!guest" class="account-chip"><FaIcon name="user" color="#3478f6" :size="15" /><text>{{ user.account || '我的账号' }}</text></view>
|
||||
<view v-else class="account-chip" @tap="goLogin"><FaIcon name="right-to-bracket" color="#3478f6" :size="15" /><text>登录</text></view>
|
||||
<view v-else class="account-chip"><FaIcon name="user" color="#3478f6" :size="15" /><text>游客</text></view>
|
||||
</view>
|
||||
<view v-if="!guest" class="tenant-switch" @tap="chooseTenant">
|
||||
<view class="tenant-mark"><FaIcon name="building" color="#ffffff" :size="19" /></view>
|
||||
<view class="tenant-copy"><text class="tenant-label">当前租户</text><text class="tenant-name">{{ user.tenant || '云泽平台' }}</text></view>
|
||||
<view class="tenant-copy"><text class="tenant-label">当前企业</text><text class="tenant-name">{{ user.tenant || '尚未加入企业' }}</text></view>
|
||||
<view class="tenant-action"><text>切换</text><FaIcon name="chevron-down" color="#3478f6" :size="12" /></view>
|
||||
</view>
|
||||
<text class="head-sub">{{ guest ? '免登录可逛部分功能,登录后使用完整工作台' : '选择一个业务系统,开始高效工作' }}</text>
|
||||
<!-- <text class="head-sub">{{ guest ? '免登录可逛部分功能,登录后使用完整工作台' : '选择一个业务系统,开始高效工作' }}</text> -->
|
||||
</view>
|
||||
|
||||
<view class="body">
|
||||
@@ -36,8 +36,28 @@
|
||||
</view>
|
||||
|
||||
<view v-else class="grid">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- 未授权手机号:卡片本身就是微信授权按钮,点一下直接拉起手机号授权 -->
|
||||
<button
|
||||
v-for="card in cards"
|
||||
v-show="guest"
|
||||
:key="'phone-' + (card.code || card.id)"
|
||||
class="cell cell-button"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="onCardPhone($event, card)"
|
||||
>
|
||||
<view class="portal-card" :class="{ locked: card.locked }">
|
||||
<view class="tint-icon portal-icon" :class="card.tint">
|
||||
<FaIcon :name="card.icon" color="#ffffff" :size="26" />
|
||||
</view>
|
||||
<text class="portal-name">{{ card.name }}</text>
|
||||
</view>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- 已授权(注册即登录):普通卡片,按模块是否需要企业分流 -->
|
||||
<view
|
||||
v-for="card in cards"
|
||||
v-show="!guest || !isMp"
|
||||
:key="card.code || card.id"
|
||||
class="cell"
|
||||
@tap="enter(card)"
|
||||
@@ -47,7 +67,6 @@
|
||||
<FaIcon :name="card.icon" color="#ffffff" :size="26" />
|
||||
</view>
|
||||
<text class="portal-name">{{ card.name }}</text>
|
||||
<text v-if="guest" class="portal-tag" :class="card.needLogin ? 'need' : 'free'">{{ card.needLogin ? '需登录' : '免登录' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -58,11 +77,58 @@
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- 游客无感授权:无弹窗,全屏透明按钮——首次点按即拉起微信原生手机号确认层;
|
||||
后端按手机号自动判定:绑定过租户→直接登录,否则→游客令牌 -->
|
||||
<view v-if="guestAuthVisible" class="guest-auth-mask">
|
||||
<!-- <view v-if="guestAuthVisible" class="guest-auth-mask">
|
||||
<button class="guest-auth-full" open-type="getPhoneNumber" :disabled="guestAuthLoading" @getphonenumber="onGuestPhone" />
|
||||
<text class="guest-auth-later" @tap="skipGuestAuth">暂不,先浏览</text>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 企业选择:自绘底部抽屉。原生 actionSheet 只有一行居中文本,长企业名会挤在一起,
|
||||
也无法标注「当前企业」;这里给每项独立卡片 + 两行标题 + 状态标识 -->
|
||||
<view v-if="tenantPickerVisible" class="tenant-picker-mask" @tap="closeTenantPicker">
|
||||
<view class="tenant-picker-sheet" @tap.stop>
|
||||
<view class="picker-grabber" />
|
||||
<view class="picker-head">
|
||||
<view class="picker-head-icon"><FaIcon name="building" color="#ffffff" :size="18" /></view>
|
||||
<view class="picker-head-copy">
|
||||
<text class="picker-title">{{ tenantPickerMode === 'login' ? '选择要进入的企业' : '切换企业' }}</text>
|
||||
<text class="picker-sub">{{ tenantPickerMode === 'login' ? '该账号已加入多家企业,请选择工作空间' : '选择一家企业,工作台将随之刷新' }}</text>
|
||||
</view>
|
||||
<view v-if="!tenantPickerRequired" class="picker-close" @tap="closeTenantPicker">
|
||||
<FaIcon name="xmark" color="#94a3b8" :size="14" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="picker-list" scroll-y :show-scrollbar="false">
|
||||
<view
|
||||
v-for="(item, index) in tenantPickerList"
|
||||
:key="item.tid"
|
||||
class="tenant-option"
|
||||
:class="{ current: isCurrentTenant(item), disabled: isPickingAny() }"
|
||||
@tap="pickTenant(item)"
|
||||
>
|
||||
<view class="tenant-option-badge" :class="'badge-' + (index % 4)">
|
||||
<FaIcon name="building" color="#ffffff" :size="18" />
|
||||
</view>
|
||||
<view class="tenant-option-copy">
|
||||
<text class="tenant-option-name">{{ item.tenant_name || '企业' + item.tid }}</text>
|
||||
<text class="tenant-option-meta">{{ item.name ? item.name + ' · ' : '' }}企业 ID {{ item.tid }}</text>
|
||||
</view>
|
||||
<text v-if="isCurrentTenant(item)" class="tenant-current-tag">当前</text>
|
||||
<text v-else-if="isPickingTenant(item)" class="tenant-option-loading">进入中…</text>
|
||||
<FaIcon v-else name="chevron-right" color="#cbd5e1" :size="14" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="picker-foot">
|
||||
<view v-if="tenantPickerRequired" class="picker-hint">
|
||||
<FaIcon name="circle-info" color="#94a3b8" :size="12" />
|
||||
<text>选择企业后才能继续使用工作台</text>
|
||||
</view>
|
||||
<view v-else class="picker-cancel" @tap="closeTenantPicker"><text>取消</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -73,8 +139,10 @@ import { mpHeadPad } from '@/utils/mpHead.js'
|
||||
const headPad = mpHeadPad()
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
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 { switchTenant, loginByMiniapp } from '@/api/auth.js'
|
||||
import { fetchHome, fetchPublicModules, fetchPublicMenus, clearHomeCache } from '@/api/workspace.js'
|
||||
import { onboardingPhone, getOnboardingStatus } from '@/api/onboarding.js'
|
||||
import { setPendingCard, takePendingCard } from '@/utils/onboarding.js'
|
||||
import { openMenu } from '@/utils/nav.js'
|
||||
import { getSubsystemByModule, goSubsystem } from '@/config/subsystems.js'
|
||||
import { getGuestToken, setGuestToken, clearGuestToken } from '@/utils/guest.js'
|
||||
@@ -86,6 +154,12 @@ const guestAuthVisible = ref(false)
|
||||
const guestAuthLoading = ref(false)
|
||||
// 本次停留内跳过:关闭后不在 nav 反复弹窗,下次冷启动再引导
|
||||
const guestAuthDismissed = ref(false)
|
||||
const phoneBusy = ref(false)
|
||||
// 非微信端没有 getPhoneNumber 能力:卡片退化为普通点击并给出提示
|
||||
let isMp = false
|
||||
// #ifdef MP-WEIXIN
|
||||
isMp = true
|
||||
// #endif
|
||||
|
||||
/** 仅微信小程序可拿手机号动态凭证 */
|
||||
function canRequestGuestPhone() {
|
||||
@@ -170,36 +244,83 @@ async function completeUserLogin(res) {
|
||||
uni.showToast({ title: '已登录,欢迎回来', icon: 'none' })
|
||||
}
|
||||
|
||||
/** 待选状态下必须选一个企业才能继续,关闭选择器后再次拉起 */
|
||||
/* ==================== 企业选择抽屉(替代 uni.showActionSheet) ==================== */
|
||||
// 原生 actionSheet 只有一行居中文本:长企业名挤成一片、无法标注「当前企业」。
|
||||
// 登录后的首次选择必须选一家(不可关闭),主动切换可取消,两者共用这一套抽屉。
|
||||
const tenantPickerVisible = ref(false)
|
||||
const tenantPickerRequired = ref(false)
|
||||
const tenantPickerMode = ref('login') // login=登录后的首次选择 / switch=主动切换
|
||||
const tenantPickerList = ref([])
|
||||
const tenantPickingTid = ref('')
|
||||
|
||||
function openTenantPicker(list, mode, required) {
|
||||
tenantPickerList.value = Array.isArray(list) ? list : []
|
||||
tenantPickerMode.value = mode
|
||||
tenantPickerRequired.value = required
|
||||
tenantPickingTid.value = ''
|
||||
tenantPickerVisible.value = true
|
||||
}
|
||||
|
||||
function closeTenantPicker() {
|
||||
if (tenantPickingTid.value) return
|
||||
if (tenantPickerRequired.value) {
|
||||
uni.showToast({ title: '请选择一家企业以继续', icon: 'none' })
|
||||
return
|
||||
}
|
||||
tenantPickerVisible.value = false
|
||||
}
|
||||
|
||||
function isCurrentTenant(item) {
|
||||
return tenantPickerMode.value === 'switch' && item && String(item.tid) === String(getTid())
|
||||
}
|
||||
|
||||
function isPickingAny() {
|
||||
return tenantPickingTid.value !== ''
|
||||
}
|
||||
|
||||
function isPickingTenant(item) {
|
||||
return item && tenantPickingTid.value === String(item.tid)
|
||||
}
|
||||
|
||||
/** 待选状态下必须选一家企业才能继续 */
|
||||
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)
|
||||
openTenantPicker(list, 'login', true)
|
||||
}
|
||||
|
||||
async function pickTenant(item) {
|
||||
if (!item || isPickingAny() || isCurrentTenant(item)) return
|
||||
const isSwitch = tenantPickerMode.value === 'switch'
|
||||
tenantPickingTid.value = String(item.tid)
|
||||
switchingTenant.value = true
|
||||
uni.showLoading({ title: isSwitch ? '正在切换...' : '登录中...', mask: true })
|
||||
try {
|
||||
const res = await switchTenant({ tid: item.tid })
|
||||
if (!res || !res.tokens) throw new Error((res && res.msg) || (isSwitch ? '切换企业失败' : '进入企业失败'))
|
||||
setAuthTokens(res.tokens)
|
||||
setTenants(tenantPickerList.value)
|
||||
setTid(item.tid)
|
||||
setTenantName(item.tenant_name || '')
|
||||
if (isSwitch) {
|
||||
const profile = getUser() || {}
|
||||
const nextUser = { ...profile, tid: item.tid, tenant_name: item.tenant_name || '' }
|
||||
setUser(nextUser)
|
||||
user.value = { ...user.value, tenant: item.tenant_name || '', account: nextUser.account || user.value.account }
|
||||
}
|
||||
})
|
||||
// 成功才关闭;失败保留抽屉让用户直接重试
|
||||
tenantPickerRequired.value = false
|
||||
tenantPickerVisible.value = false
|
||||
// 先游客后转正:企业落定后把游客数据并入正式账号
|
||||
await mergeGuestIfAny()
|
||||
await load(true)
|
||||
uni.showToast({ title: isSwitch ? '已切换企业' : '已登录,欢迎回来', icon: isSwitch ? 'success' : 'none' })
|
||||
} catch (e) {
|
||||
console.error('[nav] switch tenant failed:', e)
|
||||
uni.showToast({ title: (e && e.message) || '进入失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
tenantPickingTid.value = ''
|
||||
switchingTenant.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function skipGuestAuth() {
|
||||
@@ -262,17 +383,17 @@ const cards = computed(() => {
|
||||
if (guest.value) {
|
||||
return publicModules.value.map((mod, index) => {
|
||||
const code = String(mod.code || '').toLowerCase()
|
||||
const needLogin = !guestMenuModuleSet.value.has(code)
|
||||
const needTenant = mod.require_login !== 0
|
||||
return {
|
||||
id: mod.id,
|
||||
code: mod.code,
|
||||
path: mod.path || '',
|
||||
name: mod.name || '未命名系统',
|
||||
desc: mod.description || (needLogin ? '需登录使用' : '免登录'),
|
||||
desc: mod.description || (needTenant ? '加入企业后可用' : '可直接使用'),
|
||||
icon: MODULE_ICONS[code] || 'cube',
|
||||
tint: 'tint-' + (index % 8),
|
||||
locked: false,
|
||||
needLogin,
|
||||
needTenant,
|
||||
items: [],
|
||||
total: 0
|
||||
}
|
||||
@@ -303,6 +424,7 @@ const cards = computed(() => {
|
||||
icon: MODULE_ICONS[code] || 'cube',
|
||||
tint: locked ? 'tint-off' : 'tint-' + (index % 8),
|
||||
locked,
|
||||
needTenant: mod.require_login !== 0,
|
||||
items,
|
||||
total: items.length
|
||||
}
|
||||
@@ -329,8 +451,7 @@ async function load(force = false) {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
// 进小程序即引导授权:游客无令牌时弹手机号授权窗
|
||||
maybeAskGuestAuth()
|
||||
// 新流程:不再自动弹授权,点任意模块卡片时才拉起微信手机号授权
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -352,56 +473,123 @@ async function load(force = false) {
|
||||
|
||||
function chooseTenant() {
|
||||
const list = tenants.value
|
||||
if (list.length <= 1) { uni.showToast({ title: list.length ? '当前账号仅绑定一个租户' : '暂无可切换的租户', icon: 'none' })
|
||||
if (list.length <= 1) { uni.showToast({ title: list.length ? '当前账号仅绑定一个企业' : '暂无可切换的企业', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (switchingTenant.value) return
|
||||
uni.showActionSheet({
|
||||
itemList: list.map((tenant) => tenant.tenant_name || `企业${tenant.tid}`),
|
||||
success: async ({ tapIndex }) => {
|
||||
const tenant = list[tapIndex]
|
||||
if (!tenant || String(tenant.tid) === String(getTid())) return
|
||||
switchingTenant.value = true
|
||||
uni.showLoading({ title: '正在切换...', mask: true })
|
||||
try {
|
||||
const res = await switchTenant({ tid: tenant.tid })
|
||||
if (!res || !res.tokens) throw new Error((res && res.msg) || '切换租户失败')
|
||||
setAuthTokens(res.tokens)
|
||||
setTid(tenant.tid)
|
||||
setTenantName(tenant.tenant_name || '')
|
||||
const profile = getUser() || {}
|
||||
const nextUser = { ...profile, tid: tenant.tid, tenant_name: tenant.tenant_name || '' }
|
||||
setUser(nextUser)
|
||||
user.value = { ...user.value, tenant: tenant.tenant_name || '', account: nextUser.account || user.value.account }
|
||||
await load(true)
|
||||
uni.showToast({ title: '已切换租户', icon: 'success' })
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.message) || '切换失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
switchingTenant.value = false
|
||||
}
|
||||
if (switchingTenant.value || tenantPickerVisible.value) return
|
||||
openTenantPicker(list, 'switch', false)
|
||||
}
|
||||
|
||||
/* ==================== 手机号授权 / 注册 / 企业分流 ==================== */
|
||||
|
||||
/** 点卡片(未授权时)直接触发微信手机号授权;有账号静默登录,无账号去设密码注册 */
|
||||
async function onCardPhone(e, card) {
|
||||
const detail = (e && e.detail) || {}
|
||||
if (String(detail.errMsg || '').indexOf('ok') === -1 || !detail.code) {
|
||||
uni.showToast({ title: '需要授权手机号才能使用', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (phoneBusy.value) return
|
||||
phoneBusy.value = true
|
||||
uni.showLoading({ title: '验证中...', mask: true })
|
||||
try {
|
||||
const code = await navUniLoginCode()
|
||||
let appid = ''
|
||||
try {
|
||||
appid = (uni.getAccountInfoSync().miniProgram || {}).appId || ''
|
||||
} catch (err) {
|
||||
appid = ''
|
||||
}
|
||||
})
|
||||
setPendingCard(card)
|
||||
const res = await onboardingPhone({ code, phoneCode: detail.code, appid })
|
||||
if (res && res.need_register) {
|
||||
uni.hideLoading()
|
||||
uni.navigateTo({
|
||||
url: '/pages/onboarding/register?token=' + encodeURIComponent(res.register_token || '') +
|
||||
'&mobile_mask=' + encodeURIComponent(res.mobile_mask || '')
|
||||
})
|
||||
return
|
||||
}
|
||||
// 已有账号:复用小程序一键登录(后端按手机号认人并绑定 openid)
|
||||
const ready = await silentLogin(code, detail.code)
|
||||
if (!ready) return
|
||||
await load(true)
|
||||
await enter(card)
|
||||
takePendingCard()
|
||||
} catch (err) {
|
||||
console.error('[nav] onboarding phone failed:', err)
|
||||
uni.showToast({ title: (err && err.message) || '授权失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
phoneBusy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 已有账号静默登录;无企业时后端签发 tid=0 令牌,多企业时拉起企业选择抽屉 */
|
||||
async function silentLogin(code, phoneCode) {
|
||||
const res = await loginByMiniapp({ code, phoneCode })
|
||||
if (!res || !res.tokens || !res.tokens.access_token) {
|
||||
throw new Error((res && res.msg) || '登录失败,请重试')
|
||||
}
|
||||
setAuthTokens(res.tokens)
|
||||
clearHomeCache()
|
||||
const list = Array.isArray(res.tenants) ? res.tenants : []
|
||||
setTenants(list)
|
||||
if (res.need_choose_tenant && list.length > 1) {
|
||||
askChooseTenant(list)
|
||||
return false
|
||||
}
|
||||
const t0 = list[0] || {}
|
||||
setTid(t0.tid || 0)
|
||||
setTenantName(t0.tenant_name || '')
|
||||
await mergeGuestIfAny()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击子系统卡片 → 进入该子系统独立页面组(带自己的 tabbar)。
|
||||
* 未在 config/subsystems.js 注册的模块,降级为直接打开 Web 端该模块首页。
|
||||
* 游客:命中「必须登录」模块时不直接进,先引导登录(登录后回 nav)。
|
||||
* 需企业模块:查审核状态,未通过则引导创建 / 挂靠企业;通过后刷新企业令牌再进入。
|
||||
*/
|
||||
function enter(card) {
|
||||
async function enter(card) {
|
||||
if (card.locked) {
|
||||
openMenu({ locked: true, title: card.name })
|
||||
return
|
||||
}
|
||||
if (guest.value && card.needLogin) {
|
||||
goLogin(card.name)
|
||||
if (guest.value) {
|
||||
uni.showToast({ title: '请先授权手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (card.needTenant) {
|
||||
let st = null
|
||||
try {
|
||||
st = await getOnboardingStatus()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '状态获取失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (st && st.has_tenant) {
|
||||
// 审核刚通过时本地令牌可能仍是 tid=0,重新静默登录拿企业令牌
|
||||
if (!getTid()) {
|
||||
try {
|
||||
const code = await navUniLoginCode()
|
||||
const ready = await silentLogin(code, '')
|
||||
if (!ready) return
|
||||
await load(true)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '登录态刷新失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 不暂存卡片:避免企业申请页返回工作台时 onShow 再次自动跳回,形成循环
|
||||
uni.navigateTo({ url: '/pages/onboarding/company' })
|
||||
return
|
||||
}
|
||||
}
|
||||
if (goSubsystemByModule(card.code)) return
|
||||
if (card.items.length) {
|
||||
openMenu(card.items[0], card.name)
|
||||
const items = card.items || []
|
||||
if (items.length) {
|
||||
openMenu(items[0], card.name)
|
||||
return
|
||||
}
|
||||
if (card.path) {
|
||||
@@ -411,24 +599,20 @@ function enter(card) {
|
||||
uni.showToast({ title: card.name + '暂未开放', icon: 'none' })
|
||||
}
|
||||
|
||||
/** 引导登录:登录页登录成功后会回 nav(见 login.vue finishLogin) */
|
||||
function goLogin(name = '') {
|
||||
if (name) {
|
||||
uni.showToast({ title: '「' + name + '」需登录后使用', icon: 'none' })
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
|
||||
/* ==================== 生命周期 ==================== */
|
||||
|
||||
onMounted(() => {
|
||||
// 默认落地页:未登录不再强制回登录页,而是以游客态渲染公开模块宫格
|
||||
load()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
// 登录态可能在登录页/其他入口发生变化,每次展示重新判定并加载
|
||||
// 注册 / 企业申请页回来后登录态可能变化,重新判定并加载
|
||||
if (!loading.value) load()
|
||||
// 注册完成后自动继续进入用户刚才点击的模块
|
||||
const pending = takePendingCard()
|
||||
if (pending && isLoggedIn()) {
|
||||
setTimeout(() => enter(pending), 600)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -627,26 +811,20 @@ onShow(() => {
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
/* 游客态:卡片左上角标出免登录 / 需登录 */
|
||||
.portal-card {
|
||||
position: relative;
|
||||
}
|
||||
.portal-tag {
|
||||
position: absolute;
|
||||
top: 14rpx;
|
||||
right: 14rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 18rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.portal-tag.free {
|
||||
color: #16a34a;
|
||||
background: rgba(22, 163, 74, 0.12);
|
||||
}
|
||||
.portal-tag.need {
|
||||
color: #94a3b8;
|
||||
background: rgba(148, 163, 184, 0.16);
|
||||
/* 未授权时卡片是微信授权按钮:抹平 button 默认样式,保持与 view 卡片一致 */
|
||||
.cell-button {
|
||||
display: block;
|
||||
margin: 0 0 20rpx 0;
|
||||
padding: 0 20rpx 0 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-space {
|
||||
@@ -692,4 +870,173 @@ onShow(() => {
|
||||
color: #94a3b8;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
/* ============ 企业选择抽屉(自绘,替代原生 actionSheet) ============ */
|
||||
.tenant-picker-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
background: rgba(15, 23, 42, .48);
|
||||
@include mask-responsive;
|
||||
}
|
||||
|
||||
.tenant-picker-sheet {
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
box-shadow: 0 -12rpx 40rpx rgba(15, 23, 42, .12);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
animation: tenant-sheet-in .22s ease-out;
|
||||
@include sheet-responsive;
|
||||
}
|
||||
|
||||
@keyframes tenant-sheet-in {
|
||||
from { transform: translateY(60rpx); opacity: .6; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.picker-grabber {
|
||||
width: 72rpx;
|
||||
height: 8rpx;
|
||||
margin: 16rpx auto 4rpx;
|
||||
border-radius: $radius-full;
|
||||
background: #dbe3ef;
|
||||
}
|
||||
|
||||
.picker-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx $page-padding-x 24rpx;
|
||||
border-bottom: 1rpx solid $color-divider;
|
||||
}
|
||||
|
||||
.picker-head-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #60a5fa, #2563eb);
|
||||
box-shadow: 0 8rpx 18rpx rgba(37, 99, 235, .25);
|
||||
}
|
||||
|
||||
.picker-head-copy { flex: 1; min-width: 0; }
|
||||
.picker-title { display: block; font-size: 32rpx; font-weight: 700; letter-spacing: .5rpx; color: $color-text; }
|
||||
.picker-sub { display: block; margin-top: 8rpx; font-size: 22rpx; line-height: 1.5; color: $color-text-muted; }
|
||||
.picker-close {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius-full;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.picker-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: 52vh;
|
||||
padding: 24rpx $page-padding-x 4rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tenant-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 24rpx 22rpx;
|
||||
border: 1rpx solid #e6edf8;
|
||||
border-radius: 20rpx;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4rpx 16rpx rgba(38, 86, 150, .05);
|
||||
transition: transform .15s ease, border-color .15s ease, background .15s ease;
|
||||
|
||||
&:active { transform: scale(.985); background: #f7faff; border-color: #b9d2ff; }
|
||||
&.current { border-color: #bfd4ff; background: #f5f9ff; }
|
||||
&.disabled { opacity: .55; }
|
||||
}
|
||||
|
||||
.tenant-option-badge {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #60a5fa, #2563eb);
|
||||
|
||||
&.badge-1 { background: linear-gradient(135deg, #34d399, #0ea5a4); }
|
||||
&.badge-2 { background: linear-gradient(135deg, #a78bfa, #6366f1); }
|
||||
&.badge-3 { background: linear-gradient(135deg, #fbbf6e, #f97316); }
|
||||
}
|
||||
|
||||
.tenant-option-copy { flex: 1; min-width: 0; }
|
||||
.tenant-option-name {
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
color: $color-text;
|
||||
}
|
||||
.tenant-option-meta {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-muted;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tenant-current-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: $radius-full;
|
||||
background: $color-primary-bg;
|
||||
font-size: 20rpx;
|
||||
color: $color-primary;
|
||||
}
|
||||
.tenant-option-loading { flex-shrink: 0; font-size: 22rpx; color: $color-primary; }
|
||||
|
||||
.picker-foot { padding: 8rpx $page-padding-x 20rpx; }
|
||||
.picker-cancel {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: $radius-full;
|
||||
background: #f1f5f9;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-secondary;
|
||||
|
||||
&:active { background: #e6ecf5; }
|
||||
}
|
||||
.picker-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
height: 72rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user