Files
xk-doctor-wx/subPackages/sub_salesperson/input/InputDetailPage.vue
2026-06-05 12:28:36 +08:00

354 lines
11 KiB
Vue

<template>
<business-page-layout :title="pageTitle" :show-back="true">
<view v-if="loading" class="loading-wrap">加载中...</view>
<view v-else-if="!info.id" class="loading-wrap">暂无数据</view>
<view v-else class="detail-page">
<scroll-view scroll-y class="detail-scroll">
<view class="detail-wrap">
<view v-for="section in sections" :key="section.title" class="section-block">
<view class="section-title">{{ section.title }}</view>
<template v-for="field in section.fields">
<view
v-if="shouldShowField(field)"
:key="field.key"
class="row"
:class="{ multiline: field.multiline }"
>
<text class="k">{{ field.label }}</text>
<view class="v">
<view v-if="field.type === 'image' && getImages(field).length" class="img-list">
<image
v-for="(url, idx) in getImages(field)"
:key="idx"
:src="url"
class="thumb"
mode="aspectFill"
@click="previewImages(getImages(field), idx)"
/>
</view>
<view v-else-if="field.type === 'images' && getImages(field).length" class="img-list">
<image
v-for="(url, idx) in getImages(field)"
:key="idx"
:src="url"
class="thumb"
mode="aspectFill"
@click="previewImages(getImages(field), idx)"
/>
</view>
<view v-else-if="field.type === 'files' && getFiles(field).length" class="file-list">
<text
v-for="(file, idx) in getFiles(field)"
:key="idx"
class="file-link"
@click="openFile(file.url)"
>{{ file.name }}</text>
</view>
<text v-else class="v-text">{{ fieldValue(field) }}</text>
</view>
</view>
</template>
</view>
<view v-if="showLinkedDoctors" class="section-block linked-doctors-block">
<view class="section-title">关联医生</view>
<view v-if="!linkedDoctors.length" class="linked-empty">暂无关联医生预填</view>
<view
v-for="doc in linkedDoctors"
:key="doc.id"
class="doctor-card"
@click="goDoctorDetail(doc.id)"
>
<image
v-if="doc.avatar"
:src="doc.avatar"
class="doctor-avatar"
mode="aspectFill"
/>
<view v-else class="doctor-avatar doctor-avatar-placeholder">
<text>{{ (doc.name || '?').charAt(0) }}</text>
</view>
<view class="doctor-card-body">
<text class="doctor-name">{{ doc.name || '-' }}</text>
<text class="doctor-meta">{{ doctorMetaText(doc) }}</text>
<text class="doctor-status">{{ doctorStatusText(doc.status) }}</text>
</view>
<u-icon name="arrow-right" color="#c9cdd4" size="28" />
</view>
</view>
</view>
</scroll-view>
<view class="footer-actions">
<u-button v-if="canEdit" class="action-btn" type="primary" :custom-style="primaryStyle" @click="goEdit">继续编辑</u-button>
<u-button v-if="showDoctorEntry" class="action-btn" type="primary" :custom-style="primaryStyle" @click="goDoctorInput">录入医生</u-button>
<template v-if="canAudit">
<u-button class="action-btn reject" @click="audit(2)">审核拒绝</u-button>
<u-button class="action-btn" type="primary" :custom-style="primaryStyle" @click="audit(1)">审核通过</u-button>
</template>
</view>
</view>
</business-page-layout>
</template>
<script>
import BusinessPageLayout from '@/subPackages/sub_salesperson/components/business-page-layout.vue'
import businessMixin from '@/subPackages/sub_salesperson/common/businessMixin.js'
import {
getStoreDetailSections,
getDoctorDetailSections,
formatDetailFieldValue,
getDetailImageUrls,
getDetailFileLinks,
getLinkedDoctorInputs,
inputStatusText,
} from '@/subPackages/sub_salesperson/common/inputDetailFields.js'
import { goDoctorInputFromStore } from '@/subPackages/sub_salesperson/common/inputNavigation.js'
export default {
mixins: [businessMixin],
components: { BusinessPageLayout },
props: {
recordId: { type: Number, default: 0 },
inputType: { type: String, default: 'store' },
editPath: { type: String, default: '' },
doctorFormPath: { type: String, default: '' },
doctorDetailPath: {
type: String,
default: '/subPackages/sub_salesperson/doctor-input/detail',
},
},
data() {
return {
info: {},
loading: false,
primaryStyle: {
backgroundColor: '#6acdbb',
color: '#ffffff',
borderColor: '#6acdbb',
},
}
},
computed: {
pageTitle() {
return this.inputType === 'doctor' ? '医生预填详情' : '门店录入详情'
},
sections() {
return this.inputType === 'doctor' ? getDoctorDetailSections() : getStoreDetailSections()
},
canEdit() {
return !this.canAudit && Number(this.info.status) === 0 && !!this.editPath
},
canAudit() {
if (this.inputType === 'doctor') return !!this.bizCap.canDoctorInputAudit
return !!this.bizCap.canStoreInputAudit
},
showDoctorEntry() {
return this.inputType === 'store' && !this.canAudit && !!this.info.id
},
showLinkedDoctors() {
return this.inputType === 'store' && !!this.info.id
},
linkedDoctors() {
return getLinkedDoctorInputs(this.info)
},
},
watch: {
recordId: {
immediate: true,
handler(id) {
if (Number(id) > 0) this.load()
else {
this.info = {}
this.loading = false
}
},
},
},
methods: {
detailApi() {
return this.inputType === 'doctor'
? this.bizApi.getDoctorInputDetail(this.recordId)
: this.bizApi.getStoreInputDetail(this.recordId)
},
auditApi(data) {
return this.inputType === 'doctor'
? this.bizApi.auditDoctorInput(data)
: this.bizApi.auditStoreInput(data)
},
load() {
if (!this.recordId) return
this.loading = true
this.info = {}
this.detailApi()
.then((w) => {
if (w.ok) this.info = w.data || {}
else uni.showToast({ title: (w.res && w.res.msg) || '加载失败', icon: 'none' })
})
.catch(() => {
uni.showToast({ title: '加载失败', icon: 'none' })
})
.finally(() => {
this.loading = false
})
},
shouldShowField(field) {
if (field.show && !field.show(this.info)) return false
if (field.type === 'image' || field.type === 'images') {
return getDetailImageUrls(field, this.info).length > 0
}
if (field.type === 'files') {
return getDetailFileLinks(field, this.info).length > 0
}
const val = formatDetailFieldValue(field, this.info)
return val != null && val !== ''
},
fieldValue(field) {
return formatDetailFieldValue(field, this.info) ?? '-'
},
getImages(field) {
return getDetailImageUrls(field, this.info)
},
getFiles(field) {
return getDetailFileLinks(field, this.info)
},
previewImages(urls, index) {
uni.previewImage({ urls, current: urls[index] })
},
openFile(url) {
if (!url) return
uni.setClipboardData({
data: url,
success: () => uni.showToast({ title: '链接已复制', icon: 'none' }),
})
},
goEdit() {
if (this.editPath) uni.navigateTo({ url: `${this.editPath}?id=${this.recordId}` })
},
goDoctorInput() {
goDoctorInputFromStore(this.info.id, this.bizCtx)
},
goDoctorDetail(doctorId) {
if (!doctorId || !this.doctorDetailPath) return
uni.navigateTo({ url: `${this.doctorDetailPath}?id=${doctorId}` })
},
doctorMetaText(doc) {
const depart = doc.depart?.name || ''
const title = doc.titleInfo?.name || doc.title_info?.name || ''
return [depart, title].filter(Boolean).join(' · ') || '-'
},
doctorStatusText(status) {
return inputStatusText(status)
},
audit(status) {
this.auditApi({ id: this.recordId, status, audit_remark: '' })
.then((w) => {
if (w.ok) {
uni.showToast({ title: '操作成功' })
setTimeout(() => uni.navigateBack(), 500)
} else {
uni.showToast({ title: w.msg || '操作失败', icon: 'none' })
}
})
.catch(() => uni.showToast({ title: '操作失败', icon: 'none' }))
},
},
}
</script>
<style scoped>
.loading-wrap { text-align: center; color: #86909c; padding: 120rpx 0; font-size: 28rpx; }
.detail-page {
display: flex;
flex-direction: column;
height: calc(100vh - 200rpx);
min-height: 400rpx;
}
.detail-scroll {
flex: 1;
height: 0;
box-sizing: border-box;
}
.detail-wrap { padding: 24rpx; padding-bottom: 32rpx; }
.section-block {
background: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
padding: 0 28rpx 8rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #1d2129;
padding: 28rpx 0 16rpx;
border-bottom: 1rpx solid #f2f3f5;
}
.row {
display: flex;
padding: 24rpx 0;
border-bottom: 1rpx solid #f2f3f5;
align-items: flex-start;
}
.row:last-child { border-bottom: none; }
.row.multiline .v { line-height: 1.6; }
.k { width: 200rpx; color: #86909c; font-size: 28rpx; flex-shrink: 0; }
.v { flex: 1; min-width: 0; }
.v-text { font-size: 28rpx; color: #1d2129; word-break: break-all; }
.img-list { display: flex; flex-wrap: wrap; gap: 16rpx; }
.thumb { width: 160rpx; height: 160rpx; border-radius: 12rpx; }
.file-list { display: flex; flex-direction: column; gap: 12rpx; }
.file-link { font-size: 28rpx; color: #6acdbb; word-break: break-all; }
.footer-actions {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background: #f5f7f8;
flex-shrink: 0;
}
.action-btn { flex: 1; min-width: 200rpx; }
.action-btn.reject { color: #f53f3f; border-color: #f53f3f; }
.linked-empty {
padding: 32rpx 0 40rpx;
font-size: 28rpx;
color: #86909c;
text-align: center;
}
.doctor-card {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f2f3f5;
}
.doctor-card:last-child { border-bottom: none; }
.doctor-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
flex-shrink: 0;
margin-right: 20rpx;
background: #f2f3f5;
}
.doctor-avatar-placeholder {
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #86909c;
}
.doctor-card-body { flex: 1; min-width: 0; }
.doctor-name {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #1d2129;
margin-bottom: 8rpx;
}
.doctor-meta {
display: block;
font-size: 26rpx;
color: #86909c;
margin-bottom: 6rpx;
}
.doctor-status { font-size: 24rpx; color: #6acdbb; }
</style>