<template>
|
<view class="page">
|
<view class="custom-nav">
|
<view class="safe-space" :style="'height:' + navTop + 'px'"></view>
|
<view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'">
|
<text class="page-title">人才卡</text>
|
<view class="capsule-space" :style="'width:' + navWidth + 'px'"></view>
|
</view>
|
</view>
|
|
<scroll-view class="content" scroll-y="true" show-scrollbar="false">
|
<view v-if="isGuest" class="guest-content">
|
<view class="guest-intro"><text class="guest-intro-title">东区人才卡</text><text class="guest-intro-copy">面向区内不同层次人才推出的一二三类人才卡,提供消费优惠、便捷政务、安家补助、子女教育、创业支持等优质服务,增强人才归属感,提升人才引育用留质效,加快建设区域人才集聚高地。</text><view class="apply-button" hover-class="apply-button-active" @tap="openApplyNotice"><text>立即申请</text></view></view>
|
<view class="guest-card-item" v-for="item in guestCardTypes" :key="item.title">
|
<view class="guest-card-title"><view></view><text>{{item.title}}</text><view></view></view>
|
<view class="guest-card-preview"><image class="guest-card-image" :src="item.image" mode="aspectFit"></image></view>
|
<view class="condition-link" hover-class="condition-link-active" @tap="openApplyConditions"><text>点击查看申请条件</text></view>
|
</view>
|
<view class="guest-bottom"></view>
|
</view>
|
|
<view v-else-if="cardLoading" class="member-state"><view class="loading-spinner"></view><text>正在加载人才卡</text></view>
|
<view v-else-if="cardLoadFailed" class="member-state"><text>人才卡信息加载失败</text><text class="retry-text" @tap="loadTalentCard">重新加载</text></view>
|
|
<view v-else>
|
<view class="card-panel">
|
<view class="talent-card-face">
|
<image class="card-background" :src="cardBackground" mode="aspectFill"></image>
|
<text class="card-tier-title">{{cardLevel}}</text>
|
<view class="card-data-row name-row">
|
<view class="field-group"><text class="field-label">姓名</text><text class="field-value">{{cardName}}</text></view>
|
<view class="field-group gender-group"><text class="field-label">性别</text><text class="field-value">{{cardGender}}</text></view>
|
</view>
|
<view class="card-data-row number-row"><text class="field-label">编号</text><text class="field-value">{{cardNo}}</text></view>
|
<view class="card-data-row work-row"><text class="field-label">工作单位</text><text class="field-value">{{workUnit}}</text></view>
|
</view>
|
|
<view :class="['benefit-badge', canShowCode ? '' : 'is-disabled']">
|
<image src="/static/card-assets/code-benefit.svg" mode="aspectFit"></image>
|
<text>{{canShowCode ? '亮码享受优惠' : cardStatusText}}</text>
|
</view>
|
|
<view v-if="canShowCode" class="qr-frame" hover-class="qr-frame-active" @tap="refreshCode">
|
<image v-if="codeImageUrl != ''" class="dynamic-code-image" :src="codeImageUrl" mode="aspectFit" @error="handleCodeImageError"></image>
|
<view v-else class="code-state"><view v-if="codeLoading" class="loading-spinner"></view><text>{{codeMessage}}</text><text v-if="codeLoadFailed" class="retry-text" @tap.stop="refreshCode">重新加载</text></view>
|
</view>
|
<view v-else class="qr-frame qr-unavailable"><text class="dynamic-code-placeholder">{{cardStatusText}},暂不可亮码</text></view>
|
<text class="refresh-tip">{{canShowCode ? refreshTip : '请联系服务专员了解卡片状态'}}</text>
|
</view>
|
|
<view v-if="canShowCode" class="service-panel">
|
<view class="service-title-row"><view class="title-line"></view><text>{{cardLevel}}专属服务专员</text><view class="title-line"></view></view>
|
<text class="service-advisor">{{advisorName != '' ? advisorDepartment + ' ' + advisorName : '暂未配置服务专员'}}</text>
|
<view class="service-actions">
|
<view class="service-action" hover-class="service-action-active" @tap="onlineConsult">
|
<image src="/static/card-assets/consult.png"></image><text>在线咨询</text>
|
</view>
|
<view class="service-action" hover-class="service-action-active" @tap="callAdvisor">
|
<image src="/static/card-assets/phone.png"></image><text>拨打电话</text>
|
</view>
|
</view>
|
</view>
|
<view class="content-bottom"></view>
|
</view>
|
</scroll-view>
|
|
<view class="app-tab-bar">
|
<view class="app-tab" @tap="goHome"><view class="app-tab-icon"><image src="/static/home-assets/home-default.svg"></image></view><text>首页</text></view>
|
<view class="app-tab" @tap="goBenefits"><view class="app-tab-icon"><image src="/static/home-assets/benefits.svg"></image></view><text>人才权益</text></view>
|
<view class="app-tab card-tab"><view class="app-card-icon"><image src="/static/home-assets/card.png"></image><text>人才卡</text></view></view>
|
<view class="app-tab" @tap="goPolicy"><view class="app-tab-icon"><image src="/static/home-assets/policy.svg"></image></view><text>人才政策</text></view>
|
<view class="app-tab" @tap="goProfile"><view class="app-tab-icon"><image src="/static/home-assets/profile.svg"></image></view><text>个人中心</text></view>
|
</view>
|
|
<view v-if="showApplyNotice" class="apply-mask" @tap="closeApplyNotice">
|
<view class="apply-dialog" @tap.stop="preventClose">
|
<text class="dialog-title">温馨提示</text>
|
<text class="dialog-copy"> 请前往东区政务服务中心(东区大渡口街86号)线下进行人才认定。</text>
|
<view class="dialog-contact">
|
<text class="dialog-phone">联系电话:0812-3509008</text>
|
<text class="dialog-office">东区人才综合服务专窗</text>
|
</view>
|
<view class="dialog-confirm" hover-class="dialog-confirm-active" @tap="closeApplyNotice"><text>确定</text></view>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup lang="uts">
|
import { getApiBaseUrl, isApiConfigured } from '../../config/api.uts'
|
import { getTalentCardProfile, issueTalentDynamicCode } from '../../services/portal.uts'
|
import { getSession } from '../../services/session.uts'
|
|
const navTop = ref(28)
|
const navHeight = ref(32)
|
const navWidth = ref(88)
|
const navRight = ref(10)
|
const isGuest = ref(true)
|
const cardLoading = ref(false)
|
const cardLoadFailed = ref(false)
|
const talentId = ref('')
|
const cardLevel = ref('卓越人才卡')
|
const cardName = ref('-')
|
const cardGender = ref('-')
|
const cardNo = ref('-')
|
const workUnit = ref('未填写')
|
const cardStatusText = ref('人才卡有效')
|
const canShowCode = ref(false)
|
const cardBackground = ref('/static/card-assets/talent-card-background.jpg')
|
const codeImageUrl = ref('')
|
const codeLoading = ref(false)
|
const codeLoadFailed = ref(false)
|
const codeMessage = ref('正在生成动态人才码')
|
const refreshTip = ref('动态人才码每 60 秒自动更新,点击可手动刷新')
|
const advisorName = ref('')
|
const advisorDepartment = ref('')
|
const advisorMobile = ref('')
|
const advisorConsultUrl = ref('')
|
const showApplyNotice = ref(false)
|
let codeRefreshTimer: number | null = null
|
const guestCardTypes = ref([
|
{ title: '卓越人才卡', image: '/static/card-assets/card-excellence.jpg' },
|
{ title: '杰出人才卡', image: '/static/card-assets/card-elite.jpg' },
|
{ title: '菁英人才卡', image: '/static/card-assets/card-outstanding.jpg' }
|
])
|
|
onLoad((options: any) => {
|
// #ifdef MP-WEIXIN
|
const capsule = uni.getMenuButtonBoundingClientRect()
|
const systemInfo = uni.getSystemInfoSync()
|
navTop.value = capsule.top
|
navHeight.value = capsule.height
|
navWidth.value = capsule.width
|
navRight.value = systemInfo.windowWidth - capsule.right
|
// #endif
|
if (options != null && options['talentId'] != null) talentId.value = options['talentId'] as string
|
const session = getSession()
|
if (talentId.value == '' && session != null && session.talentId != null) talentId.value = session.talentId as string
|
if (options != null && options['preview'] == 'member' && talentId.value == '') talentId.value = '1'
|
loadTalentCard()
|
})
|
|
onUnload(() => clearCodeTimer())
|
|
const setCardBackground = (theme: string) => {
|
if (theme == 'OUTSTANDING') { cardBackground.value = '/static/card-assets/card-elite.jpg'; return }
|
if (theme == 'ELITE') { cardBackground.value = '/static/card-assets/card-outstanding.jpg'; return }
|
cardBackground.value = '/static/card-assets/talent-card-background.jpg'
|
}
|
const loadTalentCard = () => {
|
if (!isApiConfigured() || talentId.value == '') { isGuest.value = true; return }
|
cardLoading.value = true
|
cardLoadFailed.value = false
|
getTalentCardProfile(talentId.value, (data: any) => {
|
cardLoading.value = false
|
isGuest.value = data == null || data.hasTalentCard !== true
|
if (isGuest.value || data.card == null) return
|
const card: any = data.card
|
cardLevel.value = card.level != null ? card.level as string : cardLevel.value
|
cardName.value = card.name != null ? card.name as string : '-'
|
cardGender.value = card.gender != null ? card.gender as string : '-'
|
cardNo.value = card.cardNo != null ? card.cardNo as string : '-'
|
workUnit.value = card.workUnit != null ? card.workUnit as string : '未填写'
|
cardStatusText.value = card.statusText != null ? card.statusText as string : '人才卡状态未知'
|
canShowCode.value = card.canShowCode === true
|
setCardBackground(card.theme != null ? card.theme as string : '')
|
const advisor: any = data.advisor
|
advisorName.value = advisor != null && advisor.name != null ? advisor.name as string : ''
|
advisorDepartment.value = advisor != null && advisor.department != null ? advisor.department as string : ''
|
advisorMobile.value = advisor != null && advisor.mobile != null ? advisor.mobile as string : ''
|
advisorConsultUrl.value = advisor != null && advisor.consultUrl != null ? advisor.consultUrl as string : ''
|
if (canShowCode.value) requestDynamicCode()
|
}, () => {
|
cardLoading.value = false
|
cardLoadFailed.value = true
|
isGuest.value = false
|
})
|
}
|
const clearCodeTimer = () => {
|
if (codeRefreshTimer != null) { clearTimeout(codeRefreshTimer); codeRefreshTimer = null }
|
}
|
const requestDynamicCode = () => {
|
if (!canShowCode.value || talentId.value == '' || codeLoading.value) return
|
clearCodeTimer()
|
codeLoading.value = true
|
codeLoadFailed.value = false
|
codeImageUrl.value = ''
|
codeMessage.value = '正在生成动态人才码'
|
issueTalentDynamicCode(talentId.value, (data: any) => {
|
codeLoading.value = false
|
if (data == null || data.imagePath == null) { handleCodeFailure('动态人才码生成失败'); return }
|
codeImageUrl.value = getApiBaseUrl().replace(/\/$/, '') + (data.imagePath as string)
|
refreshTip.value = '动态人才码每 60 秒自动更新,点击可手动刷新'
|
const seconds = data.refreshAfterSeconds != null ? Number(data.refreshAfterSeconds) : 60
|
codeRefreshTimer = setTimeout(() => requestDynamicCode(), Math.max(10, seconds) * 1000)
|
}, (error) => handleCodeFailure(error.message))
|
}
|
const handleCodeFailure = (message: string) => {
|
codeLoading.value = false
|
codeImageUrl.value = ''
|
codeLoadFailed.value = true
|
codeMessage.value = message == '' ? '动态人才码刷新失败' : message
|
refreshTip.value = '动态人才码已失效,请重新加载'
|
}
|
const handleCodeImageError = () => handleCodeFailure('动态人才码已失效')
|
const refreshCode = () => { if (canShowCode.value) requestDynamicCode() }
|
const openApplyNotice = () => { showApplyNotice.value = true }
|
const openApplyConditions = () => uni.navigateTo({ url: '/pages/card-application-conditions/card-application-conditions' })
|
const closeApplyNotice = () => { showApplyNotice.value = false }
|
const preventClose = () => {}
|
const onlineConsult = () => {
|
if (!advisorConsultUrl.value.match(/^https:\/\//)) { uni.showToast({ title: '暂未配置在线咨询', icon: 'none' }); return }
|
uni.navigateTo({ url: '/pages/external-webview/external-webview?source=advisor&talentId=' + encodeURIComponent(talentId.value) })
|
}
|
const callAdvisor = () => { if (advisorMobile.value == '') { uni.showToast({ title: '暂未配置服务专员电话', icon: 'none' }); return }; uni.makePhoneCall({ phoneNumber: advisorMobile.value }) }
|
const goHome = () => uni.reLaunch({ url: '/pages/index/index' })
|
const goBenefits = () => uni.reLaunch({ url: '/pages/benefits/benefits' })
|
const goPolicy = () => uni.reLaunch({ url: '/pages/policy/policy' })
|
const goProfile = () => uni.reLaunch({ url: '/pages/profile/profile' })
|
</script>
|
|
<style>
|
.page { display: flex; width: 100%; height: 100vh; overflow: hidden; background-image: linear-gradient(180deg, #FFF0AF 0%, #FFF8E9 33%, #FFF7F2 100%); flex-direction: column; }
|
.custom-nav { flex-shrink: 0; }
|
.safe-space { height: 28px; }
|
.nav-row { display: flex; height: 32px; box-sizing: border-box; padding-left: 37rpx; padding-right: 10px; flex-direction: row; align-items: center; justify-content: space-between; }
|
.page-title { color: #1C1D20; font-size: 39rpx; font-weight: 700; }
|
.capsule-space { width: 88px; height: 100%; flex-shrink: 0; }
|
.content { min-height: 0; flex: 1; }
|
.guest-content { padding: 30rpx 40rpx 0; }
|
.guest-intro { box-sizing: border-box; padding: 36rpx 34rpx 0; border-radius: 20rpx; background-color: rgba(255,255,255,.94); text-align: center; }
|
.guest-intro-title { display: block; color: #2F3032; font-size: 32rpx; line-height: 44rpx; font-weight: 700; }
|
.guest-intro-copy { display: block; margin-top: 25rpx; color: #59616D; font-size: 25rpx; line-height: 40rpx; text-align: left; }
|
.apply-button { display: flex; width: 100%; height: 84rpx; margin-top: 25rpx; border-radius: 43rpx; background-image: linear-gradient(90deg, #FF4A32 0%, #FF8A1B 100%); box-shadow: 0 9rpx 18rpx rgba(255,104,33,.18); justify-content: center; align-items: center; }
|
.apply-button text { color: #FFFFFF; font-size: 28rpx; font-weight: 700; }
|
.apply-button-active { opacity: .78; transform: scale(.99); }
|
.guest-card-item { margin-top: 30rpx; box-sizing: border-box; padding: 25rpx 31rpx 27rpx; border-radius: 20rpx; background-color: rgba(255,255,255,.94); }
|
.guest-card-title { display: flex; height: 40rpx; flex-direction: row; justify-content: center; align-items: center; }
|
.guest-card-title view { width: 70rpx; height: 2rpx; background-image: linear-gradient(90deg, rgba(230,177,44,0) 0%, #E5B12E 100%); }
|
.guest-card-title view:last-child { transform: rotate(180deg); }
|
.guest-card-title text { margin: 0 21rpx; color: #343538; font-size: 29rpx; line-height: 40rpx; font-weight: 700; }
|
.guest-card-preview { height: 386rpx; margin-top: 22rpx; overflow: hidden; border-radius: 7rpx; background-color: transparent; }
|
.guest-card-image { width: 100%; height: 100%; }
|
.condition-link { display: flex; width: 218rpx; height: 43rpx; margin: 18rpx auto 0; box-sizing: border-box; border-width: 1rpx; border-style: solid; border-color: #FFC7B2; border-radius: 6rpx; background-color: #FFFDFB; justify-content: center; align-items: center; }
|
.condition-link text { color: #FF7546; font-size: 22rpx; line-height: 32rpx; }
|
.condition-link-active { background-color: #FFF1EB; }
|
.guest-bottom { height: 44rpx; }
|
.card-panel { display: flex; margin: 34rpx 44rpx 0; box-sizing: border-box; padding: 29rpx 42rpx 25rpx; border-width: 3rpx; border-style: solid; border-color: rgba(255,255,255,.92); border-radius: 22rpx; background-color: rgba(255,255,255,.76); flex-direction: column; align-items: center; }
|
.talent-card-face { width: 100%; height: 342rpx; position: relative; overflow: hidden; border-radius: 11rpx; background-color: #D5AD4F; }
|
.card-background { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
|
.card-tier-title { position: absolute; z-index: 2; top: 26rpx; right: 29rpx; color: #63502B; font-size: 23rpx; font-weight: 600; }
|
.card-data-row { display: flex; position: absolute; z-index: 2; left: 56rpx; right: 35rpx; flex-direction: row; align-items: center; }
|
.name-row { top: 87rpx; }
|
.number-row { top: 145rpx; }
|
.work-row { top: 203rpx; }
|
.field-group { display: flex; flex-direction: row; align-items: center; }
|
.gender-group { margin-left: 81rpx; }
|
.field-label { color: #383129; font-size: 21rpx; }
|
.field-value { margin-left: 25rpx; color: #1E1C19; font-size: 22rpx; font-weight: 500; }
|
.benefit-badge { display: flex; min-width: 274rpx; height: 58rpx; margin-top: 18rpx; box-sizing: border-box; padding: 0 18rpx; border-width: 2rpx; border-style: solid; border-color: #F5B92A; border-radius: 29rpx; background-color: #FFF9E9; flex-direction: row; justify-content: center; align-items: center; }
|
.benefit-badge image { width: 45rpx; height: 39rpx; margin-right: 11rpx; }
|
.benefit-badge text { color: #4B443D; font-size: 27rpx; font-weight: 500; }
|
.qr-frame { display: flex; width: 426rpx; height: 428rpx; margin-top: 18rpx; box-sizing: border-box; border-width: 2rpx; border-style: solid; border-color: #F3D785; border-radius: 18rpx; overflow: hidden; background-color: #FFFFFF; justify-content: center; align-items: center; }
|
.dynamic-code-placeholder { max-width: 300rpx; color: #8B7554; font-size: 25rpx; line-height: 38rpx; text-align: center; }
|
.dynamic-code-image { width: 100%; height: 100%; }
|
.code-state, .member-state { display: flex; color: #8B7554; font-size: 25rpx; line-height: 38rpx; text-align: center; flex-direction: column; align-items: center; }
|
.member-state { margin-top: 260rpx; color: #6B707A; }
|
.loading-spinner { width: 44rpx; height: 44rpx; margin-bottom: 22rpx; border: 6rpx solid #F9DFC7; border-top-color: #F47A35; border-radius: 50%; animation: card-spin 850ms linear infinite; }
|
.retry-text { margin-top: 14rpx; color: #F06D31; font-size: 24rpx; }
|
.qr-unavailable { background-color: #FBFBFB; }
|
.qr-frame-active { opacity: .72; }
|
.refresh-tip { margin-top: 13rpx; color: #858C99; font-size: 23rpx; }
|
.service-panel { margin: 20rpx 44rpx 0; padding: 22rpx 20rpx 16rpx; border-radius: 19rpx; background-color: rgba(255,255,255,.84); }
|
.service-title-row { display: flex; flex-direction: row; justify-content: center; align-items: center; }
|
.service-title-row > text { margin: 0 21rpx; color: #4A4A4B; font-size: 28rpx; font-weight: 600; }
|
.title-line { width: 67rpx; height: 2rpx; background-image: linear-gradient(90deg, rgba(244,193,53,0) 0%, #F4C135 100%); }
|
.title-line:last-child { transform: rotate(180deg); }
|
.service-advisor { display: block; margin-top: 7rpx; color: #737985; font-size: 22rpx; text-align: center; }
|
.service-actions { display: flex; margin-top: 27rpx; flex-direction: row; justify-content: space-around; align-items: center; }
|
.service-action { display: flex; min-width: 205rpx; height: 58rpx; border-radius: 14rpx; flex-direction: row; justify-content: center; align-items: center; }
|
.service-action-active { background-color: #FFF4E7; }
|
.service-action image { width: 47rpx; height: 47rpx; margin-right: 13rpx; }
|
.service-action text { color: #A66D32; font-size: 25rpx; font-weight: 600; }
|
.benefit-badge.is-disabled { border-color: #D7D7D7; background-color: #F6F6F6; }
|
.benefit-badge.is-disabled text { color: #898989; }
|
.content-bottom { height: 28rpx; }
|
.app-tab-bar { display: flex; height: 126rpx; height: calc(126rpx + env(safe-area-inset-bottom)); box-sizing: border-box; padding: 8rpx 14rpx; padding-bottom: calc(8rpx + env(safe-area-inset-bottom)); flex-shrink: 0; overflow: visible; background-color: rgba(255,255,255,.96); border-top-width: 1rpx; border-top-style: solid; border-top-color: #F0F0F0; flex-direction: row; justify-content: space-between; }
|
.app-tab { display: flex; width: 20%; padding-top: 4rpx; flex-direction: column; align-items: center; color: #708099; font-size: 24rpx; }
|
.app-tab-icon { display: flex; width: 66rpx; height: 66rpx; margin-bottom: 13rpx; border-radius: 33rpx; justify-content: center; align-items: center; }
|
.app-tab-icon image { width: 42rpx; height: 42rpx; }
|
.card-tab { margin-top: -16rpx; padding-top: 0; }
|
.app-card-icon { display: flex; width: 160rpx; height: 160rpx; position: relative; justify-content: center; align-items: center; }
|
.app-card-icon image { width: 160rpx; height: 160rpx; }
|
.app-card-icon text { position: absolute; z-index: 1; left: 0; right: 0; bottom: 42rpx; color: #FFFFFF; font-size: 18rpx; font-weight: 700; text-align: center; }
|
.apply-mask { display: flex; position: fixed; z-index: 100; left: 0; right: 0; top: 0; bottom: 0; padding: 0 48rpx; background-color: rgba(0,0,0,.42); justify-content: center; align-items: center; }
|
.apply-dialog { width: 100%; box-sizing: border-box; padding: 59rpx 47rpx 39rpx; border-radius: 32rpx; background-color: #FFFFFF; }
|
.dialog-title { display: block; color: #386BB0; font-size: 43rpx; line-height: 60rpx; font-weight: 700; text-align: center; }
|
.dialog-copy { display: block; margin-top: 44rpx; color: #737F91; font-size: 27rpx; line-height: 42rpx; font-weight: 600; }
|
.dialog-contact { display: flex; margin-top: 16rpx; flex-direction: column; align-items: flex-end; }
|
.dialog-phone, .dialog-office { display: block; color: #FF8957; font-size: 27rpx; line-height: 39rpx; font-weight: 700; text-align: right; }
|
.dialog-office { margin-top: 4rpx; }
|
.dialog-confirm { display: flex; width: 200rpx; height: 60rpx; margin: 39rpx auto 0; border-radius: 31rpx; background-image: linear-gradient(90deg, #FF4A32 0%, #FF8A1B 100%); justify-content: center; align-items: center; }
|
.dialog-confirm text { color: #FFFFFF; font-size: 27rpx; font-weight: 700; }
|
.dialog-confirm-active { opacity: .78; }
|
@keyframes card-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
</style>
|