fix:修复滑动问题

This commit is contained in:
Jafeng 2026-02-11 17:45:09 +08:00
parent f980273cae
commit fdf2ca6136
5 changed files with 238 additions and 156 deletions

View File

@ -1,12 +1,14 @@
<template> <template>
<view class="page"> <view class="page">
<CustomerProfileTab <scroll-view scroll-y class="scroll" :style="{ height: scrollHeight + 'px' }">
:data="archive" <CustomerProfileTab
:baseItems="baseItems" :data="archive"
:internalItems="internalItems" :baseItems="baseItems"
:floatingBottom="16" :internalItems="internalItems"
@save="savePatch" :floatingBottom="16"
/> @save="savePatch"
/>
</scroll-view>
</view> </view>
</template> </template>
@ -44,6 +46,7 @@ const archive = ref({
const baseItems = ref([]); const baseItems = ref([]);
const internalItems = ref([]); const internalItems = ref([]);
const scrollHeight = ref(0);
const accountStore = useAccountStore(); const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore); const { account, doctorInfo } = storeToRefs(accountStore);
@ -336,6 +339,7 @@ async function savePatch(patch) {
} }
onLoad((options) => { onLoad((options) => {
scrollHeight.value = Number(uni.getSystemInfoSync()?.windowHeight || 0) || 0;
archiveId.value = options?.archiveId ? String(options.archiveId) : ''; archiveId.value = options?.archiveId ? String(options.archiveId) : '';
loadFromStorage(); loadFromStorage();
ensureDoctor().then(async () => { ensureDoctor().then(async () => {
@ -351,7 +355,12 @@ onShow(() => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; height: 100vh;
overflow: hidden;
background: #f5f6f8; background: #f5f6f8;
} }
.scroll {
height: 100vh;
}
</style> </style>

View File

@ -1,57 +1,61 @@
<template> <template>
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup-record/new-followup-record.vue简化移植去除 pinia/接口 --> <!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup-record/new-followup-record.vue简化移植去除 pinia/接口 -->
<view class="page"> <view class="page">
<view class="card"> <scroll-view scroll-y class="scroll" :style="{ height: scrollHeight + 'px' }">
<picker mode="date" :value="form.plannedExecutionTime" @change="changeDate"> <view class="card">
<view class="row clickable"> <picker mode="date" :value="form.plannedExecutionTime" @change="changeDate">
<view class="label">回访日期</view> <view class="row clickable">
<view class="label">回访日期</view>
<view class="right">
<view class="value" :class="{ muted: !form.plannedExecutionTime }">{{ form.plannedExecutionTime || '请选择回访日期' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
</picker>
<view class="block">
<view class="block-title">回访方式</view>
<view class="method-row">
<view class="method" @click="selectMethod('phone')">
<image class="radio" :src="`/static/circle${form.todoMethod === 'phone' ? 'd' : ''}.svg`" />
<view class="m-label">电话</view>
<view v-if="form.todoMethod === 'phone'" class="m-value" :class="{ muted: !form.phoneNumber }">{{ form.phoneNumber || '请选择电话号码' }}</view>
<uni-icons v-if="form.todoMethod === 'phone'" type="arrowright" size="16" color="#999" />
</view>
<view class="method" @click="selectMethod('wechat')">
<image class="radio" :src="`/static/circle${form.todoMethod === 'wechat' ? 'd' : ''}.svg`" />
<view class="m-label">微信</view>
</view>
</view>
</view>
<view class="row clickable" @click="selectType">
<view class="label">回访类型</view>
<view class="right"> <view class="right">
<view class="value" :class="{ muted: !form.plannedExecutionTime }">{{ form.plannedExecutionTime || '请选择回访日期' }}</view> <view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择回访类型' }}</view>
<uni-icons type="arrowright" size="16" color="#999" /> <uni-icons type="arrowright" size="16" color="#999" />
</view> </view>
</view> </view>
</picker>
<view class="block"> <view class="row clickable" @click="selectTeam">
<view class="block-title">回访方式</view> <view class="label">所在团队</view>
<view class="method-row"> <view class="right">
<view class="method" @click="selectMethod('phone')"> <view class="value" :class="{ muted: !form.teamId }">{{ form.teamName || '请选择团队' }}</view>
<image class="radio" :src="`/static/circle${form.todoMethod === 'phone' ? 'd' : ''}.svg`" /> <uni-icons type="arrowright" size="16" color="#999" />
<view class="m-label">电话</view>
<view v-if="form.todoMethod === 'phone'" class="m-value" :class="{ muted: !form.phoneNumber }">{{ form.phoneNumber || '请选择电话号码' }}</view>
<uni-icons v-if="form.todoMethod === 'phone'" type="arrowright" size="16" color="#999" />
</view> </view>
<view class="method" @click="selectMethod('wechat')"> </view>
<image class="radio" :src="`/static/circle${form.todoMethod === 'wechat' ? 'd' : ''}.svg`" />
<view class="m-label">微信</view> <view class="block">
<view class="block-title">回访结果</view>
<view class="textarea-box">
<textarea v-model="form.result" class="textarea tall" placeholder="请输入回访结果" maxlength="500" />
<view class="counter">{{ (form.result || '').length }}/500</view>
</view> </view>
</view> </view>
</view> </view>
<view class="row clickable" @click="selectType"> <view class="scroll-spacer" />
<view class="label">回访类型</view> </scroll-view>
<view class="right">
<view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择回访类型' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
<view class="row clickable" @click="selectTeam">
<view class="label">所在团队</view>
<view class="right">
<view class="value" :class="{ muted: !form.teamId }">{{ form.teamName || '请选择团队' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
<view class="block">
<view class="block-title">回访结果</view>
<view class="textarea-box">
<textarea v-model="form.result" class="textarea tall" placeholder="请输入回访结果" maxlength="500" />
<view class="counter">{{ (form.result || '').length }}/500</view>
</view>
</view>
</view>
<view class="footer"> <view class="footer">
<button class="btn plain" @click="cancel">取消</button> <button class="btn plain" @click="cancel">取消</button>
@ -91,6 +95,8 @@ import useAccountStore from '@/store/account';
import { toast } from '@/utils/widget'; import { toast } from '@/utils/widget';
import { getTodoEventTypeLabel, getTodoEventTypeOptions } from '@/utils/todo-const'; import { getTodoEventTypeLabel, getTodoEventTypeOptions } from '@/utils/todo-const';
const scrollHeight = ref(0);
const archiveId = ref(''); const archiveId = ref('');
const archiveName = ref(''); const archiveName = ref('');
const archiveMobile = ref(''); const archiveMobile = ref('');
@ -123,6 +129,13 @@ const mobiles = computed(() => {
}); });
onLoad((options) => { onLoad((options) => {
try {
const { windowHeight } = uni.getSystemInfoSync();
scrollHeight.value = windowHeight || 0;
} catch {
scrollHeight.value = 0;
}
archiveId.value = options?.archiveId ? String(options.archiveId) : ''; archiveId.value = options?.archiveId ? String(options.archiveId) : '';
const c = uni.getStorageSync('new-followup-record-customer'); const c = uni.getStorageSync('new-followup-record-customer');
if (c && typeof c === 'object') { if (c && typeof c === 'object') {
@ -322,9 +335,12 @@ function closeTypePicker() {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; height: 100vh;
overflow: hidden;
background: #f5f6f8; background: #f5f6f8;
padding-bottom: calc(76px + env(safe-area-inset-bottom)); }
.scroll {
width: 100%;
} }
.card { .card {
background: #fff; background: #fff;
@ -333,6 +349,9 @@ function closeTypePicker() {
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
} }
.scroll-spacer {
height: calc(76px + env(safe-area-inset-bottom));
}
.row { .row {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,79 +1,82 @@
<template> <template>
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup/new-followup.vuewxapp仅新增待办任务 --> <!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup/new-followup.vuewxapp仅新增待办任务 -->
<view class="page"> <view class="page">
<view class="card"> <scroll-view scroll-y class="scroll" :style="{ height: scrollHeight + 'px' }">
<picker mode="date" :value="form.planExecutionTime" @change="changeDate"> <view class="card">
<view class="row clickable"> <picker mode="date" :value="form.planExecutionTime" @change="changeDate">
<view class="row clickable">
<view class="left">
<view class="label">回访日期</view>
<view class="req">*</view>
</view>
<view class="right">
<view class="value" :class="{ muted: !form.planExecutionTime }">{{ form.planExecutionTime || '请选择回访日期' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
</picker>
<view class="row clickable" @click="selectExecutor">
<view class="left"> <view class="left">
<view class="label">回访日期</view> <view class="label">处理人</view>
<view class="req">*</view> <view class="req">*</view>
</view> </view>
<view class="right"> <view class="right">
<view class="value" :class="{ muted: !form.planExecutionTime }">{{ form.planExecutionTime || '请选择回访日期' }}</view> <view class="value" :class="{ muted: !form.executorName }">
{{ form.executorName ? `${form.executorName}${form.executeTeamName ? `(${form.executeTeamName})` : ''}` : '请选择处理人' }}
</view>
<uni-icons type="arrowright" size="16" color="#999" /> <uni-icons type="arrowright" size="16" color="#999" />
</view> </view>
</view> </view>
</picker>
<view class="row clickable" @click="selectExecutor"> <view class="row clickable" @click="selectType">
<view class="left"> <view class="left">
<view class="label">处理人</view> <view class="label">类型</view>
<view class="req">*</view> <view class="req">*</view>
</view>
<view class="right">
<view class="value" :class="{ muted: !form.executorName }">
{{ form.executorName ? `${form.executorName}${form.executeTeamName ? `(${form.executeTeamName})` : ''}` : '请选择处理人' }}
</view> </view>
<uni-icons type="arrowright" size="16" color="#999" /> <view class="right">
</view> <view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择类型' }}</view>
</view> <uni-icons type="arrowright" size="16" color="#999" />
</view>
<view class="row clickable" @click="selectType"> </view>
<view class="left">
<view class="label">类型</view> <view class="block">
<view class="req">*</view> <view class="block-title">目的</view>
</view> <view class="textarea-box">
<view class="right"> <textarea v-model="form.taskContent" class="textarea" placeholder="请输入文字提醒" maxlength="200" />
<view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择类型' }}</view> <view class="counter">{{ (form.taskContent || '').length }}/200</view>
<uni-icons type="arrowright" size="16" color="#999" /> </view>
</view> </view>
</view>
<view class="block">
<view class="block"> <view class="block-title">向患者发送</view>
<view class="block-title">目的</view> <view class="textarea-box">
<view class="textarea-box"> <textarea v-model="form.sendContent" class="textarea" placeholder="请输入要发送给患者的内容" maxlength="200" />
<textarea v-model="form.taskContent" class="textarea" placeholder="请输入文字提醒" maxlength="200" /> <view class="counter">{{ (form.sendContent || '').length }}/200</view>
<view class="counter">{{ (form.taskContent || '').length }}/200</view> </view>
</view> </view>
</view>
<view class="row clickable" @click="chooseFile">
<view class="block"> <view class="left">
<view class="block-title">向患者发送</view> <view class="label">添加附件</view>
<view class="textarea-box"> </view>
<textarea v-model="form.sendContent" class="textarea" placeholder="请输入要发送给患者的内容" maxlength="200" /> <view class="right">
<view class="counter">{{ (form.sendContent || '').length }}/200</view> <uni-icons type="plusempty" size="16" color="#0877F1" />
</view> </view>
</view> </view>
<view class="row clickable" @click="chooseFile"> <view v-if="showFileList.length" class="file-list">
<view class="left"> <view v-for="(i, index) in showFileList" :key="String(i._k || index)" class="file-item">
<view class="label">添加附件</view> <view class="file-main">
</view> <view v-if="i.typeStr" class="file-type">{{ i.typeStr }}</view>
<view class="right"> <view class="file-name">{{ i.fileName }}</view>
<uni-icons type="plusempty" size="16" color="#0877F1" /> </view>
</view> <uni-icons type="closeempty" size="18" color="#999" @click="removeFile(index)" />
</view>
<view v-if="showFileList.length" class="file-list">
<view v-for="(i, index) in showFileList" :key="String(i._k || index)" class="file-item">
<view class="file-main">
<view v-if="i.typeStr" class="file-type">{{ i.typeStr }}</view>
<view class="file-name">{{ i.fileName }}</view>
</view> </view>
<uni-icons type="closeempty" size="18" color="#999" @click="removeFile(index)" />
</view> </view>
</view> </view>
</view> <view class="scroll-spacer" />
</scroll-view>
<view class="footer"> <view class="footer">
<button class="btn plain" @click="cancel">取消</button> <button class="btn plain" @click="cancel">取消</button>
@ -138,8 +141,26 @@ import { chooseAndUploadImage } from '@/utils/file';
const archiveId = ref(''); const archiveId = ref('');
const archiveName = ref(''); const archiveName = ref('');
const customerData = ref({}); const customerData = ref({});
const scrollHeight = ref(0);
const eventTypeList = getTodoEventTypeOptions(); // 访 6
// 访访访
// utils/todo-const.js
// followUpNoDeal(访)followUpPostTreatment(访)followUpReminder()
// serviceSummary()eventNotification()ContentReminder()
// questionnaire()followUpComplaint(访)followUpActivity(访)
const __ALL_TODO_EVENT_TYPE_OPTIONS__ = getTodoEventTypeOptions();
const __NEW_FOLLOWUP_EVENT_TYPE_VALUES__ = [
'followUp',
'followUpPostSurgery',
'appointmentReminder',
'medicationReminder',
'followUpNoShow',
'other',
];
const eventTypeList = __NEW_FOLLOWUP_EVENT_TYPE_VALUES__
.map((v) => __ALL_TODO_EVENT_TYPE_OPTIONS__.find((i) => i.value === v))
.filter(Boolean);
const accountStore = useAccountStore(); const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore); const { account, doctorInfo } = storeToRefs(accountStore);
@ -163,6 +184,7 @@ const eventTypeLabel = computed(() => getTodoEventTypeLabel(form.eventType));
onLoad((options) => { onLoad((options) => {
resetForm(); resetForm();
scrollHeight.value = Number(uni.getSystemInfoSync()?.windowHeight || 0) || 0;
archiveId.value = options?.archiveId ? String(options.archiveId) : ''; archiveId.value = options?.archiveId ? String(options.archiveId) : '';
const c = uni.getStorageSync('new-followup-customer'); const c = uni.getStorageSync('new-followup-customer');
if (c && typeof c === 'object') { if (c && typeof c === 'object') {
@ -319,7 +341,6 @@ async function save() {
if (!form.executeTeamId) return uni.showToast({ title: '请选择处理人', icon: 'none' }); if (!form.executeTeamId) return uni.showToast({ title: '请选择处理人', icon: 'none' });
if (!form.executorUserId) return uni.showToast({ title: '请选择处理人', icon: 'none' }); if (!form.executorUserId) return uni.showToast({ title: '请选择处理人', icon: 'none' });
if (!form.eventType) return uni.showToast({ title: '请选择类型', icon: 'none' }); if (!form.eventType) return uni.showToast({ title: '请选择类型', icon: 'none' });
if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入目的', icon: 'none' });
await ensureDoctor(); await ensureDoctor();
const corpId = getCorpId(); const corpId = getCorpId();
@ -467,9 +488,13 @@ function chooseQuestionnaire() {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; height: 100vh;
background: #f5f6f8; background: #f5f6f8;
padding-bottom: calc(76px + env(safe-area-inset-bottom)); overflow: hidden;
}
.scroll-spacer {
height: calc(76px + env(safe-area-inset-bottom));
} }
.card { .card {
background: #fff; background: #fff;

View File

@ -2,7 +2,7 @@
<view class="page"> <view class="page">
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/service-record-detail/service-record-detail.vue --> <!-- Mobile 来源: ykt-management-mobile/src/pages/customer/service-record-detail/service-record-detail.vue -->
<view v-if="!recordId" class="body"> <view v-if="!recordId" class="body">
<scroll-view scroll-y class="scroll"> <scroll-view scroll-y class="scroll" :style="{ height: scrollHeight + 'px' }">
<view class="card"> <view class="card">
<view class="section-title">执行日期</view> <view class="section-title">执行日期</view>
<picker mode="date" @change="pickDate" :disabled="true"> <picker mode="date" @change="pickDate" :disabled="true">
@ -93,6 +93,8 @@ import useAccountStore from '@/store/account';
import { toast } from '@/utils/widget'; import { toast } from '@/utils/widget';
import { getServiceTypeOptions } from '@/utils/service-type-const'; import { getServiceTypeOptions } from '@/utils/service-type-const';
const scrollHeight = ref(0);
const archiveId = ref(''); const archiveId = ref('');
const mode = ref('add'); const mode = ref('add');
const recordId = ref(''); const recordId = ref('');
@ -148,6 +150,13 @@ function getCurrentTeam() {
} }
onLoad((options) => { onLoad((options) => {
try {
const { windowHeight } = uni.getSystemInfoSync();
scrollHeight.value = windowHeight || 0;
} catch {
scrollHeight.value = 0;
}
archiveId.value = options?.archiveId ? String(options.archiveId) : ''; archiveId.value = options?.archiveId ? String(options.archiveId) : '';
recordId.value = options?.id ? String(options.id) : ''; recordId.value = options?.id ? String(options.id) : '';
@ -295,9 +304,10 @@ async function submit(executionTime) {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; height: 100vh;
overflow: hidden;
background: #f5f6f8; background: #f5f6f8;
padding-bottom: calc(76px + env(safe-area-inset-bottom)); padding-bottom: 0;
} }
.body { .body {
height: 100vh; height: 100vh;
@ -305,7 +315,7 @@ async function submit(executionTime) {
flex-direction: column; flex-direction: column;
} }
.scroll { .scroll {
flex: 1; width: 100%;
} }
.card { .card {
background: #fff; background: #fff;

View File

@ -1,45 +1,49 @@
<template> <template>
<!-- 详情页参考截图顶部蓝条显示创建信息支持编辑/删除黄底提示不渲染 --> <!-- 详情页参考截图顶部蓝条显示创建信息支持编辑/删除黄底提示不渲染 -->
<view class="page"> <view class="page">
<view class="topbar"> <scroll-view scroll-y class="scroll" :style="{ height: scrollHeight + 'px' }">
<view class="topbar-text">{{ topText }}</view> <view class="topbar">
</view> <view class="topbar-text">{{ topText }}</view>
<view class="content">
<view class="section">
<view class="row">
<view class="label">病历类型</view>
<view class="value">{{ typeLabel }}</view>
</view>
<view class="row">
<view class="label">{{ visitDateLabel }}</view>
<view class="value">{{ visitDate || '--' }}</view>
</view>
<view v-if="showDiagnosisRow" class="row">
<view class="label">诊断</view>
<view class="value">{{ diagnosisText }}</view>
</view>
<view v-if="templateType === 'inhospital' && record.surgeryName" class="row">
<view class="label">手术名称</view>
<view class="value">{{ record.surgeryName }}</view>
</view>
</view> </view>
<view v-for="(s, idx) in sections" :key="idx" class="section"> <view class="content">
<view class="h2">{{ s.title }}</view> <view class="section">
<view class="p">{{ s.value }}</view> <view class="row">
</view> <view class="label">病历类型</view>
<view class="value">{{ typeLabel }}</view>
<view v-if="showFilesSection" class="section"> </view>
<view class="h2">文件上传</view> <view class="row">
<view v-if="files.length" class="files"> <view class="label">{{ visitDateLabel }}</view>
<view v-for="(f, idx) in files" :key="idx" class="file" @click="preview(idx)"> <view class="value">{{ visitDate || '--' }}</view>
<image class="thumb" :src="f.url" mode="aspectFill" /> </view>
<view v-if="showDiagnosisRow" class="row">
<view class="label">诊断</view>
<view class="value">{{ diagnosisText }}</view>
</view>
<view v-if="templateType === 'inhospital' && record.surgeryName" class="row">
<view class="label">手术名称</view>
<view class="value">{{ record.surgeryName }}</view>
</view> </view>
</view> </view>
<view v-else class="files-empty">暂无附件</view>
<view v-for="(s, idx) in sections" :key="idx" class="section">
<view class="h2">{{ s.title }}</view>
<view class="p">{{ s.value }}</view>
</view>
<view v-if="showFilesSection" class="section">
<view class="h2">文件上传</view>
<view v-if="files.length" class="files">
<view v-for="(f, idx) in files" :key="idx" class="file" @click="preview(idx)">
<image class="thumb" :src="f.url" mode="aspectFill" />
</view>
</view>
<view v-else class="files-empty">暂无附件</view>
</view>
</view> </view>
</view>
<view class="scroll-spacer" />
</scroll-view>
<view class="footer"> <view class="footer">
<button class="btn danger" @click="remove">删除</button> <button class="btn danger" @click="remove">删除</button>
@ -59,6 +63,8 @@ import { normalizeVisitRecordFormData } from './utils/visit-record';
import { normalizeTemplate, unwrapTemplateResponse } from './utils/template'; import { normalizeTemplate, unwrapTemplateResponse } from './utils/template';
import { normalizeFileUrl } from '@/utils/file'; import { normalizeFileUrl } from '@/utils/file';
const scrollHeight = ref(0);
const archiveId = ref(''); const archiveId = ref('');
const id = ref(''); const id = ref('');
const medicalType = ref(''); const medicalType = ref('');
@ -331,6 +337,13 @@ async function fetchRecord({ silent = false } = {}) {
} }
onLoad(async (opt) => { onLoad(async (opt) => {
try {
const { windowHeight } = uni.getSystemInfoSync();
scrollHeight.value = windowHeight || 0;
} catch {
scrollHeight.value = 0;
}
archiveId.value = opt?.archiveId ? String(opt.archiveId) : ''; archiveId.value = opt?.archiveId ? String(opt.archiveId) : '';
id.value = opt?.id ? String(opt.id) : ''; id.value = opt?.id ? String(opt.id) : '';
medicalType.value = opt?.type ? String(opt.type) : ''; medicalType.value = opt?.type ? String(opt.type) : '';
@ -408,9 +421,12 @@ function remove() {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; height: 100vh;
overflow: hidden;
background: #fff; background: #fff;
padding-bottom: calc(152rpx + env(safe-area-inset-bottom)); }
.scroll {
width: 100%;
} }
.topbar { .topbar {
background: #5d6df0; background: #5d6df0;
@ -475,6 +491,9 @@ function remove() {
color: #9aa0a6; color: #9aa0a6;
padding: 16rpx 0; padding: 16rpx 0;
} }
.scroll-spacer {
height: calc(152rpx + env(safe-area-inset-bottom));
}
.footer { .footer {
position: fixed; position: fixed;
left: 0; left: 0;