This commit is contained in:
huxuejian 2026-02-11 17:11:24 +08:00
commit 609fc8cd18
15 changed files with 546 additions and 203 deletions

View File

@ -1,5 +1,13 @@
<template>
<view class="page">
<scroll-view
scroll-y
class="page-scroll"
:scroll-top="pageScrollTop"
:scroll-with-animation="true"
:lower-threshold="80"
@scrolltolower="onScrollToLower"
>
<view class="card">
<view class="header">
<view class="header-main">
@ -83,6 +91,7 @@
</view>
</view>
<view id="tabs-anchor" class="tabs-anchor"></view>
<view class="tabs">
<view
v-for="t in tabs"
@ -119,6 +128,9 @@
/>
</view>
<view class="scroll-spacer" />
</scroll-view>
<view v-if="showGoChat" class="footer">
<button class="bind-btn" @click="goChat">
<uni-icons type="chat-filled" size="18" color="#fff" />
@ -213,13 +225,13 @@
<script setup>
import { computed, getCurrentInstance, nextTick, ref } from 'vue';
import { onLoad, onPullDownRefresh, onReachBottom, onReady, onShow } from '@dcloudio/uni-app';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import dayjs from 'dayjs';
import HealthProfileTab from './components/archive-detail/health-profile-tab.vue';
import ServiceInfoTab from './components/archive-detail/service-info-tab.vue';
import FollowUpManageTab from './components/archive-detail/follow-up-manage-tab.vue';
import HealthProfileTab from '@/pages/case/components/archive-detail/health-profile-tab.vue';
import ServiceInfoTab from '@/pages/case/components/archive-detail/service-info-tab.vue';
import FollowUpManageTab from '@/pages/case/components/archive-detail/follow-up-manage-tab.vue';
import api from '@/utils/api';
import useAccountStore from '@/store/account';
import { hideLoading, loading, toast } from '@/utils/widget';
@ -238,15 +250,32 @@ const currentTab = ref('visitRecord');
const reachBottomTime = ref(0);
const archiveId = ref('');
const fromChat = ref(false);
const tabsScrollTop = ref(0);
const pageScrollTop = ref(0);
const instanceProxy = getCurrentInstance()?.proxy;
function scrollTabsToTop() {
const q = instanceProxy ? uni.createSelectorQuery().in(instanceProxy) : uni.createSelectorQuery();
q.select('.page-scroll').boundingClientRect();
q.select('#tabs-anchor').boundingClientRect();
q.select('.page-scroll').scrollOffset();
q.exec((res) => {
const scrollRect = res && res[0];
const anchorRect = res && res[1];
const viewport = res && res[2];
if (!scrollRect || !anchorRect || !viewport) return;
const delta = Number(anchorRect.top || 0) - Number(scrollRect.top || 0);
const target = Number(viewport.scrollTop || 0) + delta;
// scrollTop
if (pageScrollTop.value === target) pageScrollTop.value = target + 1;
pageScrollTop.value = target;
});
}
function switchTab(key) {
currentTab.value = key;
// tab tab
nextTick(() => {
uni.pageScrollTo({ scrollTop: tabsScrollTop.value || 0, duration: 300 });
});
nextTick(() => scrollTabsToTop());
}
const archive = ref({
@ -368,8 +397,6 @@ async function fetchArchive() {
}
}
saveToStorage();
// tabs
nextTick(() => setTimeout(measureTabsTop, 30));
} catch (e) {
toast('获取档案失败');
} finally {
@ -477,7 +504,6 @@ async function updateArchive(patch) {
const createTeamId = team?.teamId ? String(team.teamId) : '';
const createTeamName = team?.name ? String(team.name) : '';
if (!userId || !corpId) {
toast('缺少登录信息,请先完成登录');
return false;
}
@ -536,22 +562,6 @@ onLoad((options) => {
fetchArchive();
});
function measureTabsTop() {
const q = instanceProxy ? uni.createSelectorQuery().in(instanceProxy) : uni.createSelectorQuery();
q.select('.tabs').boundingClientRect();
q.selectViewport().scrollOffset();
q.exec((res) => {
const rect = res && res[0];
const viewport = res && res[1];
if (!rect || !viewport) return;
tabsScrollTop.value = (rect.top || 0) + (viewport.scrollTop || 0);
});
}
onReady(() => {
setTimeout(measureTabsTop, 30);
});
onShow(() => {
const cached = uni.getStorageSync(STORAGE_KEY);
const cachedId = cached && typeof cached === 'object' ? String(cached._id || cached.id || '') : '';
@ -563,21 +573,12 @@ onShow(() => {
};
chatGroupId.value = normalizeGroupId(archive.value.chatGroupId || '');
}
setTimeout(measureTabsTop, 30);
fetchArchive();
});
onPullDownRefresh(() => {
uni.pageScrollTo({
scrollTop: tabsScrollTop.value || 0,
duration: 0,
});
setTimeout(() => uni.stopPullDownRefresh(), 150);
});
onReachBottom(() => {
function onScrollToLower() {
reachBottomTime.value = Date.now();
});
}
const sexOrAge = computed(() => {
const sex = archive.value.sex ? String(archive.value.sex) : '';
@ -919,7 +920,6 @@ const saveAddGroup = async () => {
const teamId = team?.teamId ? String(team.teamId) : '';
const creator = getUserId();
if (!corpId || !teamId || !creator) {
toast('缺少用户/团队信息');
return;
}
@ -947,13 +947,21 @@ const saveAddGroup = async () => {
<style lang="scss" scoped>
.page {
min-height: 100vh;
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f6f8;
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
overflow: hidden;
}
.page-scroll {
flex: 1;
min-height: 0;
}
.card {
background: #fff;
margin-bottom: 20rpx;
}
.header {
@ -1140,7 +1148,6 @@ const saveAddGroup = async () => {
}
.tabs {
margin-top: 20rpx;
background: #fff;
display: flex;
border-top: 2rpx solid #f2f2f2;
@ -1150,6 +1157,10 @@ const saveAddGroup = async () => {
z-index: 30;
}
.tabs-anchor {
height: 1px;
}
.tab {
flex: 1;
text-align: center;
@ -1179,6 +1190,12 @@ const saveAddGroup = async () => {
.content {
padding: 0;
flex: 1;
min-height: 0;
}
.scroll-spacer {
height: calc(180rpx + env(safe-area-inset-bottom));
}
.empty {

View File

@ -308,7 +308,6 @@ async function savePatch(patch) {
const createTeamId = team?.teamId ? String(team.teamId) : '';
const createTeamName = team?.name ? String(team.name) : '';
if (!archiveId.value || !userId || !corpId) {
toast('缺少用户/团队信息,请先完成登录与个人信息');
return;
}

View File

@ -115,7 +115,6 @@ async function transferToCustomerPool() {
const currentTeamId = getCurrentTeamId();
const creatorUserId = getUserId();
if (!corpId || !currentTeamId || !creatorUserId) {
toast('缺少用户/团队信息');
return false;
}
@ -147,7 +146,6 @@ async function transferToOtherTeam() {
const currentTeamId = getCurrentTeamId();
const creatorUserId = getUserId();
if (!corpId || !currentTeamId || !creatorUserId) {
toast('缺少用户/团队信息');
return false;
}

View File

@ -379,6 +379,214 @@ function resolveUserName(userId) {
return String(map[id] || "") || id;
}
function normalizeName(v) {
const s = v === 0 ? "0" : v ? String(v) : "";
const trimmed = s.trim();
if (!trimmed) return "";
if (["-", "—", "--"].includes(trimmed)) return "";
return trimmed;
}
function getExecuteTeamId(todo) {
const row = todo && typeof todo === "object" ? todo : {};
return String(
row.executeTeamId ||
row.executeTeamID ||
row.teamId ||
row.teamID ||
row.executeTeam?._id ||
row.executeTeam?.teamId ||
""
)
.trim();
}
const loadedTeamMemberIds = new Set();
const teamMemberInflight = new Map();
async function loadTeamMembers(teamId) {
const tid = String(teamId || "").trim();
if (!tid) return;
if (loadedTeamMemberIds.has(tid)) return;
const existingInflight = teamMemberInflight.get(tid);
if (existingInflight) return existingInflight;
const inflight = (async () => {
await ensureDoctor();
const corpId = getCorpId();
if (!corpId) return;
try {
const fallback = await api("getTeamData", { corpId, teamId: tid }, false);
if (!fallback?.success) return;
const t = fallback?.data && typeof fallback.data === "object" ? fallback.data : {};
const members = Array.isArray(t.memberList) ? t.memberList : [];
const map = members.reduce((acc, m) => {
if (typeof m === "string") {
const id = String(m || "");
if (id) acc[id] = id;
return acc;
}
const uid = String(m?.userid || m?.userId || m?.corpUserId || m?._id || m?.id || "").trim();
if (!uid) return acc;
const display = String(m?.anotherName || m?.name || m?.userid || m?.userId || "").trim();
acc[uid] = display || uid;
return acc;
}, {});
if (Object.keys(map).length) userNameMap.value = { ...(userNameMap.value || {}), ...map };
// avatars
try {
const res = await api("getTeamMemberAvatarsAndName", { corpId, teamId: tid }, false);
if (res?.success && res?.data && typeof res.data === "object") {
const raw = res.data;
const patch = Object.keys(raw).reduce((acc, uid) => {
const id = String(uid || "").trim();
if (!id) return acc;
const existing = userNameMap.value?.[id];
if (existing && existing !== id) return acc;
const name = String(raw?.[uid]?.name || raw?.[uid]?.anotherName || "").trim();
if (!name || name === id) return acc;
acc[id] = name;
return acc;
}, {});
if (Object.keys(patch).length) userNameMap.value = { ...(userNameMap.value || {}), ...patch };
}
} catch {
// ignore
}
} finally {
loadedTeamMemberIds.add(tid);
}
})().finally(() => {
teamMemberInflight.delete(tid);
});
teamMemberInflight.set(tid, inflight);
return inflight;
}
let corpMemberBatchInflight = null;
async function batchLoadCorpMembers(userIds) {
const ids = Array.isArray(userIds)
? userIds.map((v) => String(v || "").trim()).filter(Boolean)
: [];
if (!ids.length) return;
const uniq = Array.from(new Set(ids));
const unknown = uniq.filter((id) => {
const existing = userNameMap.value?.[id];
return !existing || existing === id;
});
if (!unknown.length) return;
if (corpMemberBatchInflight) return corpMemberBatchInflight;
await ensureDoctor();
const corpId = getCorpId();
if (!corpId) return;
corpMemberBatchInflight = (async () => {
try {
const res = await api(
"getCorpMember",
{
page: 1,
pageSize: Math.min(Math.max(unknown.length, 10), 500),
params: {
corpId,
memberList: unknown,
},
},
false
);
if (!res?.success) return;
const rows = Array.isArray(res?.data)
? res.data
: Array.isArray(res?.data?.data)
? res.data.data
: [];
if (!rows.length) return;
const patch = rows.reduce((acc, m) => {
const id = String(m?.userid || m?.userId || m?.corpUserId || "").trim();
if (!id) return acc;
const existing = userNameMap.value?.[id];
if (existing && existing !== id) return acc;
const display = String(m?.anotherName || m?.name || "").trim();
if (!display || display === id) return acc;
acc[id] = display;
return acc;
}, {});
if (Object.keys(patch).length) userNameMap.value = { ...(userNameMap.value || {}), ...patch };
} catch {
// ignore
}
})().finally(() => {
corpMemberBatchInflight = null;
});
return corpMemberBatchInflight;
}
let ensureNamesInflight = null;
async function ensureTodoNames(todos) {
if (ensureNamesInflight) return ensureNamesInflight;
const rows = Array.isArray(todos) ? todos : [];
if (!rows.length) return;
const teamIds = Array.from(
new Set(
rows
.map((t) => getExecuteTeamId(t) || getCurrentTeamId())
.map((v) => String(v || "").trim())
.filter(Boolean)
)
);
const unknownUserIds = Array.from(
new Set(
rows
.flatMap((t) => [t?.executorUserId, t?.creatorUserId])
.map((v) => String(v || "").trim())
.filter(Boolean)
.filter((id) => {
const existing = userNameMap.value?.[id];
return !existing || existing === id;
})
)
);
ensureNamesInflight = (async () => {
// corp
const limit = 3;
let idx = 0;
const workers = Array.from({ length: Math.min(limit, teamIds.length || 1) }, async () => {
while (idx < teamIds.length) {
const tid = teamIds[idx++];
try {
await loadTeamMembers(tid);
} catch {
// ignore
}
}
});
await Promise.allSettled(workers);
await batchLoadCorpMembers(unknownUserIds);
//
list.value = (Array.isArray(list.value) ? list.value : []).map((t) => ({
...t,
executorName: normalizeName(t?.executorName) || resolveUserName(t?.executorUserId),
creatorName: normalizeName(t?.creatorName) || resolveUserName(t?.creatorUserId),
}));
})().finally(() => {
ensureNamesInflight = null;
});
return ensureNamesInflight;
}
function refreshChatRoom() {
// chatGroupId
const fromArchive =
@ -460,7 +668,6 @@ async function getMore() {
const corpId = getCorpId();
const userId = getUserId();
if (!corpId) {
toast("缺少 corpId请先完成登录/团队选择");
return;
}
@ -498,6 +705,9 @@ async function getMore() {
pages.value = Math.ceil(total.value / pageSize) || 0;
list.value = page.value === 1 ? next : [...list.value, ...next];
page.value += 1;
// executor/creator
void ensureTodoNames(list.value);
} catch (e) {
console.error("getCustomerTodos failed:", e);
toast("获取回访任务失败");
@ -506,6 +716,7 @@ async function getMore() {
}
}
function toggleMy() {
query.isMy = !query.isMy;
resetList();
@ -1248,6 +1459,7 @@ watch(
font-size: 13px;
}
.fab {
position: fixed;
right: 16px;

View File

@ -592,6 +592,7 @@ watch(
background: #f5f6f8;
border-bottom: 2rpx solid #f2f2f2;
}
.filter-pill {
background: #fff;
border: 2rpx solid #e6e6e6;
@ -638,6 +639,7 @@ watch(
.list {
padding: 0 28rpx;
}
.card {
background: #fff;
border-radius: 20rpx;

View File

@ -725,6 +725,7 @@ watch(
align-items: center;
gap: 24rpx;
}
.filter-item {
display: block;
flex: 1;
@ -872,6 +873,7 @@ watch(
font-size: 26rpx;
}
.fab {
position: fixed;
right: 32rpx;

View File

@ -1,5 +1,6 @@
<template>
<view class="followup-task-page">
<scroll-view scroll-y class="content" :lower-threshold="80" @scrolltolower="onScrollToLower">
<!-- 回访任务列表组件 -->
<FollowUpManageTab
:data="patientData"
@ -8,6 +9,8 @@
:floatingBottom="0"
:fromChat="true"
/>
<view class="scroll-spacer" />
</scroll-view>
</view>
</template>
@ -30,6 +33,10 @@ onLoad((options) => {
const handleBack = () => {
uni.navigateBack();
};
function onScrollToLower() {
reachBottomTime.value = Date.now();
}
</script>
<style scoped>
@ -40,6 +47,15 @@ const handleBack = () => {
background: #f5f6f8;
}
.content {
flex: 1;
min-height: 0;
}
.scroll-spacer {
height: calc(120px + env(safe-area-inset-bottom));
}
.nav-bar {
display: flex;
align-items: center;

View File

@ -1,7 +1,7 @@
<template>
<view class="page">
<view class="body">
<scroll-view scroll-y class="scroll">
<scroll-view scroll-y class="scroll" :style="{ height: scrollHeight + 'px' }">
<view class="form-wrap">
<form-template ref="formRef" :items="baseItems" :form="form" :rule="rules" @change="onChange" />
</view>
@ -43,6 +43,7 @@ const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
const formRef = ref(null);
const form = reactive({});
const baseItems = ref([]);
const scrollHeight = ref(0);
const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
@ -191,6 +192,9 @@ const rules = {
};
onLoad(async () => {
// scroll-view /
scrollHeight.value = Number(uni.getSystemInfoSync()?.windowHeight || 0) || 0;
const cached = uni.getStorageSync(STORAGE_KEY);
if (cached && typeof cached === 'object') {
Object.assign(form, cached);
@ -260,18 +264,19 @@ function getAgeFromBirthday(birthday) {
background: #f6f6f6;
display: flex;
flex-direction: column;
overflow: hidden;
}
.body {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.scroll {
flex: 1;
min-height: 0;
height: 0;
}
.form-wrap {

View File

@ -396,6 +396,26 @@ function buildPayload(base, inner) {
payload.customerSource = [payload.customerSource];
}
const creatorId = String(payload.creator || '').trim();
const teamIdStr = String(payload.teamId || '').trim();
const rawPersonResponsibles = payload.personResponsibles;
const personResponsiblesList = Array.isArray(rawPersonResponsibles)
? rawPersonResponsibles
: rawPersonResponsibles && typeof rawPersonResponsibles === 'object'
? [rawPersonResponsibles]
: [];
const normalizedPersonResponsibles = personResponsiblesList
.map((i) => ({
corpUserId: String(i?.corpUserId || i?.userId || creatorId || '').trim(),
teamId: String(i?.teamId || teamIdStr || '').trim(),
}))
.filter((i) => i.corpUserId && i.teamId);
if (normalizedPersonResponsibles.length) {
payload.personResponsibles = normalizedPersonResponsibles;
} else if (creatorId && teamIdStr) {
payload.personResponsibles = [{ corpUserId: creatorId, teamId: teamIdStr }];
}
return { payload, team };
}
@ -421,7 +441,6 @@ async function save() {
const { payload, team } = buildPayload(base, form);
if (!payload.corpId || !payload.teamId || !payload.creator) {
toast('缺少用户/团队信息,请先完成登录与个人信息');
return;
}

View File

@ -233,7 +233,6 @@ const doSearch = useDebounce(async () => {
const userId = getUserId();
const { corpId, teamId } = getTeamContext();
if (!corpId || !teamId || !userId) {
toast('缺少用户/团队信息,请先完成登录与个人信息');
return;
}

View File

@ -1090,7 +1090,6 @@ async function loadTeams(opts = {}) {
const corpId = String(account.value?.corpId || doctorInfo.value?.corpId || '') || '';
if (!corpId || !userId) {
toast('缺少用户信息,请先完善个人信息');
return;
}
@ -1356,35 +1355,70 @@ const toggleBatchMode = withInfo(() => {
selectedItems.value = [];
});
const handleCreate = withInfo(() => {
const handleCreate = withInfo(async () => {
if (checkBatchMode()) return;
const rawMax = doctorInfo.value?.maxCustomerArchive;
const hasMaxField = rawMax !== undefined && rawMax !== null && String(rawMax).trim() !== '';
const maxCustomerArchive = hasMaxField ? Number(rawMax) : NaN;
// maxCustomerArchive
// -1 = 沿10/100
if (hasMaxField && Number.isFinite(maxCustomerArchive)) {
if (maxCustomerArchive !== -1 && managedArchiveCountAllTeams.value >= maxCustomerArchive) {
uni.showModal({
title: '提示',
content: `当前管理档案数已达上限 ${maxCustomerArchive} 个,无法继续新增。如需提升档案管理数,请联系客服处理。`,
cancelText: '知道了',
confirmText: '添加客服',
// 沿
// - 10toast
// - 10
// - 100
//
let total = null;
let limit = null;
let unlimited = false;
try {
const corpId = getCorpId();
const userId = getUserId();
if (corpId && userId) {
const res = await api('doctorCreatedTeamsCustomerLimitation', { corpId, userId }, false);
if (res?.success) {
const count = Number(res?.count ?? res?.data?.count);
if (Number.isFinite(count)) total = count;
const rawLimit = Number(res?.limit ?? res?.data?.limit);
if (Number.isFinite(rawLimit)) {
limit = rawLimit;
unlimited = rawLimit === -1;
}
}
}
} catch {
// ignore
}
if (!Number.isFinite(total)) {
total = Number(managedArchiveCountAllTeams.value || 0) || 0;
}
// 10/100
if (!Number.isFinite(limit)) {
limit = isVerified.value ? 100 : 10;
unlimited = false;
}
//
if (unlimited) {
uni.showActionSheet({
itemList: ['邀请患者建档', '我帮患者建档'],
success: (res) => {
if (res.confirm) {
openAddCustomerServiceEntry();
if (res.tapIndex === 0) {
openInvitePatientEntry();
} else if (res.tapIndex === 1) {
openCreatePatientEntry();
}
}
});
return;
}
} else {
// 100 ->
if (managedArchiveCountAllTeams.value >= 100) {
const limitText = Number.isFinite(limit) ? String(limit) : '';
// ->
if (isVerified.value && total >= limit) {
uni.showModal({
title: '提示',
content: '当前管理档案数已达 100 个,无法继续新增。如需提升档案管理数,请联系客服处理。',
content: limitText
? `当前管理档案数已达上限 ${limitText} 个,无法继续新增。如需提升档案管理数,请联系客服处理。`
: '当前管理档案数已达上限,无法继续新增。如需提升档案管理数,请联系客服处理。',
cancelText: '知道了',
confirmText: '添加客服',
success: (res) => {
@ -1396,15 +1430,17 @@ const handleCreate = withInfo(() => {
return;
}
// + 10
if (!isVerified.value && managedArchiveCountAllTeams.value >= 10) {
// / -> toast
if (!isVerified.value && total >= limit) {
if (verifyStatus.value === 'verifying') {
toast('信息认证中,请耐心等待!');
return;
}
uni.showModal({
title: '提示',
content: '当前管理档案数已达上限 10 个,完成认证即可升级至 100 个。',
content: limitText
? `当前管理档案数已达上限 ${limitText} 个,完成认证可提升档案管理上限。`
: '当前管理档案数已达上限,完成认证可提升档案管理上限。',
cancelText: '暂不认证',
confirmText: '去认证',
success: (res) => {
@ -1415,7 +1451,6 @@ const handleCreate = withInfo(() => {
});
return;
}
}
//
uni.showActionSheet({

View File

@ -6,13 +6,16 @@ $text-color-sub: #999;
$primary-color: #0877F1;
.chat-page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
overflow: hidden;
position: relative;
width: 100%;
transition: padding-bottom 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 患者信息栏样式 - 固定在顶部 */
@ -24,10 +27,8 @@ $primary-color: #0877F1;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
padding: 20rpx 32rpx;
z-index: 100;
flex-shrink: 0; /* 防止被压缩 */
width: 100%;
box-sizing: border-box;
z-index: 10;
flex-shrink: 0;
}
.patient-info-content {
@ -91,15 +92,15 @@ $primary-color: #0877F1;
}
.chat-content {
flex: 1;
position: fixed;
top: 100rpx; /* 患者信息栏高度,根据实际调整 */
left: 0;
right: 0;
bottom: 120rpx; /* 输入框高度,根据实际调整 */
box-sizing: border-box;
overflow-x: hidden;
overflow-y: auto;
min-height: 0;
margin-top: 120rpx;
margin-bottom: 0;
position: relative;
z-index: 1;
transition: bottom 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.chat-content-compressed {
@ -364,18 +365,24 @@ $primary-color: #0877F1;
}
.input-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 1rpx solid #e0e0e0;
position: relative;
z-index: 200;
padding-bottom: env(safe-area-inset-bottom);
flex-shrink: 0;
transform: translateZ(0); /* 开启硬件加速,提升性能 */
transition: bottom 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
will-change: bottom;
}
.input-toolbar {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
padding: 12rpx 20rpx;
padding-bottom: env(safe-area-inset-bottom);
gap: 12rpx;
}
@ -499,7 +506,8 @@ $primary-color: #0877F1;
justify-content: flex-start;
background: #fff;
border-top: 1rpx solid #eee;
padding: 20rpx 0 40rpx 60rpx;
padding: 20rpx 0 20rpx 60rpx;
padding-bottom: env(safe-area-inset-bottom);
gap: 40rpx 50rpx;
flex-wrap: wrap;
background-color: #f5f5f5;

View File

@ -1,5 +1,5 @@
<template>
<view class="input-section">
<view class="input-section" :style="{ bottom: props.keyboardHeight + 'px' }">
<view class="input-toolbar">
<view @click="toggleVoiceInput" class="voice-toggle-btn">
<image v-if="showVoiceInput" src="/static/jianpan.png" class="voice-toggle-icon" mode="aspectFit"></image>
@ -8,7 +8,8 @@
<view class="input-area">
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
:auto-height="true" :show-confirm-bar="false" :adjust-position="true" :cursor-spacing="30"
:auto-height="true" :show-confirm-bar="false" :adjust-position="false" :hold-keyboard="true"
ref="textareaRef"
/>
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
@ -84,6 +85,7 @@ const props = defineProps({
corpId: { type: String, default: "" },
orderStatus: { type: String, default: "" },
isGenerating: { type: Boolean, default: false },
keyboardHeight: { type: Number, default: 0 },
});
// Emits
@ -100,6 +102,7 @@ const showVoiceInput = ref(false);
const showMorePanel = ref(false);
const isRecording = ref(false);
const recordingText = ref("录音中...");
const textareaRef = ref(null);
const cloudCustomData = computed(() => {
const arr = [
props.chatRoomBusiness.businessType,
@ -167,8 +170,19 @@ const initRecorderManager = () => {
const sendTextMessage = async () => {
if (!inputText.value.trim()) return;
await sendMessage("text", inputText.value);
const textToSend = inputText.value;
inputText.value = "";
await sendMessage("text", textToSend);
//
nextTick(() => {
// focus
//
setTimeout(() => {
// hold-keyboard
}, 50);
});
};
//

View File

@ -1,5 +1,6 @@
<template>
<view class="chat-page">
<page-meta :page-style="'overflow:' + (keyboardHeight > 0 ? 'hidden' : 'visible')"></page-meta>
<view class="chat-page" :style="{ paddingBottom: keyboardHeight + 'px' }">
<!-- 患者信息栏 -->
<view class="patient-info-bar" v-if="patientInfo.name">
<view class="patient-info-content">
@ -18,6 +19,7 @@
<!-- 聊天消息区域 -->
<scroll-view
class="chat-content"
:style="{ bottom: (keyboardHeight > 0 ? keyboardHeight + 60 : 60) + 'px' }"
scroll-y="true"
enhanced="true"
bounces="false"
@ -168,6 +170,7 @@
:patientInfo="patientInfo"
:orderStatus="orderStatus"
:isGenerating="isGenerating"
:keyboardHeight="keyboardHeight"
@scrollToBottom="() => scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)"
@endConsult="handleEndConsult"
@ -302,6 +305,10 @@ const chatInfo = ref({
userID: "",
avatar: "/static/home/avatar.svg",
});
//
const keyboardHeight = ref(0);
//
const isEvaluationPopupOpen = ref(false);
@ -458,6 +465,22 @@ onLoad((options) => {
chatInfo.value.userID = decodeQueryValue(options.userID);
}
//
uni.onKeyboardHeightChange((res) => {
console.log("键盘高度变化:", res.height);
const oldHeight = keyboardHeight.value;
keyboardHeight.value = res.height;
// 00
if (oldHeight === 0 && res.height > 0) {
nextTick(() => {
setTimeout(() => {
scrollToBottom(true);
}, 100);
});
}
});
checkLoginAndInitTIM();
updateNavigationTitle();
});
@ -888,16 +911,6 @@ onShow(() => {
// 访
uni.$on("send-followup-message", handleSendFollowUpMessage);
//
uni.onKeyboardHeightChange((res) => {
if (res.height > 0) {
//
setTimeout(() => {
scrollToBottom(true);
}, 100);
}
});
});
// 访
@ -1146,6 +1159,9 @@ const handleOpenConsult = async () => {
onUnmounted(() => {
clearMessageCache();
//
uni.offKeyboardHeightChange();
timChatManager.setCallback("onSDKReady", null);
timChatManager.setCallback("onSDKNotReady", null);
timChatManager.setCallback("onMessageReceived", null);

View File

@ -69,6 +69,7 @@ const urlsConfig = {
addCustomer: 'add',
updateCustomer: 'update',
transferCustomers: 'transferCustomers',
doctorCreatedTeamsCustomerLimitation: 'doctorCreatedTeamsCustomerLimitation',
getGroups: 'getGroups',
createGroup: 'createGroup',
updateGroup: 'updateGroup',