feat: 新增日程管理完整功能模块
1. 新增scheduleManager云函数,实现日程增删改查与权限校验 2. 重构日程页面,支持筛选、分类展示与状态切换 3. 完善提醒编辑页面,支持新增和编辑日程 4. 调整全局安全边距与TabBar样式适配 5. 修复日程查询条件,替换completed判断为status字段
This commit is contained in:
parent
c709e72896
commit
78305c00dc
|
|
@ -359,7 +359,7 @@
|
|||
const userLocation = ref<{ longitude : number; latitude : number } | null>(null)
|
||||
const hospitals = ref<Hospital[]>([])
|
||||
const articles = ref<Article[]>([])
|
||||
const scrollPaddingBottom = ref(140)
|
||||
const scrollPaddingBottom = ref(78)
|
||||
|
||||
const primaryButtonStyle = {
|
||||
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||
|
|
@ -583,7 +583,7 @@
|
|||
|
||||
function calcLayout() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
scrollPaddingBottom.value = (systemInfo.safeAreaInsets?.bottom || 0) + 150
|
||||
scrollPaddingBottom.value = (systemInfo.safeAreaInsets?.bottom || 0) + 78
|
||||
}
|
||||
|
||||
async function loadDiscoveryData() {
|
||||
|
|
@ -944,6 +944,11 @@
|
|||
|
||||
.tab-card :deep(.tn-tabs) {
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.tab-card :deep(.tn-tabs__container) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab-card :deep(.tn-tabs__bar-container) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
</view>
|
||||
</view>
|
||||
|
||||
<TnTabbar v-model="currentTabbar" fixed switch-animation>
|
||||
<TnTabbar v-model="currentTabbar" fixed switch-animation :placeholder="false">
|
||||
<TnTabbarItem
|
||||
v-for="(item, index) in tabbarData"
|
||||
:key="index"
|
||||
|
|
|
|||
|
|
@ -261,8 +261,7 @@ onShow(() => {
|
|||
|
||||
function calcSafeArea() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
// TabBar 高度约 50px,加上安全区底部
|
||||
safeAreaBottom.value = (systemInfo.safeAreaInsets?.bottom || 0) + 20
|
||||
safeAreaBottom.value = (systemInfo.safeAreaInsets?.bottom || 0) + 78
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,7 +3,7 @@
|
|||
<scroll-view class="schedule-scroll" scroll-y>
|
||||
<view class="hero-card">
|
||||
<view>
|
||||
<text class="hero-title">添加提醒</text>
|
||||
<text class="hero-title">{{ pageTitle }}</text>
|
||||
<text class="hero-desc">为疫苗、驱虫、体检或自定义事项设置提醒。</text>
|
||||
</view>
|
||||
<TnIcon name="calendar-fill" :size="54" color="#F97316" />
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<TnButton text-color="#FFFFFF" shape="round" :bold="true" :custom-style="primaryButtonStyle"
|
||||
:loading="submitting" :disabled="submitting || !canSubmit"
|
||||
width="100%" height="96" :font-size="30" @click="handleSubmit">
|
||||
{{ submitting ? '保存中...' : '保存提醒' }}
|
||||
{{ submitText }}
|
||||
</TnButton>
|
||||
</view>
|
||||
|
||||
|
|
@ -89,30 +89,45 @@ const submitting = ref(false)
|
|||
const safeBottom = ref(0)
|
||||
const showPetPicker = ref(false)
|
||||
const showDatePicker = ref(false)
|
||||
const scheduleId = ref('')
|
||||
const primaryButtonStyle = {
|
||||
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
petId: '',
|
||||
type: 'custom',
|
||||
title: '',
|
||||
scheduledDate: '',
|
||||
frequency: 'once',
|
||||
note: '',
|
||||
})
|
||||
|
||||
const selectedPetName = computed(() => petList.value.find((pet) => pet._id === form.petId)?.name || '')
|
||||
const canSubmit = computed(() => !!(form.petId && form.title.trim() && form.scheduledDate))
|
||||
const pageTitle = computed(() => (scheduleId.value ? '编辑提醒' : '添加提醒'))
|
||||
const submitText = computed(() => {
|
||||
if (submitting.value) return '保存中...'
|
||||
return scheduleId.value ? '更新提醒' : '保存提醒'
|
||||
})
|
||||
const todayStr = computed(() => {
|
||||
const d = new Date()
|
||||
return `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')} 00:00:00`
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
onLoad((options: any) => {
|
||||
scheduleId.value = options?.id || ''
|
||||
const sys = uni.getSystemInfoSync()
|
||||
safeBottom.value = sys.safeAreaInsets?.bottom || 0
|
||||
loadPets()
|
||||
uni.setNavigationBarTitle({ title: scheduleId.value ? '编辑提醒' : '添加提醒' })
|
||||
loadPageData()
|
||||
})
|
||||
|
||||
async function loadPageData() {
|
||||
await loadPets()
|
||||
if (scheduleId.value) await loadScheduleDetail()
|
||||
}
|
||||
|
||||
async function loadPets() {
|
||||
try {
|
||||
const uid = uni.getStorageSync('uid')
|
||||
|
|
@ -127,6 +142,30 @@ async function loadPets() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadScheduleDetail() {
|
||||
try {
|
||||
const uid = uni.getStorageSync('uid')
|
||||
if (!uid || !scheduleId.value) return
|
||||
const res = await uniCloud.callFunction({
|
||||
name: 'scheduleManager',
|
||||
data: { action: 'detail', uid, scheduleId: scheduleId.value },
|
||||
})
|
||||
if (res.result?.code !== 0 || !res.result.data) {
|
||||
throw new Error(res.result?.message || '提醒不存在')
|
||||
}
|
||||
const data = res.result.data
|
||||
form.petId = data.pet_id || ''
|
||||
form.type = data.type || 'custom'
|
||||
form.title = data.title || ''
|
||||
form.scheduledDate = normalizeDateValue(data.scheduledDate)
|
||||
form.frequency = data.frequency || 'once'
|
||||
form.note = data.note || ''
|
||||
} catch (e) {
|
||||
console.warn('加载提醒详情失败', e)
|
||||
uni.showToast({ title: '提醒不存在或已删除', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function selectPet(id: string) {
|
||||
form.petId = id
|
||||
showPetPicker.value = false
|
||||
|
|
@ -137,30 +176,38 @@ async function handleSubmit() {
|
|||
submitting.value = true
|
||||
try {
|
||||
const uid = uni.getStorageSync('uid')
|
||||
const db = uniCloud.database()
|
||||
await db.collection('schedules').add({
|
||||
user_id: uid,
|
||||
const payload: any = {
|
||||
action: scheduleId.value ? 'update' : 'add',
|
||||
uid,
|
||||
pet_id: form.petId,
|
||||
type: 'custom',
|
||||
type: form.type,
|
||||
title: form.title.trim(),
|
||||
scheduledDate: form.scheduledDate,
|
||||
frequency: 'once',
|
||||
frequency: form.frequency,
|
||||
interval: 1,
|
||||
status: 'pending',
|
||||
reminderDays: [7, 3, 1],
|
||||
note: form.note.trim(),
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
uni.showToast({ title: '添加成功', icon: 'success' })
|
||||
}
|
||||
if (scheduleId.value) payload.scheduleId = scheduleId.value
|
||||
const res = await uniCloud.callFunction({ name: 'scheduleManager', data: payload })
|
||||
if (res.result?.code !== 0) throw new Error(res.result?.message || '保存失败')
|
||||
uni.showToast({ title: scheduleId.value ? '更新成功' : '添加成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1000)
|
||||
} catch (e) {
|
||||
console.error('保存提醒失败', e)
|
||||
uni.showToast({ title: '保存失败,请检查云权限', icon: 'none' })
|
||||
uni.showToast({ title: '保存失败,请稍后再试', icon: 'none' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDateValue(value: number | string | undefined) {
|
||||
if (!value) return ''
|
||||
if (typeof value === 'string') return value
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return ''
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
'use strict'
|
||||
const db = uniCloud.database()
|
||||
|
||||
const allowedUpdateFields = [
|
||||
'pet_id',
|
||||
'type',
|
||||
'title',
|
||||
'scheduledDate',
|
||||
'frequency',
|
||||
'interval',
|
||||
'status',
|
||||
'reminderDays',
|
||||
'relatedRecordId',
|
||||
'note'
|
||||
]
|
||||
|
||||
exports.main = async (event, context) => {
|
||||
const { action, uid } = event
|
||||
|
||||
if (!uid) {
|
||||
return { code: -1, message: '缺少用户标识' }
|
||||
}
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'list': {
|
||||
const { status, petId, page, pageSize } = event
|
||||
const where = { user_id: uid }
|
||||
if (status && status !== 'all') where.status = status
|
||||
if (petId) where.pet_id = petId
|
||||
|
||||
const p = page || 1
|
||||
const ps = pageSize || 100
|
||||
const skip = (p - 1) * ps
|
||||
const res = await db.collection('schedules')
|
||||
.where(where)
|
||||
.orderBy('scheduledDate', 'asc')
|
||||
.skip(skip)
|
||||
.limit(ps)
|
||||
.get()
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
list: res.data || [],
|
||||
total: res.affectedDocs !== undefined ? res.affectedDocs : (res.data || []).length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 'detail': {
|
||||
const { scheduleId } = event
|
||||
if (!scheduleId) {
|
||||
return { code: -1, message: '缺少提醒ID' }
|
||||
}
|
||||
const res = await db.collection('schedules').doc(scheduleId).get()
|
||||
const schedule = res.data && res.data[0]
|
||||
if (!schedule || schedule.user_id !== uid) {
|
||||
return { code: -1, message: '提醒不存在' }
|
||||
}
|
||||
return { code: 0, data: schedule }
|
||||
}
|
||||
|
||||
case 'add': {
|
||||
const { pet_id, type, title, scheduledDate, frequency, interval, reminderDays, relatedRecordId, note } = event
|
||||
const scheduleDate = normalizeScheduleDate(scheduledDate)
|
||||
if (!pet_id || !title || !scheduleDate) {
|
||||
return { code: -1, message: '请填写宠物、标题和日期' }
|
||||
}
|
||||
const now = Date.now()
|
||||
const scheduleData = {
|
||||
user_id: uid,
|
||||
pet_id,
|
||||
type: type || 'custom',
|
||||
title: title || '',
|
||||
scheduledDate: scheduleDate,
|
||||
frequency: frequency || 'once',
|
||||
interval: interval || 1,
|
||||
status: 'pending',
|
||||
reminderDays: Array.isArray(reminderDays) ? reminderDays : [7, 3, 1],
|
||||
relatedRecordId: relatedRecordId || '',
|
||||
note: note || '',
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
const res = await db.collection('schedules').add(scheduleData)
|
||||
return { code: 0, data: { _id: res.id }, message: '添加成功' }
|
||||
}
|
||||
|
||||
case 'update': {
|
||||
const { scheduleId } = event
|
||||
if (!scheduleId) {
|
||||
return { code: -1, message: '缺少提醒ID' }
|
||||
}
|
||||
|
||||
const detailRes = await db.collection('schedules').doc(scheduleId).get()
|
||||
const schedule = detailRes.data && detailRes.data[0]
|
||||
if (!schedule || schedule.user_id !== uid) {
|
||||
return { code: -1, message: '提醒不存在' }
|
||||
}
|
||||
|
||||
const updateFields = {}
|
||||
allowedUpdateFields.forEach((field) => {
|
||||
if (event[field] !== undefined) updateFields[field] = event[field]
|
||||
})
|
||||
if (event.scheduledDate !== undefined) {
|
||||
const scheduleDate = normalizeScheduleDate(event.scheduledDate)
|
||||
if (!scheduleDate) {
|
||||
return { code: -1, message: '日期格式不正确' }
|
||||
}
|
||||
updateFields.scheduledDate = scheduleDate
|
||||
}
|
||||
updateFields.updatedAt = Date.now()
|
||||
await db.collection('schedules').doc(scheduleId).update(updateFields)
|
||||
return { code: 0, message: '更新成功' }
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
const { scheduleId } = event
|
||||
if (!scheduleId) {
|
||||
return { code: -1, message: '缺少提醒ID' }
|
||||
}
|
||||
|
||||
const detailRes = await db.collection('schedules').doc(scheduleId).get()
|
||||
const schedule = detailRes.data && detailRes.data[0]
|
||||
if (!schedule || schedule.user_id !== uid) {
|
||||
return { code: -1, message: '提醒不存在' }
|
||||
}
|
||||
|
||||
await db.collection('schedules').doc(scheduleId).remove()
|
||||
return { code: 0, message: '删除成功' }
|
||||
}
|
||||
|
||||
default:
|
||||
return { code: -1, message: `未知操作: ${action}` }
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('scheduleManager error:', err)
|
||||
return { code: -1, message: err.message || '操作失败' }
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeScheduleDate(value) {
|
||||
if (!value) return 0
|
||||
if (typeof value === 'number') return value
|
||||
if (typeof value === 'string') {
|
||||
const match = value.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/)
|
||||
if (match) {
|
||||
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])).getTime()
|
||||
}
|
||||
}
|
||||
const date = new Date(value)
|
||||
return Number.isNaN(date.getTime()) ? 0 : date.getTime()
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ exports.main = async (event, context) => {
|
|||
|
||||
try {
|
||||
const schedulesRes = await db.collection('schedules')
|
||||
.where({ user_id: uid, completed: false })
|
||||
.where({ user_id: uid, status: 'pending' })
|
||||
.count()
|
||||
pendingScheduleCount = schedulesRes.total || 0
|
||||
} catch (e) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue