Files
akmon/pages/ec/admin/elder-form.uvue
2026-01-20 08:04:15 +08:00

837 lines
22 KiB
Plaintext

<template> <scroll-view class="elder-form-container">
<!-- Header -->
<view class="form-header">
<text class="header-title">{{ isEdit ? '编辑老人信息' : '添加新老人' }}</text>
<text class="header-subtitle">请填写完整的老人基本信息和健康状况</text>
</view>
<!-- 使用 form 标签进行统一数据收集 -->
<form @submit="onFormSubmit">
<!-- 基本信息 -->
<view class="form-section">
<view class="section-header">
<text class="section-title">基本信息</text>
<text class="required-tip">* 必填项</text>
</view>
<!-- 头像上传 -->
<view class="avatar-upload-section">
<view class="avatar-container" @click="uploadAvatar">
<image v-if="avatar" class="avatar-preview" :src="avatar" mode="aspectFill"></image>
<view v-else class="avatar-placeholder">
<text class="upload-icon">📷</text>
<text class="upload-text">上传头像</text>
</view>
</view>
<!-- 头像数据隐藏字段 -->
<input name="avatar" type="text" :value="avatar" style="display: none;" />
</view>
<view class="form-row">
<view class="form-item required">
<text class="form-label">姓名</text>
<input class="form-input" name="name" :value="name" placeholder="请输入老人姓名" />
</view>
<view class="form-item required">
<text class="form-label">性别</text>
<!-- 选择器改为 actionSheet -->
<view class="form-input" @click="chooseGender">
<text class="picker-text">{{ genderOptions[gender === 'male' ? 0 : 1] }}</text>
<text class="picker-arrow">▼</text>
</view>
<!-- 性别数据隐藏字段 -->
<input name="gender" type="text" :value="gender" style="display: none;" />
</view>
</view>
<view class="form-row">
<view class="form-item required">
<text class="form-label">出生日期</text>
<!-- 出生日期选择,改用 lime-date-time-picker -->
<view class="form-item required">
<text class="form-label">出生日期</text>
<view class="form-input" @click="showBirthDatePicker = true">
<text class="picker-text">{{ birth_date !== '' ? birth_date : '选择日期' }}</text>
<text class="picker-arrow">▼</text>
</view>
<input name="birth_date" type="text" :value="birth_date" style="display: none;" />
<l-date-time-picker v-if="showBirthDatePicker" v-model="birth_date" title="选择出生日期"
mode="年月日" :start="'1920-01-01'" :end="new Date().toISOString().split('T')[0]"
confirm-btn="确认" cancel-btn="取消" @confirm="onBirthDateConfirm"
@cancel="showBirthDatePicker = false" />
</view>
</view>
<view class="form-item">
<text class="form-label">身份证号</text>
<view class="id-card-input-row">
<input class="form-input" name="id_card" :value="id_card" placeholder="请输入身份证号码" />
<button class="id-card-photo-btn" type="button" @click="uploadIdCardPhoto">
<text class="photo-icon">📷</text>
<text class="photo-text">上传照片</text>
</button>
</view>
<view v-if="id_card_photo_url" class="id-card-photo-preview">
<image :src="id_card_photo_url" mode="aspectFill" class="id-card-photo-img" />
</view>
</view>
</view>
<view class="form-row">
<view class="form-item">
<text class="form-label">联系电话</text>
<input class="form-input" name="phone" :value="phone" placeholder="请输入联系电话" />
</view>
<view class="form-item">
<text class="form-label">房间号</text>
<input class="form-input" name="room_number" :value="room_number" placeholder="请输入房间号" />
</view>
</view>
<view class="form-item">
<text class="form-label">联系地址</text>
<textarea class="form-textarea" name="address" :value="address" placeholder="请输入详细地址" />
</view>
</view>
<!-- 健康状况 -->
<view class="form-section">
<view class="section-header">
<text class="section-title">健康状况</text>
</view>
<view class="form-row">
<view class="form-item">
<text class="form-label">健康状态</text>
<!-- 选择器改为 actionSheet -->
<view class="form-input" @click="chooseHealthStatus">
<text
class="picker-text">{{ healthStatusOptions[health_status === 'good' ? 0 : health_status === 'fair' ? 1 : health_status === 'poor' ? 2 : 3] }}</text>
<text class="picker-arrow">▼</text>
</view>
<!-- 健康状态数据隐藏字段 -->
<input name="health_status" type="text" :value="health_status" style="display: none;" />
</view>
<view class="form-item">
<text class="form-label">护理等级</text>
<!-- 选择器改为 actionSheet -->
<view class="form-input" @click="chooseCareLevel">
<text
class="picker-text">{{ careLevelOptions[care_level === 'level1' ? 0 : care_level === 'level2' ? 1 : care_level === 'level3' ? 2 : 3] }}</text>
<text class="picker-arrow">▼</text>
</view>
<!-- 护理等级数据隐藏字段 -->
<input name="care_level" type="text" :value="care_level" style="display: none;" />
</view>
</view>
<view class="form-item">
<text class="form-label">疾病史</text>
<textarea class="form-textarea" name="medical_history" :value="medical_history"
placeholder="请输入主要疾病史和治疗情况" />
</view>
<view class="form-item">
<text class="form-label">过敏史</text>
<textarea class="form-textarea" name="allergies" :value="allergies" placeholder="请输入过敏史,如无请填写'无'" />
</view>
<view class="form-item">
<text class="form-label">特殊需求</text>
<textarea class="form-textarea" name="special_needs" :value="special_needs"
placeholder="请输入特殊护理需求" />
</view>
</view>
<!-- 紧急联系人 -->
<view class="form-section">
<view class="section-header">
<text class="section-title">紧急联系人</text>
</view>
<view class="form-row">
<view class="form-item">
<text class="form-label">联系人姓名</text>
<input class="form-input" name="emergency_contact_name" :value="emergency_contact_name"
placeholder="请输入联系人姓名" />
</view>
<view class="form-item">
<text class="form-label">与老人关系</text>
<input class="form-input" name="emergency_contact_relationship"
:value="emergency_contact_relationship" placeholder="如:子女、配偶等" />
</view>
</view>
<view class="form-row">
<view class="form-item">
<text class="form-label">联系电话</text>
<input class="form-input" name="emergency_contact_phone" :value="emergency_contact_phone"
placeholder="请输入联系电话" />
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="form-actions">
<button class="btn btn-cancel" type="button" @click="goBack">取消</button>
<button class="btn btn-submit" form-type="submit" :disabled="isSubmitting">
{{ isSubmitting ? '保存中...' : (isEdit ? '更新信息' : '添加老人') }}
</button>
</view>
</form>
</scroll-view>
</template>
<script lang="uts">
import type { Elder } from '../types.uts'
import supa from '@/components/supadb/aksupainstance.uts'
export default {
data() {
return {
isEdit: false,
elderId: '',
isSubmitting: false,
// 所有表单变量一维展开
name: '',
id_card: '',
gender: 'male',
birth_date: '',
avatar: '',
phone: '',
address: '',
health_status: 'good',
care_level: 'level1',
medical_history: '',
allergies: '',
special_needs: '',
emergency_contact_name: '',
emergency_contact_relationship: '',
emergency_contact_phone: '',
room_number: '',
id_card_photo_url: '', // 身份证照片url
// 选项
genderOptions: ['男', '女'],
healthStatusOptions: ['良好', '一般', '较差', '危重'],
careLevelOptions: ['一级护理', '二级护理', '三级护理', '特级护理'],
showBirthDatePicker: false // 控制出生日期选择器显示
}
},
onLoad(options) {
if (options['id'] !== null && options['id'] !== undefined) {
this.isEdit = true
this.elderId = options['id'] as string
this.loadElderInfo(this.elderId)
} else {
this.isEdit = false
this.elderId = ''
}
},
methods: {
// 性别选择
chooseGender() {
uni.showActionSheet({
itemList: this.genderOptions,
success: (res : any) => {
if (res.tapIndex !== null) {
this.gender = res.tapIndex === 0 ? 'male' : 'female'
}
}
})
},
// 出生日期选择
chooseBirthDate() {
uni.showActionSheet({
itemList: this.getDateOptions(),
success: (res : any) => {
if (res.tapIndex !== null) {
const selectedDate = this.getDateOptions()[res.tapIndex]
this.birth_date = selectedDate
}
}
})
},
// 健康状态选择
chooseHealthStatus() {
uni.showActionSheet({
itemList: this.healthStatusOptions,
success: (res : any) => {
if (res.tapIndex !== null) {
const statusMap = ['good', 'fair', 'poor', 'critical']
this.health_status = statusMap[res.tapIndex]
}
}
})
},
// 护理等级选择
chooseCareLevel() {
uni.showActionSheet({
itemList: this.careLevelOptions,
success: (res : any) => {
if (res.tapIndex !== null) {
const levelMap = ['level1', 'level2', 'level3', 'special']
this.care_level = levelMap[res.tapIndex]
}
}
})
},
// 统一的表单提交处理
onFormSubmit(e : UniFormSubmitEvent) {
// 从表单数据中更新 formData
const formValues = e.detail.value
// 更新所有表单字段
this.name = formValues.getString('name') ?? ''
this.id_card = formValues.getString('id_card') ?? ''
this.phone = formValues.getString('phone') ?? ''
this.room_number = formValues.getString('room_number') ?? ''
this.address = formValues.getString('address') ?? ''
this.medical_history = formValues.getString('medical_history') ?? ''
this.allergies = formValues.getString('allergies') ?? ''
this.special_needs = formValues.getString('special_needs') ?? ''
this.emergency_contact_name = formValues.getString('emergency_contact_name') ?? ''
this.emergency_contact_relationship = formValues.getString('emergency_contact_relationship') ?? ''
this.emergency_contact_phone = formValues.getString('emergency_contact_phone') ?? ''
// 对于 picker 组件,数据已经通过事件处理更新到 formData 了
// avatar, gender, birth_date, health_status, care_level 无需从表单中获取
// 执行表单提交逻辑
this.submitForm()
},
async loadElderInfo(id : string) {
try {
const result = await supa.from('ec_elders')
.select('*', {})
.eq('id', id)
.executeAs<Elder>()
// UTS/uni-app-x: result 结构兼容性处理
if (result !== null && typeof result === 'object' && result.data !== null && result.data instanceof Array && result.data.length > 0) {
const elder = result.data[0] as Elder
this.formData = { ...elder }
this.updateFormIndexes()
} else {
uni.showToast({
title: '未找到老人信息',
icon: 'error'
})
}
} catch (error) {
console.error('加载老人信息失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
}
},
updateFormIndexes() {
// 更新选择器索引
this.genderIndex = this.formData.gender === 'male' ? 0 : 1
const healthStatusMap = {
'good': 0,
'fair': 1,
'poor': 2,
'critical': 3
}
this.healthStatusIndex = healthStatusMap[this.formData.health_status] ?? 0
const careLevelMap = {
'level1': 0,
'level2': 1,
'level3': 2,
'special': 3
}
this.careLevelIndex = careLevelMap[this.formData.care_level] ?? 0
},
onGenderChange(e : UniPickerChangeEvent) {
this.genderIndex = e.detail.value as number
this.formData.gender = (e.detail.value as number) === 0 ? 'male' : 'female'
},
onBirthDateChange(e : UniPickerChangeEvent) {
this.formData.birth_date = e.detail.value as string
},
onHealthStatusChange(e : UniPickerChangeEvent) {
this.healthStatusIndex = e.detail.value as number
const statusMap = ['good', 'fair', 'poor', 'critical']
this.formData.health_status = statusMap[e.detail.value as number]
},
onCareLevelChange(e : UniPickerChangeEvent) {
this.careLevelIndex = e.detail.value as number
const levelMap = ['level1', 'level2', 'level3', 'special']
this.formData.care_level = levelMap[e.detail.value as number]
},
uploadAvatar() {
// 上传头像逻辑
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// 这里应该上传到服务器并获取URL
// 暂时使用本地路径
this.formData.avatar = res.tempFilePaths[0]
}
})
},
uploadIdCardPhoto() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const filePath = res.tempFilePaths[0]
// 上传到 storage
const cloudPath = 'elder_idcard/' + Date.now() + '_' + Math.floor(Math.random() * 10000) + '.jpg'
uniCloud.uploadFile({
filePath: filePath,
cloudPath: cloudPath,
success: (uploadRes) => {
if (uploadRes.fileID) {
this.id_card_photo_url = uploadRes.fileID
uni.showToast({ title: '上传成功', icon: 'success' })
} else {
uni.showToast({ title: '上传失败', icon: 'error' })
}
},
fail: (err) => {
console.error('身份证上传失败:', err)
uni.showToast({ title: '上传失败', icon: 'error' })
}
})
}
})
},
async submitForm() {
if (!this.validateForm()) {
return
}
this.isSubmitting = true
try {
if (this.isEdit) {
await this.updateElder()
} else {
await this.createElder()
}
uni.showToast({
title: this.isEdit ? '更新成功' : '添加成功',
icon: 'success'
})
setTimeout(() => {
this.goBack()
}, 1500)
} catch (error) {
console.error('保存失败:', error)
uni.showToast({
title: '保存失败',
icon: 'error'
})
} finally {
this.isSubmitting = false
}
},
async createElder() {
const result = await supa.executeAs('create_elder', {
...this.formData,
age: this.calculateAge(this.formData.birth_date),
admission_date: new Date().toISOString().split('T')[0],
status: 'active'
})
if (!result.success) {
throw new Error(result.error ?? '添加失败')
}
},
async updateElder() {
const result = await supa.executeAs('update_elder', {
elder_id: this.elderId,
...this.formData,
age: this.calculateAge(this.formData.birth_date)
})
if (!result.success) {
throw new Error(result.error ?? '更新失败')
}
},
validateForm() {
if (!this.formData.name.trim()) {
uni.showToast({
title: '请输入姓名',
icon: 'error'
})
return false
}
if (!this.formData.birth_date) {
uni.showToast({
title: '请选择出生日期',
icon: 'error'
})
return false
}
return true
},
calculateAge(birthDate : string) : number {
const today = new Date()
const birth = new Date(birthDate)
let age = today.getFullYear() - birth.getFullYear()
const monthDiff = today.getMonth() - birth.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--
}
return age
},
goBack() {
uni.navigateBack()
},
scanIdCardPhoto() {
// 调用拍照识别身份证的接口
uni.chooseImage({
count: 1,
sizeType: ['original'],
sourceType: ['camera'],
success: (res) => {
const imagePath = res.tempFilePaths[0]
// 这里调用身份证识别的云函数或API
// 假设有一个云函数叫做 'recognizeIdCard'
uni.cloud.callFunction({
name: 'recognizeIdCard',
data: {
image: imagePath
},
success: (res) => {
if (res.result && res.result.code === 200) {
// 假设返回的结果中有 name, id_card, gender, birth_date 字段
const { name, id_card, gender, birth_date } = res.result.data
// 更新表单数据
this.name = name
this.id_card = id_card
this.gender = gender === '男' ? 'male' : 'female'
this.birth_date = birth_date
uni.showToast({
title: '识别成功',
icon: 'success'
})
} else {
uni.showToast({
title: '识别失败,请重试',
icon: 'error'
})
}
},
fail: (err) => {
console.error('调用云函数失败:', err)
uni.showToast({
title: '识别失败,请重试',
icon: 'error'
})
}
})
}
})
},
uploadIdCardPhoto() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const filePath = res.tempFilePaths[0]
// 上传到 storage
const cloudPath = 'elder_idcard/' + Date.now() + '_' + Math.floor(Math.random() * 10000) + '.jpg'
uniCloud.uploadFile({
filePath: filePath,
cloudPath: cloudPath,
success: (uploadRes) => {
if (uploadRes.fileID) {
this.id_card_photo_url = uploadRes.fileID
uni.showToast({ title: '上传成功', icon: 'success' })
} else {
uni.showToast({ title: '上传失败', icon: 'error' })
}
},
fail: (err) => {
console.error('身份证上传失败:', err)
uni.showToast({ title: '上传失败', icon: 'error' })
}
})
}
})
}
}
}
</script>
<style lang="scss">
.elder-form-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.form-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 30rpx 30rpx;
color: white;
.header-title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.header-subtitle {
font-size: 26rpx;
opacity: 0.9;
}
}
.form-section {
background: white;
margin: 20rpx 30rpx;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.required-tip {
font-size: 24rpx;
color: #ff6b6b;
}
}
.avatar-upload-section {
display: flex;
justify-content: center;
margin-bottom: 30rpx;
}
.avatar-container {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
overflow: hidden;
border: 4rpx solid #e1e1e1;
position: relative;
.avatar-preview {
width: 100%;
height: 100%;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background-color: #f8f8f8;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.upload-icon {
font-size: 30rpx;
margin-bottom: 5rpx;
}
.upload-text {
font-size: 20rpx;
color: #999;
}
}
}
.form-row {
display: flex;
margin-bottom: 30rpx;
}
.form-item {
flex: 1;
margin-bottom: 30rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.form-input {
width: 100%;
height: 80rpx;
flex-direction: row;
justify-content: space-between;
border: 2rpx solid #e1e1e1;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 28rpx;
background-color: #fff;
&:focus {
border-color: #667eea;
}
}
.form-textarea {
width: 100%;
min-height: 120rpx;
border: 2rpx solid #e1e1e1;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
background-color: #fff;
&:focus {
border-color: #667eea;
}
}
.form-picker {
width: 100%;
height: 80rpx;
border: 2rpx solid #e1e1e1;
border-radius: 10rpx;
background-color: #fff;
}
.picker-display {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 20rpx;
.picker-text {
font-size: 28rpx;
color: #333;
}
.picker-arrow {
font-size: 24rpx;
color: #999;
}
}
.form-actions {
padding: 30rpx;
display: flex;
}
.btn {
flex: 1;
height: 80rpx;
border-radius: 10rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.btn-cancel {
background-color: #f5f5f5;
color: #666;
border: 2rpx solid #e1e1e1;
}
.btn-submit {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
}
.btn-disabled {
opacity: 0.6;
}
.btn-last-child {
margin-right: 0;
}
.id-card-input-row {
display: flex;
align-items: center;
border: 2rpx solid #e1e1e1;
border-radius: 10rpx;
background-color: #fff;
padding: 0 10rpx;
}
.id-card-input-row .form-input {
flex: 1;
height: 80rpx;
border: none;
padding: 0 10rpx;
font-size: 28rpx;
}
.id-card-photo-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 80rpx;
background-color: #667eea;
color: white;
border: none;
border-radius: 10rpx;
font-size: 28rpx;
margin-left: 10rpx;
}
.photo-icon {
margin-right: 5rpx;
}
.id-card-photo-preview {
margin-top: 10rpx;
display: flex;
justify-content: center;
}
.id-card-photo-img {
width: 100%;
max-width: 300rpx;
height: auto;
border-radius: 10rpx;
border: 2rpx solid #e1e1e1;
}
</style>