Files
yunzerwebsiteallinone/uniapp/pages/tools/schedule/reminder-edit.vue
T
2026-11-03 09:00:29 +08:00

378 lines
9.1 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="page">
<view class="form-card">
<view class="field">
<text class="field-label">日程内容 <text class="required">*</text></text>
<u-textarea
v-model="form.content"
placeholder="输入日程提醒内容"
:maxlength="500"
count
height="140"
border="none"
/>
</view>
<view class="field">
<text class="field-label">日程时间 <text class="required">*</text></text>
<view class="dt-row">
<picker mode="date" :value="form.date" @change="onDateChange" class="dt-picker">
<view class="picker-value">
<text>{{ form.date || '选择日期' }}</text>
<FaIcon name="calendar-days" color="#94a3b8" :size="15" />
</view>
</picker>
<picker mode="time" :value="form.time" @change="onTimeChange" class="dt-picker">
<view class="picker-value">
<text>{{ form.time || '选择时间' }}</text>
<FaIcon name="clock" color="#94a3b8" :size="15" />
</view>
</picker>
</view>
</view>
<view class="field">
<text class="field-label">提前提醒</text>
<picker :range="minutesLabels" :value="minutesIndex" @change="onMinutesChange">
<view class="picker-value">
<text>{{ minutesLabels[minutesIndex] }}</text>
<FaIcon name="bell" color="#94a3b8" :size="15" />
</view>
</picker>
</view>
<view class="field">
<text class="field-label">提醒渠道 <text class="required">*</text></text>
<view class="channel-grid">
<view
v-for="opt in channelOptions"
:key="opt.value"
class="channel-chip"
:class="{ active: form.remindChannels.includes(opt.value), disabled: opt.disabled }"
@tap="toggleChannel(opt)"
>
<text>{{ opt.label }}</text>
</view>
</view>
<text class="form-tip">可多选,至少选择一个提醒渠道</text>
</view>
<template v-if="hasRepeatChannel">
<view class="field">
<text class="field-label">重复提醒间隔</text>
<picker :range="repeatLabels" :value="repeatIndex" @change="onRepeatChange">
<view class="picker-value">
<text>{{ repeatLabels[repeatIndex] }}</text>
<FaIcon name="rotate" color="#94a3b8" :size="15" />
</view>
</picker>
<text class="form-tip">仅邮件 / Bark 渠道支持重复提醒</text>
</view>
<view v-if="form.repeatIntervalMinutes > 0" class="field">
<text class="field-label">最大发送次数</text>
<picker :range="maxSendLabels" :value="maxSendIndex" @change="onMaxSendChange">
<view class="picker-value">
<text>{{ maxSendLabels[maxSendIndex] }}</text>
<FaIcon name="clock" color="#94a3b8" :size="15" />
</view>
</picker>
</view>
</template>
</view>
<view class="footer">
<u-button
type="primary"
color="#2563eb"
shape="circle"
:loading="saving"
loading-text="保存中..."
text="保存"
@click="onSave"
/>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import {
getSchedule,
createSchedule,
updateSchedule,
REMIND_CHANNEL_OPTIONS,
REMIND_MINUTES_OPTIONS
} from '@/api/schedule.js'
import { formatDate, pad } from '@/utils/date.js'
const reminderId = ref('')
const saving = ref(false)
// 渠道选择:Bark/APP 暂时隐藏,短信暂不可选
const channelOptions = REMIND_CHANNEL_OPTIONS
.filter(opt => opt.value !== 'bark' && opt.value !== 'app')
.map(opt => ({ ...opt, disabled: opt.value === 'sms' }))
const minutesLabels = REMIND_MINUTES_OPTIONS.map(item => item.label)
const REPEAT_OPTIONS = [
{ label: '不重复', value: 0 },
{ label: '每 10 分钟', value: 10 },
{ label: '每 30 分钟', value: 30 },
{ label: '每 1 小时', value: 60 },
{ label: '每 6 小时', value: 360 },
{ label: '每 1 天', value: 1440 }
]
const repeatLabels = REPEAT_OPTIONS.map(item => item.label)
const MAX_SEND_OPTIONS = [1, 2, 3, 5, 10]
const maxSendLabels = MAX_SEND_OPTIONS.map(n => `${n} 次`)
const form = reactive({
content: '',
date: '',
time: '',
remindMinutes: 15,
remindChannels: ['site'],
repeatIntervalMinutes: 0,
maxSendCount: 1
})
const minutesIndex = computed(() => {
const i = REMIND_MINUTES_OPTIONS.findIndex(item => item.value === form.remindMinutes)
return i >= 0 ? i : 2
})
const repeatIndex = computed(() => {
const i = REPEAT_OPTIONS.findIndex(item => item.value === form.repeatIntervalMinutes)
return i >= 0 ? i : 0
})
const maxSendIndex = computed(() => {
const i = MAX_SEND_OPTIONS.indexOf(form.maxSendCount)
return i >= 0 ? i : 0
})
const hasRepeatChannel = computed(() =>
form.remindChannels.some(ch => ch === 'email' || ch === 'bark')
)
onLoad(async (query) => {
const now = new Date()
form.date = formatDate(now)
form.time = `${pad(now.getHours())}:${pad(now.getMinutes())}`
if (query?.id) {
reminderId.value = query.id
uni.setNavigationBarTitle({ title: '编辑日程提醒' })
await loadReminder(query.id)
} else {
uni.setNavigationBarTitle({ title: '新增日程提醒' })
}
})
async function loadReminder(id) {
try {
const item = await getSchedule(id)
if (!item) {
uni.showToast({ title: '日程不存在', icon: 'none' })
setTimeout(() => uni.navigateBack(), 800)
return
}
form.content = item.content || ''
if (item.datetime) {
const d = new Date(item.datetime)
form.date = formatDate(d)
form.time = `${pad(d.getHours())}:${pad(d.getMinutes())}`
}
form.remindMinutes = item.remindMinutes ?? 15
form.remindChannels = (item.remindChannels || [])
.filter(ch => ch !== 'bark' && ch !== 'app')
form.repeatIntervalMinutes = item.repeatIntervalMinutes ?? 0
form.maxSendCount = item.maxSendCount ?? 1
if (!form.remindChannels.length) form.remindChannels = ['site']
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
}
function onDateChange(e) {
form.date = e.detail.value
}
function onTimeChange(e) {
form.time = e.detail.value
}
function onMinutesChange(e) {
form.remindMinutes = REMIND_MINUTES_OPTIONS[Number(e.detail.value)].value
}
function onRepeatChange(e) {
form.repeatIntervalMinutes = REPEAT_OPTIONS[Number(e.detail.value)].value
}
function onMaxSendChange(e) {
form.maxSendCount = MAX_SEND_OPTIONS[Number(e.detail.value)]
}
function toggleChannel(opt) {
if (opt.disabled) {
uni.showToast({ title: '短信提醒暂不可用', icon: 'none' })
return
}
const i = form.remindChannels.indexOf(opt.value)
if (i >= 0) {
form.remindChannels.splice(i, 1)
} else {
form.remindChannels.push(opt.value)
}
}
async function onSave() {
if (saving.value) return
if (!form.content.trim()) {
uni.showToast({ title: '请输入日程内容', icon: 'none' })
return
}
if (!form.date || !form.time) {
uni.showToast({ title: '请选择日程时间', icon: 'none' })
return
}
if (!form.remindChannels.length) {
uni.showToast({ title: '请选择提醒渠道', icon: 'none' })
return
}
saving.value = true
const [y, m, d] = form.date.split('-').map(Number)
const [hh, mm] = form.time.split(':').map(Number)
const payload = {
content: form.content.trim(),
datetime: new Date(y, m - 1, d, hh, mm).getTime(),
remindChannels: form.remindChannels.slice(),
remindMinutes: form.remindMinutes,
repeatIntervalMinutes: form.repeatIntervalMinutes,
maxSendCount: form.maxSendCount
}
try {
if (reminderId.value) {
await updateSchedule(reminderId.value, payload)
} else {
await createSchedule(payload)
}
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => uni.navigateBack(), 400)
} catch (e) {
// request 封装已弹出服务端错误提示
} finally {
saving.value = false
}
}
</script>
<style lang="scss" scoped>
@import '@/src/styles/page-common.scss';
.page {
min-height: 100vh;
background: $color-bg-page;
padding: 24rpx $page-padding-x;
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
}
.form-card {
@include card;
padding: 8rpx 0;
}
.field {
padding: 24rpx 28rpx;
& + & {
border-top: 1rpx solid $color-divider;
}
}
.field-label {
display: block;
font-size: 24rpx;
color: $color-text-muted;
margin-bottom: 12rpx;
}
.required {
color: #f56c6c;
}
.picker-value {
display: flex;
align-items: center;
justify-content: space-between;
height: 72rpx;
padding: 0 20rpx;
background: $color-bg-page;
border-radius: $radius-md;
font-size: 28rpx;
color: $color-text;
}
.dt-row {
display: flex;
gap: 16rpx;
}
.dt-picker {
flex: 1;
min-width: 0;
}
.channel-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.channel-chip {
padding: 12rpx 28rpx;
border-radius: 32rpx;
background: $color-bg-page;
border: 1rpx solid $color-border;
font-size: 26rpx;
color: $color-text;
&.active {
background: rgba(37, 99, 235, 0.08);
border-color: $color-primary;
color: $color-primary;
font-weight: 600;
}
&.disabled {
opacity: 0.45;
}
}
.form-tip {
display: block;
font-size: 22rpx;
color: $color-text-muted;
margin-top: 12rpx;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
gap: 20rpx;
padding: 20rpx $page-padding-x;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: $color-card;
border-top: 1rpx solid $color-border;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04);
}
</style>