ykt-team-wxapp/pages/team/business-card.vue

2007 lines
45 KiB
Vue
Raw Normal View History

2026-06-23 15:36:34 +08:00
<template>
<full-page>
<view class="card-page">
<view class="card-service-container">
<BusinessCard :card="businessCard" />
<view class="service-section">
<view class="service-grid">
2026-06-24 10:59:03 +08:00
<view class="service-item service-item-1" @click="showTeams()">
2026-06-23 15:36:34 +08:00
<image class="service-item-bg" src="/static/card/jzfwBack.png" mode="aspectFit"></image>
<image class="service-icon" src="/static/card/jzfwIcon.png" mode="aspectFit"></image>
<text class="service-text">全周期管理</text>
</view>
2026-06-24 10:59:03 +08:00
<view class="service-item service-item-3" @click="displayPhone()">
2026-06-23 15:36:34 +08:00
<image class="service-item-bg" src="/static/card/gzhBack.png" mode="aspectFit"></image>
<image class="service-icon" src="/static/card/telephone.svg" mode="aspectFit"></image>
<text class="service-text">联系电话</text>
</view>
2026-06-24 10:59:03 +08:00
<view class="service-item service-item-4" @click="openEmailModal">
2026-06-23 15:36:34 +08:00
<image class="service-item-bg" src="/static/card/sphBack.png" mode="aspectFit"></image>
<image class="service-icon" src="/static/card/email.svg" mode="aspectFit"></image>
<text class="service-text">邮箱</text>
</view>
2026-06-24 10:59:03 +08:00
<view class="service-item service-item-2" @click="openAddressModal">
<image class="service-item-bg" src="/static/card/gwBack.png" mode="aspectFit"></image>
<image class="service-icon" src="/static/card/gwIcon.png" mode="aspectFit"></image>
<text class="service-text">地址</text>
</view>
2026-06-23 15:36:34 +08:00
</view>
</view>
</view>
<view class="info-container">
<!-- Tab 切换栏 -->
<view class="tab-header">
<view v-if="hospitalInfo" class="tab-item" :class="{ active: activeTab === 'hospital' }"
@click="switchTab('hospital')">
<text class="tab-text">医院信息</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 'intro' }" @click="switchTab('intro')">
<text class="tab-text">个人简介</text>
</view>
</view>
<!-- 医院信息内容 -->
<view v-if="hospitalInfo" class="tab-content" :class="{ 'tab-content-left': activeTab === 'hospital' }"
v-show="activeTab === 'hospital'">
<view class="hospital-info">
<view class="hospital-header">
<view class="hospital-logo">
<image :src="hospitalInfo.logo || '/static/card/hospital-icon.png'" mode="aspectFit">
</image>
</view>
<view class="hospital-details">
<text class="hospital-name">
{{ hospitalInfo.name || '未获取到医院名称' }}
</text>
<view class="hospital-tags">
<text class="hospital-tag" :class="getTagClass(index)"
v-for="(tag, index) in (hospitalInfo.tags || [])" :key="tag">{{ tag }}
</text>
</view>
</view>
</view>
<view class="hospital-intro">
<view class="hospital-text-wrapper">
<view v-if="hospitalInfo.introduction" class="hospital-text">
<div v-html="hospitalInfo.introduction"></div>
</view>
<text class="hospital-text" v-else>暂无医院介绍信息</text>
<!-- <view class="more-link" @click="openMoreInfoModal">更多>></view> -->
</view>
</view>
</view>
</view>
<!-- 个人简介内容 -->
<view class="tab-content" :class="{ 'tab-content-right': activeTab === 'intro' }"
v-show="activeTab === 'intro'">
<view class="intro-content">
<view class="intro-header">
<view class="intro-title-bar"></view>
<text class="intro-title">个人简介</text>
</view>
<text class="intro-text" v-if="businessCard && businessCard.memberTroduce">
{{ businessCard.memberTroduce }}
</text>
<text class="intro-text no-data" v-else>暂无个人简介</text>
</view>
</view>
</view>
</view>
<template #footer>
<view class="bottom-actions">
<button class="edit-btn" @click="addFriend()">
<image class="edit-icon" src="/static/card/edit-card.png" mode="aspectFit"></image>
<text class="edit-text">加好友</text>
</button>
<button class="share-btn" @click="goToSave">
<text class="share-text">转发名片</text>
</button>
</view>
</template>
</full-page>
<!-- 便民服务弹窗 -->
<view v-if="showServiceModal" class="modal-mask" @click="closeServiceModal">
<view class="service-modal-content" @click.stop>
<view class="service-modal-header">
<text class="service-modal-close" @click="closeServiceModal">×</text>
<text class="service-modal-title">便民服务</text>
</view>
<view class="service-modal-body">
<view class="service-list">
<view class="service-list-item" v-for="(service, index) in services" :key="index"
@click="openServiceLink(service)">
<text class="service-list-text">{{ service.label }}</text>
<view class="service-list-icon">🔗</view>
</view>
<view v-if="services.length === 0" class="no-service">
<text class="no-service-text">暂无便民服务</text>
</view>
</view>
</view>
</view>
</view>
<!-- 电话弹窗 -->
<view v-if="showPhoneModal" class="modal-mask" @click="closePhoneModal">
<view class="service-modal-content" @click.stop>
<view class="service-modal-header">
<text class="service-modal-close" @click="closePhoneModal">×</text>
<text class="service-modal-title">电话</text>
</view>
<view class="service-modal-body">
<view class="service-list">
2026-06-24 10:59:03 +08:00
<view class="service-list-item" v-if="businessCard && businessCard.externalContact">
<view class="service-list-text">{{ businessCard.externalContact }}</view>
<view class="flex">
<view class="text-base text-primary mr-10" @click="setClipboardData(businessCard.externalContact)">
复制
</view>
<view class="text-base text-primary" @click="makePhoneCall(businessCard.externalContact)">拨打</view>
</view>
2026-06-23 15:36:34 +08:00
</view>
<view class="service-list-item" v-else>
<text class="service-list-text no-data">暂无电话信息</text>
<view class="service-list-icon">📞</view>
</view>
</view>
</view>
</view>
</view>
<!-- 邮箱弹窗 -->
<view v-if="showEmailModal" class="modal-mask" @click="closeEmailModal">
<view class="service-modal-content" @click.stop>
<view class="service-modal-header">
<text class="service-modal-close" @click="closeEmailModal">×</text>
<text class="service-modal-title">邮箱</text>
</view>
<view class="service-modal-body">
<view class="service-list">
2026-06-24 10:59:03 +08:00
<view class="service-list-item" v-if="businessCard.externalEmail"
@click="copyEmail(businessCard.externalEmail)">
<text class="service-list-text">{{ businessCard.externalEmail }}</text>
<view class="text-base text-primary mr-10" @click="setClipboardData(businessCard.externalEmail)">
复制
</view>
2026-06-23 15:36:34 +08:00
</view>
2026-06-24 10:59:03 +08:00
<view class="service-list-item" v-else>
2026-06-23 15:36:34 +08:00
<text class="service-list-text no-data">暂无邮箱信息</text>
<view class="service-list-icon"></view>
</view>
</view>
</view>
</view>
</view>
<!-- 地址弹窗 -->
<view v-if="showAddressModal" class="modal-mask" @click="closeAddressModal">
<view class="service-modal-content" @click.stop>
<view class="service-modal-header">
<text class="service-modal-close" @click="closeAddressModal">×</text>
<text class="service-modal-title">地址</text>
</view>
<view class="service-modal-body">
<view class="service-list">
2026-06-24 10:59:03 +08:00
<view class="service-list-item" v-if="businessCard.externalAddress"
@click="setClipboardData(businessCard.externalAddress)">
<text class="service-list-text">{{ businessCard.externalAddress }}</text>
<view class="text-base text-primary" @click="setClipboardData(businessCard.externalAddress)">
复制
</view>
2026-06-23 15:36:34 +08:00
</view>
2026-06-24 10:59:03 +08:00
<view class="service-list-item" v-else>
2026-06-23 15:36:34 +08:00
<text class="service-list-text no-data">暂无地址信息</text>
<view class="service-list-icon">📍</view>
</view>
</view>
</view>
</view>
</view>
<!-- 医院介绍弹窗 -->
<view v-if="showMoreInfoModal" class="modal-mask" @click="closeMoreInfoModal">
<view class="more-info-modal-content" @click.stop @touchmove.stop.prevent>
<view class="more-info-modal-header">
<text class="more-info-modal-title">医院介绍</text>
<text class="more-info-modal-close" @click="closeMoreInfoModal">×</text>
</view>
<scroll-view class="more-info-modal-body" scroll-y="true" show-scrollbar="true">
<view class="more-info-content" v-if="institutionInfo?.introduction">
<rich-text :nodes="institutionInfo.introduction"></rich-text>
</view>
<view class="more-info-text" v-else>
<text class="no-data-text">暂无医院介绍信息</text>
</view>
</scroll-view>
</view>
</view>
<!-- 公众号弹窗 -->
<view v-if="showOfficialAccountModal" class="modal-mask" @click="closeOfficialAccountModal">
<view class="link-modal-content" @click.stop>
<view class="link-modal-header">
<text class="link-modal-title">广医一院公众号</text>
<text class="link-modal-close" @click="closeOfficialAccountModal">×</text>
</view>
<view class="link-modal-body">
<view class="qr-code-placeholder">
<view class="qr-code-image">
<image src="/static/card/服务号.jpg" mode="aspectFit"></image>
</view>
</view>
<text class="link-modal-desc">扫描或长按二维码进入</text>
</view>
</view>
</view>
2026-06-24 10:59:03 +08:00
<teams-popup :visible="showTeamPopup" :teams="teams" @close="showTeamPopup = false" />
2026-06-23 15:36:34 +08:00
</template>
<script setup>
import { ref, computed } from 'vue';
import api from '@/utils/api';
import useGuard from '@/hooks/useGuard';
2026-06-24 10:59:03 +08:00
import { toast } from '@/utils/widget.js';
2026-06-23 15:36:34 +08:00
import BusinessCard from './components/BusinessCard.vue'
import fullPage from '@/components/full-page.vue'
2026-06-24 10:59:03 +08:00
import TeamsPopup from './components/teams-popup.vue'
2026-06-23 15:36:34 +08:00
const { useShow, useLoad } = useGuard();
const corpId = ref('');
const userid = ref('')
const businessCard = ref(null);
2026-06-24 10:59:03 +08:00
const teams = ref([])
const showTeamPopup = ref(false)
2026-06-23 15:36:34 +08:00
const hospitalInfo = computed(() => businessCard.value?.hospitalInfo || null);
2026-06-24 10:59:03 +08:00
function makePhoneCall(phone) {
closePhoneModal()
uni.makePhoneCall({
phoneNumber: phone,
success: () => {
toast('拨打电话成功')
}
})
}
function setClipboardData(data) {
uni.setClipboardData({
data: data,
success: () => {
toast('复制成功')
}
})
}
2026-06-23 15:36:34 +08:00
async function getCard() {
const res = await api('getBusinessCard', { userid: userid.value, corpId: corpId.value })
businessCard.value = res && res.data ? res.data : null;
}
2026-06-24 10:59:03 +08:00
async function displayPhone() {
if (businessCard.value.externalContact) {
showPhoneModal.value = true
}
}
async function showTeams() {
const res = await api('getJoinedTeams', { mateId: userid.value, corpId: corpId.value })
teams.value = res && Array.isArray(res.data) ? res.data : [];
if (teams.value.length === 0) {
toast('暂无加入的团队')
} else {
showTeamPopup.value = true
}
}
2026-06-23 15:36:34 +08:00
useLoad((opt) => {
corpId.value = opt.corpId
userid.value = opt.userid
})
useShow(() => {
getCard()
})
const cardStore = ref({})
const accountStore = ref({})
useLoad()
// 获取账户信息
const accountInfo = computed(() => accountStore.getAccountInfo || {})
const institutionInfo = computed(() => accountStore.getInstitutionInfo || {})
// 获取医生名片状态
const doctorCardStatus = computed(() => accountStore.getDoctorCardStatus)
// 判断是否有联系方式
const hasContactInfo = computed(() => {
return cardStore.phoneVisible || cardStore.emailVisible || cardStore.addressVisible
})
// 弹窗显示状态
const showServiceModal = ref(false)
const showPhoneModal = ref(false)
const showEmailModal = ref(false)
const showAddressModal = ref(false)
const showMoreInfoModal = ref(false)
const showOfficialAccountModal = ref(false)
const showVideoAccountModal = ref(false)
const showShareModal = ref(false)
// 便民服务列表
const services = ref([])
// Tab 切换状态
const activeTab = ref('hospital') // 'hospital' 或 'intro'
// 切换 tab
const switchTab = (tab) => {
activeTab.value = tab
}
// 初始化 tab 状态
const initTab = () => {
// 如果只有个人简介,默认显示个人简介
if (!institutionInfo.value?.showHomepage) {
activeTab.value = 'intro'
}
// 如果只有医院信息,默认显示医院信息
else if (!accountInfo.value.introduction) {
activeTab.value = 'hospital'
}
// 如果两者都有,默认显示医院信息
else {
activeTab.value = 'hospital'
}
}
// 防止在同一次拉取过程中重复创建名片
let isAutoCreatingCard = false
const goToCard = () => {
uni.navigateTo({
url: '/pages/card/card'
})
}
// 获取标签样式类名
const getTagClass = (index) => {
const styles = ['grade', 'center', 'specialty']
return styles[index % 3]
}
const goToQRCode = () => {
uni.navigateTo({
url: '/pages/card/code'
})
}
// 跳转到保存名片页
const goToSave = () => {
uni.navigateTo({
url: '/pages/business-card/card-qrcode/card-qrcode'
})
}
// 处理Banner图片点击
const handleBannerClick = (item) => {
if (!item || !item.link) {
return
}
const link = item.link
// 判断是否为外部链接
if (link.startsWith('http://') || link.startsWith('https://')) {
// #ifdef H5
window.open(link, '_blank')
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(link)
// #endif
// #ifdef MP-WEIXIN
// 微信小程序中,需要先复制链接,然后提示用户
uni.setClipboardData({
data: link,
success: () => {
uni.showToast({
title: '链接已复制',
icon: 'success'
})
}
})
// #endif
} else {
// 内部链接,使用导航
uni.navigateTo({
url: link
})
}
}
// 打开便民服务弹窗
const openServiceModal = () => {
showServiceModal.value = true
}
// 关闭便民服务弹窗
const closeServiceModal = () => {
showServiceModal.value = false
}
// 打开便民服务链接
const openServiceLink = (service) => {
if (service.url) {
// 关闭弹窗
closeServiceModal()
// 显示确认对话框
uni.showModal({
title: '提示',
content: `即将跳转到:${service.label}`,
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '正在跳转中...',
icon: 'none'
})
// #ifdef H5
// H5环境下使用window.open打开外部链接
window.open(service.url, '_blank')
// #endif
// #ifdef MP-WEIXIN
// 微信小程序环境下使用uni.navigateToMiniProgram或提示用户
uni.showToast({
title: '请在浏览器中打开链接',
icon: 'none',
duration: 2000
})
// #endif
// #ifdef APP-PLUS
// App环境下使用plus.runtime.openURL
plus.runtime.openURL(service.url)
// #endif
}
}
})
} else {
uni.showToast({
title: '该服务暂无链接',
icon: 'none'
})
}
}
// 打开电话弹窗
const openPhoneModal = () => {
showPhoneModal.value = true
}
// 关闭电话弹窗
const closePhoneModal = () => {
showPhoneModal.value = false
}
// 打开邮箱弹窗
const openEmailModal = () => {
showEmailModal.value = true
}
// 关闭邮箱弹窗
const closeEmailModal = () => {
showEmailModal.value = false
}
// 打开地址弹窗
const openAddressModal = () => {
showAddressModal.value = true
}
// 关闭地址弹窗
const closeAddressModal = () => {
showAddressModal.value = false
}
// 打开医院介绍弹窗
const openMoreInfoModal = () => {
showMoreInfoModal.value = true
}
// 关闭医院介绍弹窗
const closeMoreInfoModal = () => {
showMoreInfoModal.value = false
}
// 打开公众号弹窗
const openOfficialAccountModal = () => {
showOfficialAccountModal.value = true
}
// 关闭公众号弹窗
const closeOfficialAccountModal = () => {
showOfficialAccountModal.value = false
}
// 打开视频号弹窗
const openVideoAccountModal = () => {
showVideoAccountModal.value = true
}
// 关闭视频号弹窗
const closeVideoAccountModal = () => {
showVideoAccountModal.value = false
}
// 处理人才招聘点击
const handleRecruitClick = () => {
const recruitUrl = 'https://www.gyfyyy.cn/cn/wap/news/xwsd/'
openExternalLink(recruitUrl)
}
// 处理官网点击
const handleWebsiteClick = () => {
const websiteUrl = 'https://www.gyfyyy.cn/'
openExternalLink(websiteUrl)
}
// 打开外部链接的通用方法
const openExternalLink = (url) => {
if (!url) {
uni.showToast({
title: '链接地址无效',
icon: 'none'
})
return
}
// 判断是否为外部链接
if (url.startsWith('http://') || url.startsWith('https://')) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(url)
// #endif
// #ifdef MP-WEIXIN
// 微信小程序中,需要先复制链接,然后提示用户
uni.setClipboardData({
data: url,
success: () => {
uni.showModal({
title: '提示',
content: '链接已复制到剪贴板,请在浏览器中打开',
showCancel: false
})
}
})
// #endif
} else {
// 内部链接,使用导航
uni.navigateTo({
url: url
})
}
}
// 处理来院导航点击
const handleNavigationClick = () => {
// 跳转到导航页面
uni.navigateTo({
url: '/pages/navigation/index'
})
}
// 打开官网
const openWebsite = () => {
// 在微信小程序中打开外部网址
uni.showModal({
title: '提示',
content: '即将打开医院官网,是否继续?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '正在跳转中...',
icon: 'none',
duration: 1500
})
}
}
})
}
// 打开互联网医院
const openInternetHospital = () => {
// 在微信小程序中打开外部网址
uni.showModal({
title: '提示',
content: '即将打开互联网医院平台,是否继续?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '正在跳转中...',
icon: 'none',
duration: 3000
})
}
}
})
}
// 打开分享弹窗
const openShareModal = () => {
showShareModal.value = true
}
// 关闭分享弹窗
const closeShareModal = () => {
showShareModal.value = false
}
// 分享到企业微信
const shareToWechatWork = () => {
uni.showToast({
title: '分享到企业微信...',
icon: 'none'
})
closeShareModal()
}
// 分享到微信
const shareToWechat = () => {
uni.showToast({
title: '分享到微信...',
icon: 'none'
})
closeShareModal()
}
// 跳转到编辑名片页
const addFriend = () => {
uni.navigateTo({
url: `/pages/team/friend?corpId=${corpId.value}&userid=${userid.value}`
})
}
// 复制邮箱地址
const copyEmail = (email) => {
closeEmailModal()
// #ifdef H5
// H5环境下使用navigator.clipboard API
if (navigator.clipboard) {
navigator.clipboard.writeText(email).then(() => {
uni.showToast({
title: '邮箱已复制',
icon: 'none'
})
}).catch(() => {
// 降级方案:使用传统方法
const textArea = document.createElement('textarea')
textArea.value = email
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
uni.showToast({
title: '邮箱已复制',
icon: 'none'
})
})
} else {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = email
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
uni.showToast({
title: '邮箱已复制',
icon: 'none'
})
}
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
// 微信小程序和App环境下使用uni.setClipboardData
uni.setClipboardData({
data: email,
success: () => {
uni.showToast({
title: '邮箱已复制',
icon: 'none'
})
},
fail: () => {
uni.showToast({
title: '复制失败',
icon: 'none'
})
}
})
// #endif
}
// 打开地图
const openMap = (address) => {
// 关闭地址弹窗
closeAddressModal()
// #ifdef H5
// H5环境下使用百度地图或高德地图网页版
uni.showModal({
title: '提示',
content: '即将在浏览器中打开地图,是否继续?',
success: (res) => {
if (res.confirm) {
// 使用百度地图网页版
const mapUrl = `https://api.map.baidu.com/geocoder?address=${encodeURIComponent(address)}&output=html&src=webapp.baidu.openAPIdemo`
window.open(mapUrl, '_blank')
}
}
})
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
// 微信小程序和App环境下使用uni.openLocation
uni.openLocation({
latitude: 23.1291, // 广州的纬度
longitude: 113.2644, // 广州的经度
name: '广州医科大学附属第一医院',
address: address,
scale: 18,
success: () => {
console.log('地图打开成功')
},
fail: (err) => {
console.error('地图打开失败:', err)
// 如果打开地图失败,提供备选方案
uni.showModal({
title: '提示',
content: '无法打开地图,是否复制地址到剪贴板?',
success: (res) => {
if (res.confirm) {
uni.setClipboardData({
data: address,
success: () => {
uni.showToast({
title: '地址已复制',
icon: 'none'
})
}
})
}
}
})
}
})
// #endif
}
const getDoctorCardListData = async () => {
try {
const res = await getDoctorCardList({
staffId: accountStore.getAccountInfo.staffId
})
// 更新医生名片状态和基本信息
if (res.success && res.data) {
// 提取基本信息如果API返回了医生信息
const basicInfo = res.data.staffInfo ? {
staffId: res.data.staffInfo.staffId || accountStore.getAccountInfo.staffId,
staffName: res.data.staffInfo.staffName || accountStore.getAccountInfo.staffName,
hospitalId: res.data.staffInfo.hospitalId || accountStore.getAccountInfo.hospitalId,
hospitalName: res.data.staffInfo.hospitalName || accountStore.getAccountInfo.hospitalName,
position: res.data.staffInfo.position || accountStore.getAccountInfo.position,
jobTitle: res.data.staffInfo.jobTitle || accountStore.getAccountInfo.jobTitle,
professionalTitle: res.data.staffInfo.professionalTitle || accountStore.getAccountInfo.professionalTitle,
phone: res.data.staffInfo.phone || accountStore.getAccountInfo.phone,
enterpriseWechatId: res.data.staffInfo.enterpriseWechatId || accountStore.getAccountInfo.enterpriseWechatId,
email: res.data.staffInfo.email || accountStore.getAccountInfo.email,
address: res.data.staffInfo.address || accountStore.getAccountInfo.address,
photo: res.data.staffInfo.photo || accountStore.getAccountInfo.photo,
introduction: res.data.staffInfo.introduction || accountStore.getAccountInfo.introduction,
departments: res.data.staffInfo.departments || accountStore.getAccountInfo.departments
} : null;
accountStore.updateDoctorCardStatus(
res.data.hasCards || false,
res.data.message || '',
basicInfo
)
// 当接口返回明确没有名片时:自动创建名片,然后重新拉取列表
if (res.data.hasCards === false) {
if (!isAutoCreatingCard) {
isAutoCreatingCard = true
try {
const account = accountStore.getAccountInfo
const departments = accountStore.getDepartments || []
const primaryDept =
departments.find((d) => d.isPrimary) || departments[0]
const externalDisplay = {
phone: cardStore.phoneVisible,
email: cardStore.emailVisible,
address: cardStore.addressVisible,
deptName: cardStore.deptNameVisible,
position: cardStore.positionVisible
}
const convenienceServices = (cardStore.services || []).map((service) => ({
serviceName: service.label || service.serviceName || '',
serviceUrl: service.url || service.serviceUrl || ''
}))
const requestData = {
staffId: account.staffId,
shareMessage: cardStore.shareMessage,
phone: account.phone,
email: account.email,
address: account.address,
position: account.position,
photo: account.photo,
introduction: account.introduction,
externalDisplay,
convenienceServices
}
// 设置主部门(保持与 edit-card.vue 一致)
if (primaryDept?.deptId) {
requestData.deptId = primaryDept.deptId
await setDeptStaffPrimary({
staffId: account.staffId,
primaryDeptId: primaryDept.deptId
})
accountStore.updatePrimaryDepartment(primaryDept.deptId)
}
uni.showLoading({ title: '正在创建名片...' })
const createRes = await createDoctorCard(requestData)
uni.hideLoading()
if (!createRes?.success) {
throw new Error(createRes?.message || '创建名片失败')
}
// 创建成功后重新拉取列表
await getDoctorCardListData()
return
} catch (error) {
uni.hideLoading()
uni.showToast({
title: '名片创建失败,请稍后重试',
icon: 'none',
duration: 2000
})
console.error('自动创建名片失败:', error)
} finally {
isAutoCreatingCard = false
}
}
}
// 更新名片列表数据
if (res.data.hasCards && res.data.cards) {
// 使用接口返回的名片数据
const newCardList = res.data.cards.map(card => ({
name: accountStore.getAccountInfo.staffName || '未知',
hospital: accountStore.getAccountInfo.hospitalName || '',
title: `${accountStore.getAccountInfo.deptName || ''} ${accountStore.getAccountInfo.position || ''}`,
version: card.version || '',
cardId: card.cardId
}))
// 更新cardStore中的名片列表
cardStore.setCardList(newCardList)
console.log('cardList:', newCardList)
// 如果当前没有选中的名片,或者当前选中的名片不在新列表中,则选择第一张
if (!cardStore.currentCardId || !newCardList.some(card => card.cardId === cardStore.currentCardId)) {
const firstCardId = newCardList[0].cardId || ''
cardStore.setCurrentCardId(firstCardId)
console.log('设置第一张名片为当前选中:', firstCardId)
// 更新名片缓存数据
await updateCardCache(firstCardId)
} else {
// 更新当前选中名片的缓存数据
await updateCardCache(cardStore.currentCardId)
}
} else {
// 如果没有名片,使用默认数据
const account = accountStore.getAccountInfo
const defaultCardList = [{
name: account.staffName || '',
hospital: account.hospitalName || '',
title: `${account.deptName || ''} ${account.position || ''}`,
version: '全员通用版',
cardId: 'default'
}]
cardStore.setCardList(defaultCardList)
cardStore.setCurrentCardId('default')
}
}
} catch (error) {
console.error('获取名片列表失败:', error)
// 如果获取失败,默认设置为没有名片
accountStore.updateDoctorCardStatus(false, '获取名片信息失败')
cardId.value = ''
// 使用默认名片数据
const account = accountStore.getAccountInfo
const defaultCardList = [{
name: account.staffName || '',
hospital: account.hospitalName || '',
title: `${account.deptName || ''} ${account.position || ''}`,
version: '全员通用版',
cardId: 'default'
}]
cardStore.setCardList(defaultCardList)
cardStore.setCurrentCardId('default')
}
}
// 更新名片缓存数据
const updateCardCache = async (cardId) => {
if (!cardId) return
try {
console.log('开始获取名片详情并更新缓存cardId:', cardId)
const detailRes = await getDoctorCardDetail({ cardId })
if (detailRes.success && detailRes.data) {
const cardData = detailRes.data
console.log('名片详情数据:', cardData)
// 更新名片分享语
if (cardData.shareMessage) {
cardStore.shareMessage = cardData.shareMessage
}
// 更新对外信息展示配置
if (cardData.externalDisplay) {
cardStore.updateContactVisibility(
cardData.externalDisplay.phone || false,
cardData.externalDisplay.email || false,
cardData.externalDisplay.address || false,
cardData.externalDisplay.deptName || false,
cardData.externalDisplay.position || false
)
}
// 更新便民服务
if (cardData.convenienceServices && cardData.convenienceServices.length > 0) {
cardStore.services = cardData.convenienceServices.map(service => ({
label: service.serviceName,
url: service.serviceUrl
}))
}
// 更新审核状态到缓存
if (cardData.auditStatus) {
cardStore.setAuditStatus(cardData.auditStatus)
console.log('名片审核状态已缓存:', cardData.auditStatus)
}
// 保存到本地存储
cardStore.saveToStorage()
console.log('名片缓存数据已更新')
}
} catch (error) {
console.error('更新名片缓存失败:', error)
}
}
</script>
<style scoped lang="scss">
/* 模态框遮罩层 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
justify-content: center;
z-index: 1000;
touch-action: none;
overflow: hidden;
}
.card-page {
background-color: #F8EEE3;
min-height: 100vh;
padding-bottom: 120rpx;
/* 为底部固定按钮栏留出空间 */
}
.card-page::before {
content: '';
position: absolute;
top: -20rpx;
left: 0;
right: 0;
height: 320rpx;
background: linear-gradient(180deg, #EB6625 0%, #F8EEE3 100%);
z-index: 0;
}
/* 名片和服务容器 */
.card-service-container {
border-radius: 20rpx;
overflow: hidden;
position: relative;
}
/* 服务图标区域 */
.service-section {
margin: -10rpx 20rpx 0rpx;
}
.service-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.service-item {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 36rpx 40rpx;
background-position: center;
background-repeat: no-repeat;
border-radius: 20rpx;
position: relative;
overflow: hidden;
background-size: 114% 150%;
}
.service-item-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
// .service-item-1 {
// background-image: url('/static/card/jzfwBack.png');
// }
// .service-item-2 {
// background-image: url('/static/card/gwBack.png');
// }
// .service-item-3 {
// background-image: url('/static/card/gzhBack.png');
// }
// .service-item-4 {
// background-image: url('/static/card/sphBack.png');
// }
.service-icon {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
margin-right: 48rpx;
}
.service-text {
font-size: 28rpx;
color: #fff;
text-align: center;
font-weight: 500;
position: relative;
z-index: 1;
}
/* 禁用状态的样式 */
.service-item.disabled {
opacity: 0.5;
pointer-events: none;
filter: grayscale(50%);
}
.service-icon.disabled {
filter: grayscale(100%);
opacity: 0.6;
}
.service-text.disabled {
color: rgba(255, 255, 255, 0.6);
}
/* 信息容器通用样式 */
.info-container {
overflow: hidden;
margin: 24rpx 20rpx;
position: relative;
padding: 0;
}
/* 人才招聘和导航按钮 */
.action-buttons {
display: flex;
gap: 20rpx;
margin: 20rpx;
}
.action-btn {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 24rpx 20rpx;
border-radius: 10px;
border: 1px solid #ffbf6533;
background: linear-gradient(95deg, #FFF5DB 16.63%, #FFECDE 92.44%);
box-shadow: 0 9px 15.6px 0 #FFF inset;
transition: all 0.3s ease;
}
.action-btn:active {
transform: scale(0.98);
opacity: 0.9;
}
.action-icon {
width: 48rpx;
height: 48rpx;
flex-shrink: 0;
}
.action-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
/* Tab 切换栏样式 */
.tab-header {
display: flex;
background-color: #F8EEE3;
border-radius: 20rpx 20rpx 0 0;
padding: 0;
overflow: visible;
position: relative;
}
.tab-item {
flex: 1;
padding: 24rpx 0;
text-align: center;
background: linear-gradient(83deg, #eb662566 15.22%, #faae5766 77.02%);
transition: background 0.3s ease, clip-path 0.3s ease, transform 0.3s ease;
cursor: pointer;
position: relative;
z-index: 1;
}
.tab-item:last-child {
margin-right: 0;
}
/* 未选中的 tab - 左侧 */
.tab-item:first-child:not(.active) {
border-radius: 20rpx 0 0 0;
/* 左边垂直,右边倾斜(上宽下窄),延伸到右侧区域 */
clip-path: polygon(0 0, 100% 0, 92% 100%, 0 100%);
/* 缩小未选中的 tab */
transform: scale(0.95);
}
/* 未选中的 tab - 右侧 */
.tab-item:last-child:not(.active) {
border-radius: 0 20rpx 0 0;
/* 右边垂直,左边倾斜(上宽下窄),延伸到左侧区域 */
clip-path: polygon(0% 0, 100% 0, 100% 100%, 4% 100%);
/* 缩小未选中的 tab */
transform: scale(0.95);
}
/* 选中的 tab - 背景变为白色渐变,上窄下宽,占据另一个 tab 的下底 */
.tab-item.active {
background: linear-gradient(178deg, #FEF4E8 10.53%, #fff 100%);
transition: background 0.3s ease, clip-path 0.3s ease, transform 0.3s ease;
z-index: 2;
/* 选中状态保持正常大小 */
transform: scale(1);
}
/* 选中左侧 tab医院信息左边直角边右边斜边上窄下宽下底宽 */
.tab-item.active:first-child {
border-radius: 20rpx 0 0 0;
/* 左边垂直(直角边),右边倾斜(斜边,上窄下宽),延伸到右侧 tab 区域 */
clip-path: polygon(0 0, 92% 0, 100% 100%, 0 100%);
/* 使用负边距让两个 tab 重叠,消除中间空隙 */
margin-right: -8%;
}
/* 选中右侧 tab个人简介右边直角边左边斜边上窄下宽下底宽 */
.tab-item.active:last-child {
border-radius: 0rpx 20rpx 0 0;
/* 右边垂直(直角边),左边倾斜(斜边,上窄下宽),延伸到左侧 tab 区域 */
clip-path: polygon(8% 0, 100% 0, 100% 100%, 0 100%);
/* 使用负边距让两个 tab 重叠,消除中间空隙 */
margin-left: -8%;
}
.tab-item.active:only-child {
border-radius: 20rpx 20rpx 0 0;
/* 只有一个 tab 时,保持矩形,不需要梯形 */
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
.tab-text {
font-size: 32rpx;
color: #333;
font-weight: 500;
position: relative;
display: inline-block;
transition: all 0.3s ease;
}
.tab-item.active .tab-text {
font-weight: bold;
color: #333;
}
/* 选中 tab 文字下方的橙红色下划线 */
.tab-item.active .tab-text::after {
content: '';
position: absolute;
bottom: -4rpx;
left: 0;
width: 100%;
height: 16rpx;
/* 颜色渐变:从深橙红色到浅色 */
background: linear-gradient(90deg, #EF8839 0%, rgba(239, 136, 57, 0.4) 70%, rgba(239, 136, 57, 0.1) 100%);
border-radius: 20rpx;
/* 使用 transform scaleX 实现从粗到细的效果,从左侧开始 */
transform: scaleX(1);
transform-origin: left center;
/* 添加过渡动画 */
transition: transform 0.3s ease;
/* 通过 mask 或 clip-path 实现从粗到细的视觉效果,粗的部分占据更大比例 */
clip-path: polygon(0 0, 100% 0, 97% 100%, 3% 100%);
}
/* Tab 内容区域 */
.tab-content {
padding: 20rpx 40rpx 30rpx;
background-color: #fff;
}
/* 选中左侧 tab医院信息时的内容区域 - 左上角有圆角,底部圆角 */
.tab-content-left {
border-radius: 0 20rpx 20rpx 20rpx;
}
/* 选中右侧 tab个人简介时的内容区域 - 右上角有圆角,底部圆角 */
.tab-content-right {
border-radius: 20rpx 0 20rpx 20rpx;
}
.section-header {
margin-bottom: 30rpx;
}
.section-title-wrapper {
position: relative;
display: inline-block;
margin-bottom: 20rpx;
width: fit-content;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
position: relative;
z-index: 2;
}
.title-decoration {
position: absolute;
bottom: -8rpx;
left: 0;
width: 144rpx;
height: 28rpx;
flex-shrink: 0;
border-radius: 4rpx;
background: linear-gradient(90deg, #FAAE57 0%, rgba(255, 255, 255, 0.64) 100%);
z-index: 1;
}
.hospital-info {
margin-top: 0;
}
.hospital-header {
display: flex;
align-items: flex-start;
gap: 20rpx;
margin-bottom: 20rpx;
}
.hospital-logo {
font-size: 48rpx;
flex-shrink: 0;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
.hospital-logo image {
width: 100%;
height: 100%;
}
.hospital-details {
flex: 1;
}
.hospital-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 16rpx;
display: block;
}
.hospital-tags {
display: flex;
gap: 12rpx;
flex-wrap: wrap;
}
.hospital-tag {
font-size: 20rpx;
font-weight: 400;
padding: 0 16rpx;
line-height: 36rpx;
flex-shrink: 0;
border-radius: 4rpx;
}
.hospital-tag.grade {
border: 1rpx solid #2f8af580;
background: #2f8af51a;
color: #037AFF;
}
.hospital-tag.center {
border: 1rpx solid #fb791e80;
background: #ef88391a;
color: #fb791e;
}
.hospital-tag.specialty {
border: 1rpx solid #12b2b380;
background: #12b2b31a;
color: #12b2b3;
}
/* Banner轮播样式 */
.banner-section {
margin: 30rpx 0;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.banner-swiper {
width: 100%;
aspect-ratio: 3 / 1;
border-radius: 16rpx;
/* 兼容不支持 aspect-ratio 的浏览器,使用 calc 计算 3:1 比例 */
height: calc(100vw / 3);
}
.banner-item {
width: 100%;
height: 100%;
position: relative;
}
.banner-image {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.hospital-intro {
display: flex;
/* 解决伪元素height:100%失效的问题 */
margin-bottom: 30rpx;
}
.hospital-text-wrapper {
overflow: hidden;
line-height: 1.6;
position: relative;
/* 使用webkit-line-clamp实现多行文本省略 */
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
/* 标准属性,提升兼容性 */
-webkit-box-orient: vertical;
}
.hospital-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
display: block;
letter-spacing: 0.4rpx;
}
.more-link {
color: #ffb366;
position: absolute;
/* 改为绝对定位 */
right: 0;
bottom: 0;
font-size: 24rpx;
font-weight: 500;
background: #fff;
padding-left: 8rpx;
z-index: 1;
/* 确保按钮在最上层 */
}
.more-link::before {
content: '...';
color: #999;
font-size: 14px;
margin-right: 8rpx;
}
.hospital-links {
display: flex;
justify-content: space-between;
}
.link-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
flex: 1;
}
.link-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.link-text {
font-size: 22rpx;
color: #666;
text-align: center;
}
/* 个人擅长内容 */
.expertise-content {
margin-top: 20rpx;
min-height: 120rpx;
}
.expertise-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.expertise-text.no-data {
color: #999;
}
.intro-content {
margin-top: 0;
min-height: 120rpx;
}
.intro-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
gap: 12rpx;
}
.intro-title-bar {
width: 6rpx;
height: 32rpx;
background: #EF8839;
border-radius: 3rpx;
}
.intro-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.intro-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
display: block;
letter-spacing: 0.4rpx;
}
.intro-text.no-data {
color: #999;
}
/* 底部操作按钮 */
.bottom-actions {
padding: 20rpx 40rpx;
display: flex;
gap: 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.edit-btn {
flex: 1;
height: 80rpx;
background: #f8f9fa;
border: 2rpx solid #e0e0e0;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
}
.edit-icon {
width: 32rpx;
height: 32rpx;
}
.edit-text {
font-size: 26rpx;
color: #666;
}
.share-btn {
flex: 2;
height: 80rpx;
background: #E86B3E;
border: none;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.share-text {
font-size: 28rpx;
color: #fff;
font-weight: bold;
}
/* 便民服务弹窗样式 */
.service-modal-content {
background: white;
border-radius: 20rpx 20rpx 0 0;
width: 100%;
overflow: hidden;
}
.service-modal-header {
position: relative;
padding: 40rpx 40rpx 20rpx;
text-align: center;
border-bottom: 1rpx solid #f0f0f0;
}
.service-modal-close {
position: absolute;
left: 40rpx;
top: 40rpx;
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.service-modal-close:active {
background: #f0f0f0;
}
.service-modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.service-modal-body {
padding: 30rpx 40rpx;
}
.service-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.service-list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 25rpx;
}
.service-list-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.service-list-text.no-data {
color: #999;
font-weight: normal;
}
.service-list-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #666;
}
/* 医院介绍弹窗样式 */
.more-info-modal-content {
background: white;
border-radius: 20rpx 20rpx 0 0;
width: 100%;
overflow: hidden;
}
.more-info-modal-header {
position: relative;
padding: 40rpx 40rpx 20rpx;
text-align: center;
border-bottom: 1rpx solid #f0f0f0;
}
.more-info-modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.more-info-modal-close {
position: absolute;
right: 40rpx;
top: 40rpx;
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.more-info-modal-close:active {
background: #f0f0f0;
}
.more-info-modal-body {
padding: 20rpx 0;
margin-bottom: 20rpx;
max-height: 60vh;
box-sizing: border-box;
width: 100%;
}
.more-info-text {
padding: 0 40rpx;
margin-bottom: 20rpx;
}
.more-info-content {
padding: 0 40rpx;
margin-bottom: 20rpx;
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.more-info-content :deep(p) {
margin-bottom: 20rpx;
}
.more-info-content :deep(img) {
max-width: 100%;
height: auto;
margin: 20rpx 0;
border-radius: 8rpx;
}
.more-info-content :deep(strong) {
font-weight: bold;
color: #333;
}
.no-data-text {
font-size: 28rpx;
color: #999;
text-align: center;
padding: 60rpx 0;
display: block;
}
.more-info-image {
padding: 0 40rpx;
margin: 30rpx 0;
display: flex;
justify-content: center;
align-items: center;
}
.more-info-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
margin-bottom: 20rpx;
}
.more-info-text:last-child {
margin-bottom: 40rpx;
}
.more-info-image-placeholder {
width: 100%;
height: 200rpx;
border: 2rpx solid #e0e0e0;
border-radius: 12rpx;
margin: 30rpx 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-lines {
position: relative;
width: 100%;
height: 100%;
}
.line {
position: absolute;
background: #e0e0e0;
width: 2rpx;
height: 100%;
top: 0;
left: 50%;
transform: translateX(-50%);
}
.line-1 {
transform: translateX(-50%) rotate(45deg);
}
.line-2 {
transform: translateX(-50%) rotate(-45deg);
}
/* 链接弹窗样式 */
.link-modal-content {
background: white;
border-radius: 20rpx 20rpx 0 0;
width: 100%;
overflow: hidden;
}
.link-modal-header {
position: relative;
padding: 40rpx 40rpx 20rpx;
text-align: center;
border-bottom: 1rpx solid #f0f0f0;
}
.link-modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.link-modal-close {
position: absolute;
right: 40rpx;
top: 40rpx;
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.link-modal-close:active {
background: #f0f0f0;
}
.link-modal-body {
padding: 30rpx 40rpx;
text-align: center;
}
.link-modal-desc {
font-size: 26rpx;
color: #666;
line-height: 1.6;
margin-bottom: 30rpx;
display: block;
}
.link-modal-btn {
width: 100%;
height: 80rpx;
background: #ffb366;
color: #fff;
border: none;
border-radius: 16rpx;
font-size: 28rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.link-modal-btn:active {
background: #ffa356;
transform: scale(0.98);
}
.qr-code-placeholder {
margin-bottom: 20rpx;
display: flex;
justify-content: center;
}
.qr-code-image {
width: 360rpx;
height: 360rpx;
border: 2rpx solid #e0e0e0;
border-radius: 12rpx;
overflow: hidden;
}
.qr-code-image image {
width: 100%;
height: 100%;
}
/* 分享弹窗样式 */
.share-modal-content {
background: white;
border-radius: 20rpx 20rpx 0 0;
width: 100%;
overflow: hidden;
}
.share-modal-header {
position: relative;
padding: 40rpx 40rpx 20rpx;
text-align: center;
border-bottom: 1rpx solid #f0f0f0;
}
.share-modal-close {
position: absolute;
left: 40rpx;
top: 40rpx;
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.share-modal-close:active {
background: #f0f0f0;
}
.share-modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.share-modal-body {
padding: 30rpx 40rpx;
}
.share-options {
display: flex;
justify-content: space-around;
gap: 40rpx;
}
.share-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
flex: 1;
}
.share-icon {
width: 80rpx;
height: 80rpx;
border-radius: 12rpx;
}
.share-option-text {
font-size: 26rpx;
color: #333;
text-align: center;
}
/* 无服务时的样式 */
.no-service {
text-align: center;
padding: 40rpx 0;
}
.no-service-text {
font-size: 28rpx;
color: #999;
}
</style>