644 lines
18 KiB
Vue
644 lines
18 KiB
Vue
<template>
|
|
<view class="add-record-container">
|
|
<u-navbar title="添加记录" left-icon="arrow-left" @left-click="goBack">
|
|
<template #right>
|
|
<u-text text="保存" color="#ff6b6b" @click="saveRecord"></u-text>
|
|
</template>
|
|
</u-navbar>
|
|
|
|
<u-form ref="recordFormRef" :model="recordForm" :rules="rules" label-width="80">
|
|
<!-- 记录类型选择 -->
|
|
<u-card title="记录类型" :padding="20" margin="20">
|
|
<u-radio-group v-model="recordForm.category" @change="onCategoryChange">
|
|
<view class="category-grid">
|
|
<view
|
|
class="category-item"
|
|
v-for="category in recordCategories"
|
|
:key="category.value"
|
|
@click="selectCategory(category.value)"
|
|
>
|
|
<view class="category-icon" :class="{ active: recordForm.category === category.value }">
|
|
<u-icon :name="category.icon" size="24" :color="recordForm.category === category.value ? '#ffffff' : '#666666'"></u-icon>
|
|
</view>
|
|
<u-text :text="category.label" size="12" :color="recordForm.category === category.value ? '#ff6b6b' : '#666666'"></u-text>
|
|
</view>
|
|
</view>
|
|
</u-radio-group>
|
|
</u-card>
|
|
|
|
<!-- 基础信息 -->
|
|
<u-card title="基础信息" :padding="20" margin="20">
|
|
<u-form-item label="记录时间" prop="recordTime">
|
|
<u-input
|
|
v-model="recordForm.recordTime"
|
|
placeholder="选择记录时间"
|
|
readonly
|
|
@click="showDatePicker = true"
|
|
></u-input>
|
|
</u-form-item>
|
|
|
|
<u-form-item label="记录内容" prop="content">
|
|
<u-textarea
|
|
v-model="recordForm.content"
|
|
placeholder="描述一下具体情况..."
|
|
maxlength="500"
|
|
count
|
|
></u-textarea>
|
|
</u-form-item>
|
|
</u-card>
|
|
|
|
<!-- 动态表单区域 -->
|
|
<u-card :title="currentCategoryInfo.title" :padding="20" margin="20" v-if="currentCategoryInfo.fields.length > 0">
|
|
<view v-for="field in currentCategoryInfo.fields" :key="field.key">
|
|
<!-- 数字输入 -->
|
|
<u-form-item :label="field.label" v-if="field.type === 'number'">
|
|
<u-input
|
|
v-model="recordForm.details[field.key]"
|
|
:placeholder="field.placeholder"
|
|
type="number"
|
|
>
|
|
<template #suffix v-if="field.unit">
|
|
<u-text :text="field.unit" size="14" color="#999"></u-text>
|
|
</template>
|
|
</u-input>
|
|
</u-form-item>
|
|
|
|
<!-- 选择器 -->
|
|
<u-form-item :label="field.label" v-if="field.type === 'select'">
|
|
<u-input
|
|
v-model="recordForm.details[field.key]"
|
|
:placeholder="field.placeholder"
|
|
readonly
|
|
@click="showPicker(field)"
|
|
></u-input>
|
|
</u-form-item>
|
|
|
|
<!-- 多选标签 -->
|
|
<u-form-item :label="field.label" v-if="field.type === 'tags'">
|
|
<view class="tags-container">
|
|
<u-tag
|
|
v-for="option in field.options"
|
|
:key="option.value"
|
|
:text="option.label"
|
|
:type="isTagSelected(field.key, option.value) ? 'primary' : 'info'"
|
|
size="mini"
|
|
@click="toggleTag(field.key, option.value)"
|
|
></u-tag>
|
|
</view>
|
|
</u-form-item>
|
|
|
|
<!-- 文本输入 -->
|
|
<u-form-item :label="field.label" v-if="field.type === 'text'">
|
|
<u-input
|
|
v-model="recordForm.details[field.key]"
|
|
:placeholder="field.placeholder"
|
|
></u-input>
|
|
</u-form-item>
|
|
</view>
|
|
</u-card>
|
|
|
|
<!-- 图片上传 -->
|
|
<u-card title="添加图片" :padding="20" margin="20">
|
|
<view class="photo-upload-area">
|
|
<view class="photo-grid">
|
|
<view class="photo-item" v-for="(photo, index) in recordForm.photos" :key="index">
|
|
<u-image :src="photo" width="80px" height="80px" border-radius="8px"></u-image>
|
|
<view class="photo-delete" @click="removePhoto(index)">
|
|
<u-icon name="close-circle-fill" size="16" color="#ff6b6b"></u-icon>
|
|
</view>
|
|
</view>
|
|
<view class="photo-add" @click="choosePhotos" v-if="recordForm.photos.length < 9">
|
|
<u-icon name="camera-fill" size="24" color="#cccccc"></u-icon>
|
|
<u-text text="添加图片" size="12" color="#cccccc"></u-text>
|
|
</view>
|
|
</view>
|
|
<u-text :text="`${recordForm.photos.length}/9`" size="12" color="#999" style="margin-top: 10px;"></u-text>
|
|
</view>
|
|
</u-card>
|
|
|
|
<!-- 分享设置 -->
|
|
<u-card title="分享设置" :padding="20" margin="20">
|
|
<u-radio-group v-model="recordForm.shareLevel">
|
|
<view class="share-options">
|
|
<view class="share-option" v-for="option in shareOptions" :key="option.value">
|
|
<u-radio :name="option.value" :disabled="false">
|
|
<template #icon="{ checked }">
|
|
<u-icon :name="checked ? 'checkmark-circle-fill' : 'circle'" :color="checked ? '#ff6b6b' : '#c8c9cc'" size="18"></u-icon>
|
|
</template>
|
|
</u-radio>
|
|
<view class="share-info">
|
|
<u-text :text="option.label" size="14" bold></u-text>
|
|
<u-text :text="option.desc" size="12" color="#999"></u-text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</u-radio-group>
|
|
</u-card>
|
|
</u-form>
|
|
|
|
<!-- 日期选择器 -->
|
|
<u-datetime-picker
|
|
:show="showDatePicker"
|
|
v-model="selectedDate"
|
|
mode="datetime"
|
|
@confirm="confirmDate"
|
|
@cancel="showDatePicker = false"
|
|
></u-datetime-picker>
|
|
|
|
<!-- 选择器弹窗 -->
|
|
<u-picker
|
|
:show="showPickerModal"
|
|
:columns="pickerColumns"
|
|
@confirm="confirmPicker"
|
|
@cancel="showPickerModal = false"
|
|
></u-picker>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
|
export default {
|
|
name: 'AddRecordEnhanced',
|
|
setup() {
|
|
// 响应式数据
|
|
const state = reactive({
|
|
petId: '',
|
|
showDatePicker: false,
|
|
showPickerModal: false,
|
|
selectedDate: new Date().getTime(),
|
|
currentPickerField: null,
|
|
pickerColumns: [],
|
|
recordForm: {
|
|
category: 'daily',
|
|
recordTime: formatDateTime(new Date()),
|
|
content: '',
|
|
details: {},
|
|
photos: [],
|
|
shareLevel: 'family'
|
|
}
|
|
})
|
|
|
|
// 记录分类配置
|
|
const recordCategories = [
|
|
{ value: 'daily', label: '随手记', icon: 'edit-pen' },
|
|
{ value: 'milestone', label: '大事记', icon: 'star' },
|
|
{ value: 'health', label: '健康记录', icon: 'heart' },
|
|
{ value: 'grooming', label: '洗护记录', icon: 'flower' },
|
|
{ value: 'cleaning', label: '清洁记录', icon: 'home' },
|
|
{ value: 'expense', label: '消费记录', icon: 'rmb-circle' }
|
|
]
|
|
|
|
// 分享选项
|
|
const shareOptions = [
|
|
{ value: 'public', label: '公开', desc: '所有用户可见' },
|
|
{ value: 'family', label: '家人', desc: '仅家庭成员可见' },
|
|
{ value: 'private', label: '私有', desc: '仅自己可见' }
|
|
]
|
|
|
|
// 记录类型字段配置
|
|
const categoryFields = {
|
|
daily: {
|
|
title: '随手记详情',
|
|
fields: []
|
|
},
|
|
milestone: {
|
|
title: '大事记详情',
|
|
fields: [
|
|
{
|
|
key: 'milestoneType',
|
|
label: '里程碑类型',
|
|
type: 'select',
|
|
placeholder: '选择里程碑类型',
|
|
options: [
|
|
{ label: '绝育', value: 'neuter' },
|
|
{ label: '第一次吃奶', value: 'first_milk' },
|
|
{ label: '第一次吃猫粮', value: 'first_food' },
|
|
{ label: '第一次用猫砂', value: 'first_litter' },
|
|
{ label: '第一次出门', value: 'first_outside' },
|
|
{ label: '生日', value: 'birthday' },
|
|
{ label: '到家纪念日', value: 'adoption_day' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
health: {
|
|
title: '健康记录详情',
|
|
fields: [
|
|
{
|
|
key: 'healthType',
|
|
label: '健康类型',
|
|
type: 'select',
|
|
placeholder: '选择健康记录类型',
|
|
options: [
|
|
{ label: '疫苗接种', value: 'vaccine' },
|
|
{ label: '驱虫', value: 'deworming' },
|
|
{ label: '体检', value: 'checkup' },
|
|
{ label: '看病', value: 'treatment' },
|
|
{ label: '给药', value: 'medication' },
|
|
{ label: '手术', value: 'surgery' }
|
|
]
|
|
},
|
|
{
|
|
key: 'weight',
|
|
label: '体重',
|
|
type: 'number',
|
|
placeholder: '输入体重',
|
|
unit: 'kg'
|
|
},
|
|
{
|
|
key: 'temperature',
|
|
label: '体温',
|
|
type: 'number',
|
|
placeholder: '输入体温',
|
|
unit: '°C'
|
|
},
|
|
{
|
|
key: 'symptoms',
|
|
label: '症状',
|
|
type: 'tags',
|
|
options: [
|
|
{ label: '食欲不振', value: 'loss_appetite' },
|
|
{ label: '精神萎靡', value: 'lethargy' },
|
|
{ label: '呕吐', value: 'vomiting' },
|
|
{ label: '腹泻', value: 'diarrhea' },
|
|
{ label: '发热', value: 'fever' },
|
|
{ label: '咳嗽', value: 'cough' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
grooming: {
|
|
title: '洗护记录详情',
|
|
fields: [
|
|
{
|
|
key: 'groomingType',
|
|
label: '洗护类型',
|
|
type: 'tags',
|
|
options: [
|
|
{ label: '洗澡', value: 'bath' },
|
|
{ label: '剪指甲', value: 'nail_trim' },
|
|
{ label: '洗耳朵', value: 'ear_clean' },
|
|
{ label: '刷牙', value: 'teeth_clean' },
|
|
{ label: '梳毛', value: 'brush' },
|
|
{ label: '美容', value: 'grooming' }
|
|
]
|
|
},
|
|
{
|
|
key: 'duration',
|
|
label: '耗时',
|
|
type: 'number',
|
|
placeholder: '输入耗时',
|
|
unit: '分钟'
|
|
}
|
|
]
|
|
},
|
|
cleaning: {
|
|
title: '清洁记录详情',
|
|
fields: [
|
|
{
|
|
key: 'cleaningType',
|
|
label: '清洁类型',
|
|
type: 'tags',
|
|
options: [
|
|
{ label: '换猫砂', value: 'litter_change' },
|
|
{ label: '洗猫砂盆', value: 'litter_box_clean' },
|
|
{ label: '洗食盆', value: 'food_bowl_clean' },
|
|
{ label: '洗水盆', value: 'water_bowl_clean' },
|
|
{ label: '清洁笼子', value: 'cage_clean' },
|
|
{ label: '消毒', value: 'disinfect' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
expense: {
|
|
title: '消费记录详情',
|
|
fields: [
|
|
{
|
|
key: 'amount',
|
|
label: '金额',
|
|
type: 'number',
|
|
placeholder: '输入消费金额',
|
|
unit: '元'
|
|
},
|
|
{
|
|
key: 'expenseType',
|
|
label: '消费类型',
|
|
type: 'select',
|
|
placeholder: '选择消费类型',
|
|
options: [
|
|
{ label: '食物', value: 'food' },
|
|
{ label: '医疗', value: 'medical' },
|
|
{ label: '用品', value: 'supplies' },
|
|
{ label: '服务', value: 'service' },
|
|
{ label: '其他', value: 'other' }
|
|
]
|
|
},
|
|
{
|
|
key: 'store',
|
|
label: '购买地点',
|
|
type: 'text',
|
|
placeholder: '输入购买地点'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
// 计算属性
|
|
const currentCategoryInfo = computed(() => {
|
|
return categoryFields[state.recordForm.category] || { title: '', fields: [] }
|
|
})
|
|
|
|
// 表单验证规则
|
|
const rules = {
|
|
recordTime: [
|
|
{ required: true, message: '请选择记录时间', trigger: 'blur' }
|
|
],
|
|
content: [
|
|
{ required: true, message: '请输入记录内容', trigger: 'blur' }
|
|
]
|
|
}
|
|
|
|
// 生命周期
|
|
onMounted(() => {
|
|
initPage()
|
|
})
|
|
|
|
// 初始化页面
|
|
const initPage = () => {
|
|
const pages = getCurrentPages()
|
|
const currentPage = pages[pages.length - 1]
|
|
state.petId = currentPage.options.petId || '1'
|
|
}
|
|
|
|
// 选择记录类型
|
|
const selectCategory = (category) => {
|
|
state.recordForm.category = category
|
|
state.recordForm.details = {} // 清空详情数据
|
|
}
|
|
|
|
// 记录类型变化
|
|
const onCategoryChange = (value) => {
|
|
state.recordForm.details = {} // 清空详情数据
|
|
}
|
|
|
|
// 确认日期
|
|
const confirmDate = (e) => {
|
|
const date = new Date(e.value)
|
|
state.recordForm.recordTime = formatDateTime(date)
|
|
state.showDatePicker = false
|
|
}
|
|
|
|
// 显示选择器
|
|
const showPicker = (field) => {
|
|
state.currentPickerField = field
|
|
state.pickerColumns = [field.options.map(option => option.label)]
|
|
state.showPickerModal = true
|
|
}
|
|
|
|
// 确认选择器
|
|
const confirmPicker = (e) => {
|
|
if (state.currentPickerField) {
|
|
const selectedIndex = e.indexs[0]
|
|
const selectedOption = state.currentPickerField.options[selectedIndex]
|
|
state.recordForm.details[state.currentPickerField.key] = selectedOption.label
|
|
}
|
|
state.showPickerModal = false
|
|
}
|
|
|
|
// 标签选择
|
|
const isTagSelected = (fieldKey, value) => {
|
|
const currentValue = state.recordForm.details[fieldKey]
|
|
if (!currentValue) return false
|
|
return Array.isArray(currentValue) ? currentValue.includes(value) : currentValue === value
|
|
}
|
|
|
|
const toggleTag = (fieldKey, value) => {
|
|
if (!state.recordForm.details[fieldKey]) {
|
|
state.recordForm.details[fieldKey] = []
|
|
}
|
|
|
|
const currentValue = state.recordForm.details[fieldKey]
|
|
if (Array.isArray(currentValue)) {
|
|
const index = currentValue.indexOf(value)
|
|
if (index > -1) {
|
|
currentValue.splice(index, 1)
|
|
} else {
|
|
currentValue.push(value)
|
|
}
|
|
} else {
|
|
state.recordForm.details[fieldKey] = [value]
|
|
}
|
|
}
|
|
|
|
// 选择图片
|
|
const choosePhotos = () => {
|
|
const remainingCount = 9 - state.recordForm.photos.length
|
|
uni.chooseImage({
|
|
count: remainingCount,
|
|
sizeType: ['compressed'],
|
|
sourceType: ['album', 'camera'],
|
|
success: (res) => {
|
|
state.recordForm.photos.push(...res.tempFilePaths)
|
|
},
|
|
fail: (err) => {
|
|
console.error('选择图片失败', err)
|
|
uni.showToast({
|
|
title: '选择图片失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// 删除图片
|
|
const removePhoto = (index) => {
|
|
state.recordForm.photos.splice(index, 1)
|
|
}
|
|
|
|
// 保存记录
|
|
const saveRecord = () => {
|
|
// 表单验证
|
|
if (!state.recordForm.recordTime) {
|
|
uni.showToast({
|
|
title: '请选择记录时间',
|
|
icon: 'error'
|
|
})
|
|
return
|
|
}
|
|
|
|
if (!state.recordForm.content.trim()) {
|
|
uni.showToast({
|
|
title: '请输入记录内容',
|
|
icon: 'error'
|
|
})
|
|
return
|
|
}
|
|
|
|
// 构建记录数据
|
|
const recordData = {
|
|
id: Date.now().toString(),
|
|
petId: state.petId,
|
|
category: state.recordForm.category,
|
|
recordTime: state.recordForm.recordTime,
|
|
content: state.recordForm.content,
|
|
details: state.recordForm.details,
|
|
photos: state.recordForm.photos,
|
|
shareLevel: state.recordForm.shareLevel,
|
|
createTime: new Date().toISOString()
|
|
}
|
|
|
|
// 模拟保存到本地存储
|
|
try {
|
|
let records = uni.getStorageSync('petRecords') || []
|
|
records.unshift(recordData)
|
|
uni.setStorageSync('petRecords', records)
|
|
|
|
uni.showToast({
|
|
title: '保存成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
setTimeout(() => {
|
|
uni.navigateBack()
|
|
}, 1500)
|
|
} catch (error) {
|
|
console.error('保存记录失败', error)
|
|
uni.showToast({
|
|
title: '保存失败',
|
|
icon: 'error'
|
|
})
|
|
}
|
|
}
|
|
|
|
// 返回上一页
|
|
const goBack = () => {
|
|
uni.navigateBack()
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
recordCategories,
|
|
shareOptions,
|
|
currentCategoryInfo,
|
|
rules,
|
|
selectCategory,
|
|
onCategoryChange,
|
|
confirmDate,
|
|
showPicker,
|
|
confirmPicker,
|
|
isTagSelected,
|
|
toggleTag,
|
|
choosePhotos,
|
|
removePhoto,
|
|
saveRecord,
|
|
goBack
|
|
}
|
|
}
|
|
}
|
|
|
|
// 工具函数
|
|
function formatDateTime(date) {
|
|
const year = date.getFullYear()
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
const day = date.getDate().toString().padStart(2, '0')
|
|
const hours = date.getHours().toString().padStart(2, '0')
|
|
const minutes = date.getMinutes().toString().padStart(2, '0')
|
|
return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.add-record-container {
|
|
background-color: #f8f9fa;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.category-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 15px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.category-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.category-icon {
|
|
width: 50px;
|
|
height: 50px;
|
|
border-radius: 25px;
|
|
background-color: #f5f5f5;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.3s ease;
|
|
|
|
&.active {
|
|
background-color: #ff6b6b;
|
|
}
|
|
}
|
|
|
|
.tags-container {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.photo-upload-area {
|
|
.photo-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 10px;
|
|
}
|
|
|
|
.photo-item {
|
|
position: relative;
|
|
|
|
.photo-delete {
|
|
position: absolute;
|
|
top: -5px;
|
|
right: -5px;
|
|
background-color: #ffffff;
|
|
border-radius: 50%;
|
|
}
|
|
}
|
|
|
|
.photo-add {
|
|
width: 80px;
|
|
height: 80px;
|
|
border: 2px dashed #cccccc;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 5px;
|
|
}
|
|
}
|
|
|
|
.share-options {
|
|
.share-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
padding: 15px 0;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.share-info {
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
</style>
|