Files
intc-vue-h5/src/pages/health/doctorRecord/addEdit.vue
2026-07-08 10:10:38 +08:00

678 lines
22 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>
<view class="container" style="paddingBottom:1rpx;">
<view class="section">
<view class="section-title">{{ title}}</view>
<view class="form-view">
<u--form labelPosition="left" :model="form" :rules="rules" ref="uForm" label-width="160rpx"
:labelStyle="{ color: '#333333', fontSize: '30rpx', marginRight: '24rpx' }">
<u-form-item label="人员姓名" prop="personName" required @click="handlePerson">
<view class="input-with-arrow">
<u--input v-model="form.personName" readonly disabledColor="#ffffff" placeholder="请选择人员"
inputAlign="left" :customStyle="getInputStyle('personName')"></u--input>
<text class="arrow-icon"></text>
</view>
</u-form-item>
<u-form-item label="健康档案" prop="healthRecordName" required @click="handHealthRecord">
<view class="input-with-arrow">
<u--input v-model="form.healthRecordName" readonly disabledColor="#ffffff" placeholder="请选择健康档案"
inputAlign="left" :customStyle="getInputStyle('healthRecordName')"></u--input>
<text class="arrow-icon"></text>
</view>
</u-form-item>
<u-form-item label="医院名称" prop="hospitalName" required >
<u--input v-model="form.hospitalName" placeholder="请填写医院名称"
inputAlign="left" :customStyle="getInputStyle('hospitalName')"></u--input>
</u-form-item>
<u-form-item label="就医类型" prop="typeName" required @click="handleType">
<view class="input-with-arrow">
<u--input v-model="form.typeName" readonly disabledColor="#ffffff" placeholder="请选择就医类型"
inputAlign="left" :customStyle="getInputStyle('typeName')"></u--input>
<text class="arrow-icon"></text>
</view>
</u-form-item>
<u-form-item label="科室" prop="departments" required >
<u--input v-model="form.departments" placeholder="请填写科室"
inputAlign="left" :customStyle="getInputStyle('departments')"></u--input>
</u-form-item>
<u-form-item label="大夫" prop="doctor" required >
<u--input v-model="form.doctor" placeholder="请填写大夫"
inputAlign="left" :customStyle="getInputStyle('doctor')"></u--input>
</u-form-item>
<u-form-item label="就诊时间" prop="visitingTime" required @click="selectDate">
<view class="input-with-arrow">
<u--input v-model="form.visitingTime" readonly disabledColor="#ffffff" placeholder="请选择就诊时间" inputAlign="left" :customStyle="getInputStyle('visitingTime')"></u--input>
<text class="arrow-icon"></text>
</view>
</u-form-item>
<u-form-item label="陪同人" prop="partner" required >
<u--input v-model="form.partner" placeholder="请填写陪同人"
inputAlign="left" :customStyle="getInputStyle('partner')"></u--input>
</u-form-item>
<u-form-item label="总费用" prop="totalCost" required >
<u--input v-model="form.totalCost" placeholder="请填写总费用"
inputAlign="left" :customStyle="getInputStyle('totalCost')">
<template #suffix>
<up-text
text="元"
></up-text>
</template></u--input>
</u-form-item>
<u-form-item label="诊断结果" prop="diagnosis" required labelPosition="top">
<u--textarea v-model="form.diagnosis" height="60rpx" placeholder="请填写诊断结果" inputAlign="left" style="border: 2rpx solid #dcdfe6 !important; height: 160rpx;" :customStyle="getTextareaStyle('diagnosis')" ></u--textarea>
</u-form-item>
<u-form-item label="处理及医嘱" prop="prescribe" required labelPosition="top">
<u--textarea v-model="form.prescribe" height="60rpx" placeholder="请填写处理及医嘱" inputAlign="left" style="border: 2rpx solid #dcdfe6 !important; height: 160rpx;" :customStyle="getTextareaStyle('prescribe')"></u--textarea>
</u-form-item>
<u-form-item label="费用明细" prop="costDetail" >
<u--textarea v-model="form.costDetail" height="50rpx" placeholder="请填写费用明细" inputAlign="left" style="border: 2rpx solid #dcdfe6 !important; height: 160rpx;" :customStyle="getTextareaStyle('costDetail')"></u--textarea>
</u-form-item>
<u-form-item label="备注" prop="remark" labelPosition="top">
<u--textarea v-model="form.remark" height="40rpx" placeholder="请填写备注" inputAlign="left" style="border: 2rpx solid #dcdfe6 !important; height: 160rpx;" :customStyle="getTextareaStyle('remark')"></u--textarea>
</u-form-item>
<u-form-item label="附件图片" prop="attachment" labelPosition="top">
<view class="upload-wrap">
<view class="img-list">
<view class="img-item" v-for="(img, index) in attachmentList" :key="index">
<image :src="img" class="img-preview" mode="aspectFill" @click="previewImage(index)"></image>
<view class="img-del" @click="deleteImage(index)">
<uni-icons type="clear" size="20" color="#f5576c"></uni-icons>
</view>
</view>
<view class="img-add" v-if="attachmentList.length < 9" @click="chooseImage">
<uni-icons type="plusempty" size="28" color="#667eea"></uni-icons>
<text class="img-add-text">上传图片</text>
</view>
</view>
<view class="upload-tip">最多上传9张图片支持 jpg/png/jpeg 等格式</view>
</view>
</u-form-item>
</u--form>
<view class="form-btn">
<u-button type="primary" text="提交" @click="submit"></u-button>
</view>
</view>
</view>
<u-toast ref="uToast"></u-toast>
<u-picker itemHeight="88" :show="showPerson" :columns="personList" keyName="name" @cancel="handlePersonCancel"
@confirm="handlePersonConfirm"></u-picker>
<u-picker itemHeight="88" :show="showHealthRecord" :columns="healthRecordList" keyName="name" @cancel="handHealthRecordCancel"
@confirm="handHealthRecordConfirm"></u-picker>
<u-picker itemHeight="88" :show="showType" :columns="typeList" keyName="dictLabel" @cancel="handleTypeCancel"
@confirm="handleTypeConfirm"></u-picker>
<u-datetime-picker
:show="datePickShow"
mode="datetime"
ref="visitingTimeRef"
@cancel="datePickShow=false"
@confirm="datePickConfirm"
itemHeight="88"
></u-datetime-picker>
</view>
</template>
<script setup>
import { getDoctorRecord, addDoctorRecord, updateDoctorRecord, uploadFile } from '@/api/health/doctorRecord'
import config from '@/config'
import { compressImage } from '@/utils/imageCompress'
import { listPerson } from '@/api/health/person'
import { listHealthRecord } from '@/api/health/healthRecord'
const { proxy } = getCurrentInstance()
import { getDicts } from '@/api/system/dict/data.js'
import dayjs from 'dayjs'
import {onLoad,onReady} from "@dcloudio/uni-app";
// 计算属性与监听属性是在vue中而非uniap中 需要注意!!!
import {reactive ,toRefs,ref,computed ,getCurrentInstance }from "vue";
const datePickShow = ref(false)
const showPerson = ref(false)
const showHealthRecord = ref(false)
const title = ref("就医记录")
const healthRecordList = ref([])
const personList = ref([])
const typeList = ref([])
const showType = ref(false)
const flag = ref('add')
// 附件图片列表存储完整URL用于预览和提交
const attachmentList = ref([])
const uploading = ref(false)
const data = reactive({
form: {
id: null,
hospitalName: null,
departments: null,
doctor: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
delFlag: null,
remark: null,
healthRecordId: null,
visitingTime: null,
prescribe: null,
personId: null,
totalCost: 0,
partner: null,
costDetail: null,
attachment: null
},
queryPersonParams: {
pageNum: 1,
status: '1',
pageSize: 1000
},
queryHealthRecordParams: {
pageNum: 1,
personId:null,
pageSize: 1000
},
rules: {
healthRecordName: [{ required: true, message: '健康档案不能为空', trigger:['change', 'blur'] }],
personName: [{ required: true, message: '人员不能为空', trigger: ['change', 'blur'] }],
hospitalName: [{ required: true, message: '医院名称不能为空', trigger: ['change', 'blur'] }],
departments: [{ required: true, message: '科室不能为空', trigger: ['change', 'blur'] }],
doctor: [{ required: true, message: '大夫不能为空', trigger: ['change', 'blur'] }],
totalCost: [{ type: 'number', required: true, message: '总费用不能为空', trigger: ['change', 'blur'] }],
partner: [{ required: true, message: '陪同人不能为空', trigger: ['change', 'blur'] }],
prescribe: [{ required: true, message: '处理及医嘱不能为空', trigger: ['change', 'blur'] }],
diagnosis: [{ required: true, message: '诊断结果不能为空', trigger: ['change', 'blur'] }],
typeName: [{ required: true, message: '就医类型不能为空', trigger: ['change', 'blur'] }],
visitingTime: [{ required: true, message: '就诊时间不能为空', trigger: ['change', 'blur'] }]
}
})
const { form, queryPersonParams, queryHealthRecordParams, rules} = toRefs(data)
// 错误字段
const errorFields = ref([])
// 输入框基础样式
const inputBaseStyle = {
background: '#ffffff',
border: '2rpx solid #dcdfe6',
borderRadius: '8rpx',
padding: '0 24rpx',
height: '68rpx',
width: '100%',
boxSizing: 'border-box'
}
// 输入框错误样式
const inputErrorStyle = {
background: '#fef0f0',
border: '2rpx solid #f56c6c',
borderRadius: '8rpx',
padding: '0 24rpx',
height: '68rpx',
width: '100%',
boxSizing: 'border-box'
}
// 根据字段名获取输入框样式
const getInputStyle = (field) => {
return errorFields.value.includes(field) ? inputErrorStyle : inputBaseStyle
}
// 输入框基础样式
const textareaBaseStyle = {
background: '#ffffff',
border: '2rpx solid #dcdfe6',
}
// 输入框错误样式
const textareaErrorStyle = {
background: '#fef0f0',
border: '2rpx solid #f56c6c'
}
// 根据字段名获取输入框样式
const getTextareaStyle = (field) => {
return errorFields.value.includes(field) ? textareaErrorStyle : textareaBaseStyle
}
function normalizeAttachmentUrl(url) {
if (!url) return ''
if (url.startsWith('http')) return url
// 附件是后端静态资源直链(如 /fileUrl/...、/profile/upload/...
// 需要用域名拼接,不能带网关前缀 prod-api否则会 404
const origin = config.baseUrl.replace(/^(https?:\/\/[^/]+).*$/, '$1')
return origin + url
}
onLoad((option) => {
form.value.id = option.id
flag.value = option.flag
if(flag.value==null){
if(form.value.id!=null){
title.value="就医记录-修改"
}else{
title.value="就医记录-新增"
}
}else{
title.value="就医记录-复制"
}
getData()
})
onReady(() => {
form.value.visitingTime = dayjs(new Date().getTime()).format("YYYY-MM-DD HH:mm:ss")
})
function dictStr(val, arr) {
let str = ''
arr.map(item => {
if (item.dictValue === val) {
str = item.dictLabel
}
})
return str
}
function getData() {
// 药品来源
if(form.value.id!=null){
getDoctorRecord(form.value.id).then(res => {
listPerson(queryPersonParams.value).then((response) => {
personList.value = [response.rows]
queryHealthRecordParams.value.personId=res.data.personId
listHealthRecord(queryHealthRecordParams.value).then((response) => {
healthRecordList.value = [response.rows]
})
})
form.value = res.data
// 加载已有附件到列表
if (form.value.attachment) {
attachmentList.value = form.value.attachment.split(',').filter(url => url).map(normalizeAttachmentUrl)
}
// 就医类型
getDicts('doctor_type').then(result => {
typeList.value =[result.data]
form.value.typeName=dictStr(form.value.type, result.data)
})
})
}else{
// 就医类型
getDicts('doctor_type').then(res => {
typeList.value =[res.data]
})
listPerson(queryPersonParams.value).then((response) => {
personList.value = [response.rows]
if(response.rows.length>0){
form.value.personName= response.rows[0].name
form.value.personId = response.rows[0].id
queryHealthRecordParams.value.personId=response.rows[0].id
listHealthRecord(queryHealthRecordParams.value).then((response) => {
healthRecordList.value = [response.rows]
if(response.rows.length>0){
form.value.healthRecordName= response.rows[0].name
form.value.healthRecordId = response.rows[0].id
}
})
}
})
}
}
function handlePerson() {
if (personList.value[0].length === 0) {
proxy.$refs['uToast'].show({
message: '人员为空 ', type: 'warning'
})
} else {
showPerson.value = true
}
}
function handlePersonConfirm(e) {
form.value.personName = e.value[0].name
form.value.personId = e.value[0].id
form.value.healthRecordName = ''
form.value.healthRecordId = ''
queryHealthRecordParams.value.personId=e.value[0].id
listHealthRecord(queryHealthRecordParams.value).then((response) => {
healthRecordList.value = [response.rows]
if(response.rows.length>0){
form.value.healthRecordName= response.rows[0].name
form.value.healthRecordId = response.rows[0].id
}
showPerson.value = false
})
}
function handlePersonCancel() {
showPerson.value = false
}
function handHealthRecord() {
if (healthRecordList.value[0].length === 0) {
proxy.$refs['uToast'].show({
message: '健康档案为空 ', type: 'warning'
})
} else {
showHealthRecord.value = true
}
}
function handHealthRecordConfirm(e) {
form.value.healthRecordName = e.value[0].name
form.value.healthRecordId = e.value[0].id
showHealthRecord.value = false
}
function handHealthRecordCancel() {
showHealthRecord.value = false
}
function handleType() {
if (typeList.value[0].length === 0) {
proxy.$refs['uToast'].show({
message: '就医类型为空 ', type: 'warning'
})
} else {
showType.value = true
}
}
function handleTypeConfirm(e) {
form.value.typeName = e.value[0].dictLabel
form.value.type = e.value[0].dictValue
showType.value = false
}
function handleTypeCancel() {
showType.value = false
}
function selectDate() {
datePickShow.value = true
proxy.$refs['visitingTimeRef'].innerValue = new Date().getTime()
}
function datePickConfirm(e) {
form.value.visitingTime = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss")
datePickShow.value = false
}
function submit() {
// 提交前同步附件字段逗号分隔的URL字符串
form.value.attachment = attachmentList.value.join(',')
proxy.$refs['uForm'].validate().then(() => {
if (form.value.id != null) {
if(flag.value==null){
updateDoctorRecord(form.value).then(res => {
proxy.$refs['uToast'].show({
message: '修改成功', complete() {
uni.navigateTo({ url: `/pages/health/doctorRecord/list` })
}
})
})
}else {
form.value.id == null
addDoctorRecord(form.value).then(res => {
proxy.$refs['uToast'].show({
message: '新增成功', complete() {
uni.navigateTo({ url: `/pages/health/doctorRecord/list` })
}
})
})
}
}else {
addDoctorRecord(form.value).then(res => {
proxy.$refs['uToast'].show({
message: '新增成功', complete() {
uni.navigateTo({ url: `/pages/health/doctorRecord/list` })
}
})
})
}
}).catch(errors => {
// 验证失败,记录错误字段
if (errors && errors.length > 0) {
errorFields.value = errors.map(err => err.field)
}
proxy.$modal.msgError('请填写完整信息')
})
}
// 选择图片并上传
function chooseImage() {
if (uploading.value) {
proxy.$refs['uToast'].show({ message: '正在上传中,请稍候', type: 'warning' })
return
}
// H5 端传 camera 会触发 getUserMedia可能卡死微信小程序/APP 需要相机选项
let sourceType
// #ifdef H5
sourceType = ['album']
// #endif
// #ifndef H5
sourceType = ['album', 'camera']
// #endif
uni.chooseImage({
count: 9 - attachmentList.value.length,
sizeType: ['compressed'],
sourceType,
success: (res) => {
uploadImages(res.tempFilePaths, 0)
},
fail: (err) => {
// 用户取消选择不走 success也不会报错但某些浏览器会触发 fail
if (err && err.errMsg && err.errMsg.indexOf('cancel') === -1) {
proxy.$refs['uToast'].show({ message: '选择图片失败', type: 'warning' })
}
}
})
}
// 递归上传图片
async function uploadImages(paths, index) {
if (index >= paths.length) {
uploading.value = false
return
}
uploading.value = true
uni.showLoading({ title: `处理中 ${index + 1}/${paths.length}`, mask: false })
try {
// 上传前先压缩(超过 500KB 才压,避免小图多走一道)
const compressedPath = await compressImage(paths[index])
uni.showLoading({ title: `上传中 ${index + 1}/${paths.length}`, mask: false })
const res = await uploadFile({
filePath: compressedPath,
name: 'file'
})
const url = (res.data && res.data.url) || res.fileName || res.url
if (url) {
attachmentList.value.push(normalizeAttachmentUrl(url))
}
uni.hideLoading()
uploadImages(paths, index + 1)
} catch (e) {
uni.hideLoading()
uploading.value = false
proxy.$refs['uToast'].show({ message: `${index + 1} 张上传失败`, type: 'error' })
}
}
// 删除图片
function deleteImage(index) {
uni.showModal({
title: '提示',
content: '确定删除这张图片吗?',
confirmColor: '#f5576c',
success: (modalRes) => {
if (modalRes.confirm) {
attachmentList.value.splice(index, 1)
}
}
})
}
// 预览图片
function previewImage(index) {
uni.previewImage({
urls: attachmentList.value,
current: attachmentList.value[index]
})
}
</script>
<style lang="scss" scoped>
page {
height: 100%;
overflow: auto;
}
.section {
margin: 24rpx;
padding: 0;
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
.section-title {
padding: 16rpx 24rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #ffffff;
line-height: 1.2;
font-size: 28rpx;
font-weight: 600;
display: flex;
align-items: center;
&::before {
content: '';
width: 6rpx;
height: 28rpx;
background: #ffffff;
border-radius: 3rpx;
margin-right: 12rpx;
}
}
.form-view {
padding: 24rpx;
.form-btn {
padding-top: 16rpx;
}
// 带箭头的输入框容器
.input-with-arrow {
position: relative;
width: 100%;
.arrow-icon {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
color: #c0c4cc;
font-size: 20rpx;
pointer-events: none;
z-index: 10;
}
}
// 附件上传区域
.upload-wrap {
width: 100%;
.img-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 12rpx;
}
.img-item {
position: relative;
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.15);
.img-preview {
width: 100%;
height: 100%;
background: #f5f7fa;
}
.img-del {
position: absolute;
top: 4rpx;
right: 4rpx;
width: 36rpx;
height: 36rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15);
&:active {
transform: scale(0.9);
}
}
}
.img-add {
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
border: 2rpx dashed rgba(102, 126, 234, 0.4);
background: rgba(102, 126, 234, 0.06);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
transition: all 0.2s ease;
&:active {
transform: scale(0.96);
background: rgba(102, 126, 234, 0.12);
}
.img-add-text {
font-size: 22rpx;
color: #667eea;
font-weight: 500;
}
}
.upload-tip {
font-size: 22rpx;
color: #909399;
line-height: 1.4;
}
}
}
}
</style>
<style lang="scss">
.form-btn .u-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 24rpx !important;
height: 80rpx !important;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4) !important;
}
.form-btn .u-button__text {
font-size: 30rpx !important;
font-weight: 500 !important;
letter-spacing: 2rpx !important;
}
/* 强制让错误状态的输入框边框变红 */
.u-form-item--error .u--input,
.u-form-item--error .u-input,
.u-form-item--error .u-input__content,
.u-form-item--error .u-input__content__field-wrapper {
border: 2rpx solid #f56c6c !important;
background: #fef0f0 !important;
}
.u-form-item--error .u--textarea,
.u-form-item--error .u-textarea {
border: 2rpx solid #f56c6c !important;
background: #fef0f0 !important;
}
</style>