更新
This commit is contained in:
1 parent
fb04d66e61
commit
b6b528490c
13 files changed
+518
-168
No files matched your search
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="header" :style="headPad">
|
||||
<view class="header-back" @tap="goBack">
|
||||
<FaIcon name="chevron-left" color="#ffffff" :size="14" />
|
||||
<text>返回</text>
|
||||
</view>
|
||||
<text class="header-title">企业绑定</text>
|
||||
<text class="header-sub">一个账号可绑定多家企业,绑定申请由对应企业的管理员审核</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="body" scroll-y>
|
||||
<!-- 已绑定企业 -->
|
||||
<view class="card">
|
||||
<view class="card-head">
|
||||
<text class="card-title">已绑定企业</text>
|
||||
<text class="card-count">{{ tenants.length }} 家</text>
|
||||
</view>
|
||||
<view v-if="!tenants.length" class="empty">暂未绑定企业,可在下方提交绑定申请</view>
|
||||
<view v-for="t in tenants" :key="t.tid" class="tenant-row">
|
||||
<view class="tenant-badge"><FaIcon name="building" color="#ffffff" :size="17" /></view>
|
||||
<view class="tenant-copy">
|
||||
<text class="tenant-name">{{ t.tenant_name || '企业 ' + t.tid }}</text>
|
||||
<text class="tenant-meta">企业 ID {{ t.tid }}</text>
|
||||
</view>
|
||||
<text v-if="String(t.tid) === String(getTid())" class="current-tag">当前</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定其他企业 -->
|
||||
<view class="card">
|
||||
<text class="card-title">绑定其他企业</text>
|
||||
<view class="field">
|
||||
<text class="label">企业全称 <text class="required">*</text></text>
|
||||
<view class="search-row">
|
||||
<input v-model="keyword" class="input search-input" placeholder="输入企业全称后搜索" />
|
||||
<view class="search-btn" @tap="searchTenants">搜索</view>
|
||||
</view>
|
||||
<view v-if="tenantList.length" class="result-list">
|
||||
<view v-for="t in tenantList" :key="t.tid" class="result-row" @tap="chooseTenant(t)">
|
||||
<text>{{ t.tenant_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="searched" class="no-result">未找到企业,请核对全称</view>
|
||||
<text v-if="selectedTenant" class="selected">已选:{{ selectedTenant.tenant_name }}</text>
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="label">部门 <text class="required">*</text></text>
|
||||
<picker :range="deptNames" :value="deptIndex" :disabled="!selectedTenant || !deptNames.length" @change="onDeptChange">
|
||||
<view class="picker-value">{{ deptName() }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">职位 <text class="required">*</text></text>
|
||||
<picker :range="positionNames" :value="positionIndex" :disabled="!positionNames.length" @change="onPositionChange">
|
||||
<view class="picker-value">{{ positionName() }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">姓名 <text class="required">*</text></text>
|
||||
<input v-model="applicantName" class="input" placeholder="真实姓名" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">手机号</text>
|
||||
<view class="readonly">
|
||||
<text>{{ mobileMask || '微信已验证' }}</text>
|
||||
<text class="readonly-tag">不可修改</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-if="formError" class="form-error">{{ formError }}</text>
|
||||
<button class="submit" :disabled="submitting" @tap="submitBind">
|
||||
{{ submitting ? '提交中…' : '提交绑定申请' }}
|
||||
</button>
|
||||
<text class="form-tip">提交后由该企业管理员审核,通过后可在工作台切换企业</text>
|
||||
</view>
|
||||
|
||||
<!-- 申请记录 -->
|
||||
<view class="card">
|
||||
<text class="card-title">申请记录</text>
|
||||
<view v-if="!applies.length" class="empty">暂无申请记录</view>
|
||||
<view v-for="a in applies" :key="a.id" class="apply-row">
|
||||
<view class="apply-copy">
|
||||
<text class="apply-name">{{ a.tenant_name || '企业申请' }}</text>
|
||||
<text class="apply-meta">{{ a.type === 2 ? '创建企业' : '绑定企业' }}<text v-if="a.department_name || a.position_name"> · {{ a.department_name || '' }} {{ a.position_name || '' }}</text></text>
|
||||
<text v-if="a.status === 2" class="apply-reason">驳回原因:{{ a.reject_reason || '未填写' }}</text>
|
||||
</view>
|
||||
<text class="apply-status" :class="statusClass(a.status)">{{ statusText(a.status) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom-space" />
|
||||
</scroll-view>
|
||||
|
||||
<AppTabbar current="bind" :tabs="profileTabs" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 「我的 → 企业绑定」:展示已绑定的多租户,并向其他企业提交绑定申请。
|
||||
* 复用挂靠申请链路(企业搜索 / 部门 / 职位 / 微信手机号只读 / 租户管理员审核)。
|
||||
*/
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import {
|
||||
getOnboardingStatus, searchOnboardingTenants, getOnboardingDepartments,
|
||||
getOnboardingPositions, applyJoin
|
||||
} from '@/api/onboarding.js'
|
||||
import { getTid, getUser, setTenants } from '@/utils/auth.js'
|
||||
import { mpHeadPad } from '@/utils/mpHead.js'
|
||||
import AppTabbar from '@/components/MainTabbar.vue'
|
||||
|
||||
const headPad = mpHeadPad()
|
||||
const profileTabs = [
|
||||
{ name: 'profile', text: '我的', icon: 'user' },
|
||||
{ name: 'platform', text: '回平台', icon: 'arrow-left' }
|
||||
]
|
||||
|
||||
const tenants = ref([])
|
||||
const applies = ref([])
|
||||
const mobileMask = ref('')
|
||||
const submitting = ref(false)
|
||||
const formError = ref('')
|
||||
|
||||
const keyword = ref('')
|
||||
const searched = ref(false)
|
||||
const tenantList = ref([])
|
||||
const selectedTenant = ref(null)
|
||||
const deptList = ref([])
|
||||
const positionList = ref([])
|
||||
const deptIndex = ref(0)
|
||||
const positionIndex = ref(0)
|
||||
const applicantName = ref((getUser()?.name || getUser()?.nickname || ''))
|
||||
|
||||
const deptNames = computed(() => deptList.value.map((d) => d.name))
|
||||
const positionNames = computed(() => positionList.value.map((p) => p.name))
|
||||
|
||||
onShow(() => { loadStatus() })
|
||||
|
||||
async function loadStatus() {
|
||||
try {
|
||||
const res = await getOnboardingStatus()
|
||||
const list = Array.isArray(res?.tenants) ? res.tenants : []
|
||||
tenants.value = list
|
||||
applies.value = Array.isArray(res?.applies) ? res.applies : []
|
||||
mobileMask.value = res?.mobile_mask || ''
|
||||
// 同步本地企业列表,工作台右上角「切换」立即可用
|
||||
setTenants(list.map((t) => ({ tid: t.tid, tenant_name: t.tenant_name })))
|
||||
} catch (e) {
|
||||
console.warn('[bindTenant] status failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack({ fail: () => uni.redirectTo({ url: '/pages/profile/profile' }) })
|
||||
}
|
||||
|
||||
async function searchTenants() {
|
||||
const kw = keyword.value.trim()
|
||||
if (!kw) {
|
||||
formError.value = '请输入企业全称'
|
||||
return
|
||||
}
|
||||
formError.value = ''
|
||||
try {
|
||||
const res = await searchOnboardingTenants(kw)
|
||||
tenantList.value = Array.isArray(res?.list) ? res.list : []
|
||||
searched.value = true
|
||||
selectedTenant.value = null
|
||||
deptList.value = []
|
||||
positionList.value = []
|
||||
} catch (e) {
|
||||
formError.value = (e && e.message) || '企业搜索失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function chooseTenant(t) {
|
||||
selectedTenant.value = t
|
||||
keyword.value = t.tenant_name
|
||||
deptIndex.value = 0
|
||||
positionIndex.value = 0
|
||||
positionList.value = []
|
||||
formError.value = ''
|
||||
try {
|
||||
const res = await getOnboardingDepartments(t.tid)
|
||||
deptList.value = Array.isArray(res?.list) ? res.list : []
|
||||
if (deptList.value.length) await loadPositions()
|
||||
} catch (e) {
|
||||
formError.value = (e && e.message) || '部门列表加载失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPositions() {
|
||||
const dept = deptList.value[deptIndex.value]
|
||||
if (!selectedTenant.value || !dept) return
|
||||
try {
|
||||
const res = await getOnboardingPositions(selectedTenant.value.tid, dept.id)
|
||||
positionList.value = Array.isArray(res?.list) ? res.list : []
|
||||
positionIndex.value = 0
|
||||
} catch (e) {
|
||||
formError.value = (e && e.message) || '职位列表加载失败'
|
||||
}
|
||||
}
|
||||
|
||||
function onDeptChange(e) {
|
||||
deptIndex.value = Number(e.detail.value) || 0
|
||||
loadPositions()
|
||||
}
|
||||
|
||||
function onPositionChange(e) {
|
||||
positionIndex.value = Number(e.detail.value) || 0
|
||||
}
|
||||
|
||||
function deptName() {
|
||||
const d = deptList.value[deptIndex.value]
|
||||
return d ? d.name : (selectedTenant.value ? '请选择部门' : '请先搜索并选择企业')
|
||||
}
|
||||
|
||||
function positionName() {
|
||||
const p = positionList.value[positionIndex.value]
|
||||
return p ? p.name : (deptList.value.length ? '请选择职位' : '请先选择部门')
|
||||
}
|
||||
|
||||
function statusText(s) {
|
||||
if (s === 0) return '待审核'
|
||||
if (s === 1) return '已通过'
|
||||
return '已驳回'
|
||||
}
|
||||
|
||||
function statusClass(s) {
|
||||
if (s === 0) return 'pending'
|
||||
if (s === 1) return 'passed'
|
||||
return 'rejected'
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
keyword.value = ''
|
||||
searched.value = false
|
||||
tenantList.value = []
|
||||
selectedTenant.value = null
|
||||
deptList.value = []
|
||||
positionList.value = []
|
||||
deptIndex.value = 0
|
||||
positionIndex.value = 0
|
||||
}
|
||||
|
||||
async function submitBind() {
|
||||
if (submitting.value) return
|
||||
formError.value = ''
|
||||
if (!selectedTenant.value) {
|
||||
formError.value = '请先搜索并选择要绑定的企业'
|
||||
return
|
||||
}
|
||||
const dept = deptList.value[deptIndex.value]
|
||||
const pos = positionList.value[positionIndex.value]
|
||||
if (!dept || !pos || !applicantName.value.trim()) {
|
||||
formError.value = '请选择部门、职位并填写姓名'
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await applyJoin({
|
||||
tid: selectedTenant.value.tid,
|
||||
applicant_name: applicantName.value.trim(),
|
||||
department_id: dept.id,
|
||||
department_name: dept.name,
|
||||
position_id: pos.id,
|
||||
position_name: pos.name
|
||||
})
|
||||
uni.showToast({ title: '已提交,等待企业管理员审核', icon: 'none' })
|
||||
resetForm()
|
||||
await loadStatus()
|
||||
} catch (e) {
|
||||
formError.value = (e && e.message) || '提交失败,请重试'
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/src/styles/page-common.scss';
|
||||
|
||||
.page { @include page-shell; }
|
||||
|
||||
.header {
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 28rpx) $page-padding-x 36rpx;
|
||||
/* #ifdef MP-WEIXIN */
|
||||
padding-top: calc(env(safe-area-inset-top, 25px) + 88rpx);
|
||||
/* #endif */
|
||||
background: $hero-gradient;
|
||||
color: #ffffff;
|
||||
}
|
||||
.header-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: $radius-full;
|
||||
background: rgba(255, 255, 255, .18);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.header-title { display: block; margin-top: 28rpx; font-size: 40rpx; font-weight: 700; }
|
||||
.header-sub { display: block; margin-top: 10rpx; font-size: 23rpx; line-height: 1.6; color: rgba(255, 255, 255, .8); }
|
||||
|
||||
.body { flex: 1; min-height: 0; padding: 24rpx $page-padding-x 0; box-sizing: border-box; }
|
||||
.card { @include card; padding: 28rpx 26rpx 32rpx; margin-bottom: 24rpx; }
|
||||
.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20rpx; }
|
||||
.card-title { font-size: 30rpx; font-weight: 700; color: $color-text; }
|
||||
.card-count { font-size: 23rpx; color: $color-text-muted; }
|
||||
.empty { padding: 32rpx 0; text-align: center; font-size: 24rpx; color: $color-text-muted; }
|
||||
|
||||
.tenant-row { display: flex; align-items: center; gap: 18rpx; padding: 20rpx 0; border-bottom: 1rpx solid $color-divider; }
|
||||
.tenant-row:last-child { border-bottom: none; }
|
||||
.tenant-badge {
|
||||
width: 68rpx; height: 68rpx; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border-radius: 18rpx;
|
||||
background: linear-gradient(135deg, #60a5fa, #2563eb);
|
||||
}
|
||||
.tenant-copy { flex: 1; min-width: 0; }
|
||||
.tenant-name { display: block; font-size: 27rpx; font-weight: 600; color: $color-text; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.tenant-meta { display: block; margin-top: 6rpx; font-size: 21rpx; color: $color-text-muted; }
|
||||
.current-tag { flex-shrink: 0; padding: 6rpx 16rpx; border-radius: $radius-full; background: $color-primary-bg; font-size: 20rpx; color: $color-primary; }
|
||||
|
||||
.field { margin-top: 24rpx; }
|
||||
.label { display: block; margin-bottom: 12rpx; font-size: 25rpx; font-weight: 600; color: #334155; }
|
||||
.required { color: #ef4444; }
|
||||
.input {
|
||||
height: 84rpx; padding: 0 22rpx; box-sizing: border-box;
|
||||
border: 1rpx solid #dce7f7; border-radius: $radius-md;
|
||||
background: #fbfdff; font-size: 27rpx; color: $color-text;
|
||||
}
|
||||
.search-row { display: flex; gap: 14rpx; }
|
||||
.search-input { flex: 1; }
|
||||
.search-btn { width: 140rpx; height: 84rpx; display: flex; align-items: center; justify-content: center; border-radius: $radius-md; background: #2563eb; color: #fff; font-size: 26rpx; }
|
||||
.result-list { margin-top: 14rpx; border: 1rpx solid #e2e8f0; border-radius: $radius-md; overflow: hidden; }
|
||||
.result-row { padding: 20rpx 22rpx; font-size: 26rpx; color: #334155; border-bottom: 1rpx solid #eef2f7; }
|
||||
.result-row:last-child { border-bottom: none; }
|
||||
.no-result { margin-top: 14rpx; font-size: 23rpx; color: #94a3b8; }
|
||||
.selected { display: block; margin-top: 14rpx; font-size: 24rpx; color: #2563eb; }
|
||||
.picker-value { height: 84rpx; line-height: 84rpx; padding: 0 22rpx; border: 1rpx solid #dce7f7; border-radius: $radius-md; background: #fbfdff; font-size: 27rpx; color: $color-text; }
|
||||
.readonly { height: 84rpx; display: flex; align-items: center; justify-content: space-between; padding: 0 22rpx; border: 1rpx solid #e2e8f0; border-radius: $radius-md; background: #f1f5f9; font-size: 27rpx; color: #475569; }
|
||||
.readonly-tag { font-size: 21rpx; color: #94a3b8; }
|
||||
.form-error { display: block; margin: 20rpx 0 0; font-size: 24rpx; color: $color-danger; }
|
||||
.form-tip { display: block; margin-top: 16rpx; font-size: 22rpx; line-height: 1.6; color: $color-text-muted; }
|
||||
.submit {
|
||||
height: 90rpx; line-height: 90rpx; margin-top: 28rpx;
|
||||
border: none; border-radius: $radius-full;
|
||||
background: linear-gradient(135deg, #60a5fa, #2563eb);
|
||||
color: #fff; font-size: 29rpx; font-weight: 600;
|
||||
|
||||
&::after { border: none; }
|
||||
&[disabled] { opacity: .6; }
|
||||
}
|
||||
|
||||
.apply-row { display: flex; align-items: center; gap: 16rpx; padding: 20rpx 0; border-bottom: 1rpx solid $color-divider; }
|
||||
.apply-row:last-child { border-bottom: none; }
|
||||
.apply-copy { flex: 1; min-width: 0; }
|
||||
.apply-name { display: block; font-size: 27rpx; font-weight: 600; color: $color-text; }
|
||||
.apply-meta { display: block; margin-top: 6rpx; font-size: 22rpx; color: $color-text-muted; }
|
||||
.apply-reason { display: block; margin-top: 6rpx; font-size: 22rpx; color: #b42318; }
|
||||
.apply-status { flex-shrink: 0; padding: 6rpx 16rpx; border-radius: $radius-full; font-size: 20rpx; background: #f1f5f9; color: #64748b; }
|
||||
.apply-status.pending { background: #fff7e6; color: #b45309; }
|
||||
.apply-status.passed { background: #e8f8f0; color: #047857; }
|
||||
.apply-status.rejected { background: #fef2f2; color: #b91c1c; }
|
||||
|
||||
.bottom-space { height: 40rpx; }
|
||||
</style>
|
||||
Reference in new issue
Block a user