355 lines
10 KiB
Vue
355 lines
10 KiB
Vue
<!-- =============================================================================
|
||
// 企微IT智能服务台 — H5员工名片卡片组件
|
||
// =============================================================================
|
||
// 说明:当员工提出非IT业务问题时,AI 自动推送此名片卡片。
|
||
// 员工点击「联系TA」按钮可跳转到与联系人的企微单聊。
|
||
//
|
||
// 布局参考原型:docs/04-原型设计/prototypes-原型图/employee-contact-card-demo.html
|
||
// - 头像(44px) / 姓名+性别 / 部门·岗位 / 负责业务(绿色高亮#07C160)
|
||
// - 分机号 / 服务区域 / 「员工名片」标识 / 「联系TA」按钮
|
||
//
|
||
// 交互:
|
||
// - 点击「联系TA」→ wx.invoke('openEnterpriseChat', {userids: contact.wecom_userid})
|
||
// - userid 为空或 SDK 未就绪 → Toast 提示
|
||
// ============================================================================= -->
|
||
|
||
<template>
|
||
<div class="contact-card">
|
||
<!-- 卡片头部:头像 + 姓名 + 部门岗位 + 名片标识 -->
|
||
<div class="contact-card__header">
|
||
<!-- 头像:有URL用图片,无URL用姓名首字渐变色块 -->
|
||
<div class="contact-card__avatar" :class="avatarGradient">
|
||
<img v-if="contact.avatar_url" :src="contact.avatar_url" alt="" class="contact-card__avatar-img" />
|
||
<span v-else>{{ avatarText }}</span>
|
||
</div>
|
||
|
||
<!-- 姓名 + 部门岗位 -->
|
||
<div class="contact-card__name-row">
|
||
<div class="contact-card__name">
|
||
{{ contact.name }}
|
||
<span v-if="contact.gender" class="contact-card__gender" :class="genderClass">{{ genderIcon }}</span>
|
||
</div>
|
||
<div class="contact-card__dept">{{ contact.department }} · {{ contact.position }}</div>
|
||
</div>
|
||
|
||
<!-- 名片标识(kfid 服务模式显示「服务窗口」) -->
|
||
<span class="contact-card__badge">{{ contact.service_url ? '服务窗口' : '员工名片' }}</span>
|
||
</div>
|
||
|
||
<!-- 卡片信息区:负责业务/服务描述(绿色高亮) / 分机号 / 服务区域 -->
|
||
<div class="contact-card__body">
|
||
<div class="contact-card__info-item">
|
||
<span class="contact-card__info-label">{{ contact.service_url ? '服务内容' : '负责业务' }}</span>
|
||
<span class="contact-card__info-value contact-card__info-value--highlight">
|
||
{{ contact.responsibility }}
|
||
</span>
|
||
</div>
|
||
<div v-if="contact.extension" class="contact-card__info-item">
|
||
<span class="contact-card__info-label">分机号</span>
|
||
<span class="contact-card__info-value">{{ contact.extension }}</span>
|
||
</div>
|
||
<div v-if="contact.service_area" class="contact-card__info-item">
|
||
<span class="contact-card__info-label">{{ serviceAreaLabel }}</span>
|
||
<span class="contact-card__info-value">{{ contact.service_area }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 卡片底部:联系TA按钮 -->
|
||
<div class="contact-card__footer">
|
||
<button class="contact-card__action-btn" @click="handleContact">
|
||
<span class="contact-card__action-icon">💬</span>
|
||
<span>{{ contact.service_url ? '立即咨询' : '联系TA' }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
/**
|
||
* ContactCard 员工名片卡片组件
|
||
*
|
||
* 接收联系人信息,渲染名片卡片。
|
||
* 点击「联系TA」调用企微 JS-SDK 的 openEnterpriseChat 打开单聊。
|
||
*/
|
||
|
||
import { computed } from 'vue'
|
||
import { showToast } from 'vant'
|
||
|
||
/** 联系人信息接口(与后端 extra_data.contact 结构一致) */
|
||
export interface ContactInfo {
|
||
id: number
|
||
name: string
|
||
gender: string
|
||
department: string
|
||
position: string
|
||
responsibility: string
|
||
extension: string
|
||
service_area: string
|
||
wecom_userid: string
|
||
avatar_url: string
|
||
business_category: string
|
||
/** v4.0 kfid 服务模式:企微客服窗口链接(存在时点击打开链接而非 openEnterpriseChat) */
|
||
service_url?: string
|
||
}
|
||
|
||
const props = defineProps<{
|
||
/** 联系人信息 */
|
||
contact: ContactInfo
|
||
}>()
|
||
|
||
// ============================================================================
|
||
// 计算属性
|
||
// ============================================================================
|
||
|
||
/** 头像文字(姓名首字) */
|
||
const avatarText = computed(() => {
|
||
return props.contact.name ? props.contact.name.charAt(0) : '?'
|
||
})
|
||
|
||
/** 头像渐变色块样式类(根据姓名首字 hash 分配不同渐变) */
|
||
const avatarGradient = computed(() => {
|
||
const gradients = [
|
||
'gradient-1', 'gradient-2', 'gradient-3', 'gradient-4', 'gradient-5',
|
||
]
|
||
if (!props.contact.name) return gradients[0]
|
||
const charCode = props.contact.name.charCodeAt(0)
|
||
return gradients[charCode % gradients.length]
|
||
})
|
||
|
||
/** 性别图标 */
|
||
const genderIcon = computed(() => {
|
||
return props.contact.gender === 'female' ? '♀' : '♂'
|
||
})
|
||
|
||
/** 性别样式类 */
|
||
const genderClass = computed(() => {
|
||
return props.contact.gender === 'female' ? 'gender-female' : 'gender-male'
|
||
})
|
||
|
||
/** 服务区域标签文案(物业类用「办公地点」,其他用「服务区域」) */
|
||
const serviceAreaLabel = computed(() => {
|
||
return props.contact.business_category === '行政-物业' ? '办公地点' : '服务区域'
|
||
})
|
||
|
||
// ============================================================================
|
||
// 交互方法
|
||
// ============================================================================
|
||
|
||
/**
|
||
* 点击「联系TA」按钮,调用企微 JS-SDK 打开单聊。
|
||
*
|
||
* 容错处理:
|
||
* - wecom_userid 为空 → Toast 提示
|
||
* - wx SDK 未就绪 → Toast 提示
|
||
* - openEnterpriseChat 调用失败 → Toast 提示
|
||
*/
|
||
function handleContact(): void {
|
||
// v4.0 kfid 服务模式:有 service_url 时直接打开企微客服会话链接
|
||
if (props.contact.service_url) {
|
||
// 企微 H5 环境:window.open 打开 kfid 链接会唤起客服会话窗口
|
||
window.open(props.contact.service_url, '_blank')
|
||
return
|
||
}
|
||
|
||
const userid = props.contact.wecom_userid
|
||
|
||
// 1. 检查 userid 是否有效
|
||
if (!userid) {
|
||
showToast('暂时无法发起聊天,请联系管理员')
|
||
return
|
||
}
|
||
|
||
// 2. 检查企微 JS-SDK 是否可用
|
||
const wx = (window as any).wx
|
||
if (!wx || !wx.invoke) {
|
||
showToast('暂时无法发起聊天,请联系管理员')
|
||
return
|
||
}
|
||
|
||
// 3. 调用 openEnterpriseChat 打开与联系人的企微单聊
|
||
try {
|
||
wx.invoke(
|
||
'openEnterpriseChat',
|
||
{
|
||
userids: userid,
|
||
chatTitle: props.contact.name,
|
||
},
|
||
(res: any) => {
|
||
if (res && res.err_msg && res.err_msg !== 'openEnterpriseChat:ok') {
|
||
console.error('[ContactCard] openEnterpriseChat 失败:', res.err_msg)
|
||
showToast('暂时无法发起聊天,请联系管理员')
|
||
}
|
||
}
|
||
)
|
||
} catch (err) {
|
||
console.error('[ContactCard] openEnterpriseChat 调用异常:', err)
|
||
showToast('暂时无法发起聊天,请联系管理员')
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* ============================================================================
|
||
// 名片卡片容器
|
||
// ============================================================================ */
|
||
.contact-card {
|
||
width: 260px;
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
border-top-left-radius: 2px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
/* ============================================================================
|
||
// 卡片头部
|
||
// ============================================================================ */
|
||
.contact-card__header {
|
||
padding: 14px 16px 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
/* 头像 */
|
||
.contact-card__avatar {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 6px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
color: #fff;
|
||
font-weight: 600;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.contact-card__avatar-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* 头像渐变色块(无头像时用姓名首字) */
|
||
.gradient-1 { background: linear-gradient(135deg, #667eea, #764ba2); }
|
||
.gradient-2 { background: linear-gradient(135deg, #f093fb, #f5576c); }
|
||
.gradient-3 { background: linear-gradient(135deg, #4facfe, #00f2fe); }
|
||
.gradient-4 { background: linear-gradient(135deg, #43e97b, #38f9d7); }
|
||
.gradient-5 { background: linear-gradient(135deg, #fa709a, #fee140); }
|
||
|
||
/* 姓名行 */
|
||
.contact-card__name-row {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.contact-card__name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.contact-card__gender {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.gender-male { color: #4facfe; }
|
||
.gender-female { color: #f5576c; }
|
||
|
||
.contact-card__dept {
|
||
font-size: 13px;
|
||
color: #888;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 名片标识 */
|
||
.contact-card__badge {
|
||
display: inline-block;
|
||
font-size: 11px;
|
||
padding: 1px 6px;
|
||
border-radius: 3px;
|
||
background: #f0f0f0;
|
||
color: #888;
|
||
flex-shrink: 0;
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* ============================================================================
|
||
// 卡片信息区
|
||
// ============================================================================ */
|
||
.contact-card__body {
|
||
padding: 10px 16px;
|
||
}
|
||
|
||
.contact-card__info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 0;
|
||
font-size: 13px;
|
||
color: #555;
|
||
}
|
||
|
||
.contact-card__info-label {
|
||
color: #aaa;
|
||
width: 56px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.contact-card__info-value {
|
||
color: #333;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* 负责业务:绿色高亮 #07C160 */
|
||
.contact-card__info-value--highlight {
|
||
color: #07C160;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ============================================================================
|
||
// 卡片底部按钮
|
||
// ============================================================================ */
|
||
.contact-card__footer {
|
||
padding: 0 16px 12px;
|
||
}
|
||
|
||
.contact-card__action-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
width: 100%;
|
||
height: 36px;
|
||
background: #07C160;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
padding: 0;
|
||
}
|
||
|
||
.contact-card__action-btn:active {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.contact-card__action-icon {
|
||
font-size: 16px;
|
||
}
|
||
</style>
|