ykt-wxapp/components/archive-detail/customer-profile-tab.vue

411 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/customer-detail/customer-profile.vue -->
<view class="wrap">
<view class="sub-tabs">
<view
v-for="t in anchors"
:key="t.value"
class="sub-tab"
:class="{ active: activeAnchor === t.value }"
@click="scrollToAnchor(t.value)"
>
{{ t.label }}
</view>
</view>
<view class="card">
<view id="anchor-base" class="section-title" @click="startEdit">
<text>基本信息</text>
<image class="pen" src="/static/icons/icon-pen.svg" />
</view>
<view class="rows">
<view class="row">
<view class="label">姓名</view>
<view v-if="!editing" class="val">{{ data.name || '-' }}</view>
<input v-else v-model="draft.name" class="input" placeholder="请输入姓名" />
</view>
<view class="row">
<view class="label">性别</view>
<view v-if="!editing" class="val">{{ data.sex || '-' }}</view>
<picker v-else mode="selector" :range="sexOptions" @change="pickSex">
<view class="picker">{{ draft.sex || '请选择' }}</view>
</picker>
</view>
<view class="row">
<view class="label">年龄</view>
<view v-if="!editing" class="val">{{ data.age || '-' }}</view>
<input v-else v-model="draft.age" class="input" type="number" placeholder="请输入年龄" />
</view>
<view class="row">
<view class="label">联系电话</view>
<view v-if="!editing" class="val link" @click="call(data.mobile)">{{ data.mobile || '-' }}</view>
<input v-else v-model="draft.mobile" class="input" type="number" placeholder="请输入联系电话" />
</view>
</view>
</view>
<view class="card">
<view id="anchor-internal" class="section-title" @click="startEdit">
<text>内部信息</text>
<image class="pen" src="/static/icons/icon-pen.svg" />
</view>
<view class="rows">
<view class="row" @click="openTransferRecord">
<view class="label">院内来源</view>
<view class="val link">
{{ data.creator || '点击查看' }}
<uni-icons type="arrowright" size="14" color="#4f6ef7" />
</view>
</view>
<view class="row">
<view class="label">备注</view>
<view v-if="!editing" class="val">{{ data.notes || '-' }}</view>
<input v-else v-model="draft.notes" class="input" placeholder="请输入备注" />
</view>
</view>
</view>
<view class="card">
<view id="anchor-behavior" class="section-title">
行为画像
</view>
<view class="rows">
<view class="row">
<view class="label">门诊号</view>
<view class="val">{{ data.outpatientNo || '-' }}</view>
</view>
<view class="row">
<view class="label">住院号</view>
<view class="val">{{ data.inpatientNo || '-' }}</view>
</view>
<view class="row">
<view class="label">病案号</view>
<view class="val">{{ data.medicalRecordNo || '-' }}</view>
</view>
</view>
</view>
<view v-if="editing" class="bottom-bar" :style="{ bottom: `${floatingBottom}px` }">
<button class="btn plain" @click="cancel">取消</button>
<button class="btn primary" @click="save">保存</button>
</view>
<uni-popup ref="transferPopupRef" type="bottom" :mask-click="true">
<view class="popup">
<view class="popup-title">
<view class="popup-title-text">院内流转记录mock</view>
<view class="popup-close" @click="closeTransferRecord">
<uni-icons type="closeempty" size="18" color="#666" />
</view>
</view>
<scroll-view scroll-y class="popup-body">
<view class="timeline">
<view class="line"></view>
<view v-for="r in transferRecords" :key="r._id" class="item">
<view class="dot"></view>
<view class="content">
<view class="time">{{ r.time }}</view>
<view class="card2">
<view class="trow"><text class="tlabel">转入团队:</text>{{ r.team }}</view>
<view class="trow"><text class="tlabel">转入方式:</text>{{ r.type }}</view>
<view class="trow"><text class="tlabel">操作人:</text>{{ r.user }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { reactive, ref, watch } from 'vue';
import dayjs from 'dayjs';
const props = defineProps({
data: { type: Object, default: () => ({}) },
floatingBottom: { type: Number, default: 16 },
});
const emit = defineEmits(['save']);
const anchors = [
{ label: '基本信息', value: 'base' },
{ label: '内部信息', value: 'internal' },
{ label: '行为画像', value: 'behavior' },
];
const activeAnchor = ref('base');
const editing = ref(false);
const sexOptions = ['男', '女'];
const draft = reactive({
name: '',
sex: '',
age: '',
mobile: '',
notes: '',
});
watch(
() => props.data,
() => {
if (!editing.value) syncDraft();
},
{ deep: true }
);
function syncDraft() {
draft.name = props.data?.name || '';
draft.sex = props.data?.sex || '';
draft.age = props.data?.age || '';
draft.mobile = props.data?.mobile || '';
draft.notes = props.data?.notes || '';
}
function startEdit() {
if (editing.value) return;
editing.value = true;
syncDraft();
}
function cancel() {
editing.value = false;
}
function save() {
emit('save', {
name: draft.name,
sex: draft.sex,
age: draft.age,
mobile: draft.mobile,
notes: draft.notes,
});
editing.value = false;
uni.showToast({ title: '保存成功', icon: 'success' });
}
function pickSex(e) {
draft.sex = sexOptions[e.detail.value] || '';
}
function call(mobile) {
if (!mobile) return;
uni.makePhoneCall({ phoneNumber: String(mobile) });
}
function scrollToAnchor(key) {
activeAnchor.value = key;
const selector = `#anchor-${key}`;
const query = uni.createSelectorQuery();
query.select(selector).boundingClientRect();
query.selectViewport().scrollOffset();
query.exec((res) => {
const rect = res[0];
const scroll = res[1];
if (!rect || !scroll) return;
uni.pageScrollTo({ scrollTop: rect.top + scroll.scrollTop - 60, duration: 200 });
});
}
const transferPopupRef = ref(null);
const transferRecords = ref([]);
function openTransferRecord() {
transferRecords.value = [
{ _id: 't1', time: dayjs().subtract(120, 'day').format('YYYY-MM-DD HH:mm'), team: '口腔一科(示例)', type: '系统建档', user: '系统' },
{ _id: 't2', time: dayjs().subtract(30, 'day').format('YYYY-MM-DD HH:mm'), team: '正畸团队(示例)', type: '团队流转', user: '管理员A' },
];
transferPopupRef.value?.open?.();
}
function closeTransferRecord() {
transferPopupRef.value?.close?.();
}
</script>
<style scoped>
.wrap {
padding: 12px 0 96px;
}
.sub-tabs {
display: flex;
background: #f5f6f8;
border-bottom: 1px solid #f2f2f2;
}
.sub-tab {
flex: 1;
text-align: center;
height: 40px;
line-height: 40px;
font-size: 13px;
color: #666;
}
.sub-tab.active {
color: #4f6ef7;
font-weight: 600;
}
.card {
background: #fff;
margin-top: 10px;
}
.section-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
font-size: 15px;
font-weight: 600;
color: #333;
border-bottom: 1px solid #f2f2f2;
}
.pen {
width: 18px;
height: 18px;
}
.rows {
padding: 2px 14px;
}
.row {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f6f6f6;
}
.row:last-child {
border-bottom: none;
}
.label {
width: 90px;
font-size: 14px;
color: #666;
}
.val {
flex: 1;
text-align: right;
font-size: 14px;
color: #333;
}
.val.link {
color: #4f6ef7;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 6px;
}
.input,
.picker {
flex: 1;
text-align: right;
font-size: 14px;
color: #333;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
background: #fff;
padding: 12px 14px;
display: flex;
gap: 12px;
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
z-index: 30;
}
.btn {
flex: 1;
height: 44px;
line-height: 44px;
border-radius: 6px;
font-size: 15px;
}
.btn::after {
border: none;
}
.btn.plain {
background: #fff;
color: #4f6ef7;
border: 1px solid #4f6ef7;
}
.btn.primary {
background: #4f6ef7;
color: #fff;
}
.popup {
background: #fff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
overflow: hidden;
}
.popup-title {
position: relative;
padding: 14px;
border-bottom: 1px solid #f0f0f0;
}
.popup-title-text {
text-align: center;
font-size: 16px;
font-weight: 600;
color: #333;
}
.popup-close {
position: absolute;
right: 12px;
top: 0;
height: 100%;
display: flex;
align-items: center;
}
.popup-body {
max-height: 70vh;
}
.timeline {
position: relative;
padding: 14px 14px 18px;
}
.line {
position: absolute;
left: 22px;
top: 18px;
bottom: 18px;
width: 2px;
background: #4f6ef7;
}
.item {
position: relative;
padding-left: 32px;
margin-bottom: 14px;
}
.dot {
position: absolute;
left: 16px;
top: 6px;
width: 10px;
height: 10px;
border-radius: 50%;
background: #4f6ef7;
}
.time {
font-size: 12px;
color: #999;
margin-bottom: 8px;
}
.card2 {
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 10px;
}
.trow {
font-size: 14px;
color: #333;
line-height: 20px;
}
.tlabel {
color: #666;
margin-right: 6px;
}
</style>