优化小程序报销模块

This commit is contained in:
hero920103 committed 2026-10-10 14:24:44 +08:00
1 parent 1e9666cefd
commit 48f3bd997c
21 files changed
+5738 -1750

No files matched your search

+69 -108
View File
@@ -91,42 +91,31 @@
>
<view class="sched-main" @tap="openDetail(item.id)">
<view class="sched-left">
<text class="sched-day">{{ dayLabel(item.datetime) }}</text>
<text class="sched-time">{{ timeText(item.datetime) }}</text>
</view>
<view class="sched-sep" />
<view class="sched-right">
<view class="sched-title-row">
<text class="sched-content">{{ item.content || item.title }}</text>
<view class="status-chip" :class="item.completed ? 'done' : 'pending'">
<text>{{ item.completed ? '已完成' : '待办' }}</text>
<text class="sched-day">{{ dayLabel(item.timestamp) }}</text>
<text class="sched-time">{{ timeRangeText(item) }}</text>
</view>
<view class="sched-sep" />
<view class="sched-right">
<view class="sched-title-row">
<text class="sched-content">{{ item.title }}</text>
<view class="status-chip" :class="item.completed ? 'done' : 'pending'">
<text>{{ item.completed ? '已完成' : '待办' }}</text>
</view>
</view>
<view v-if="item.priority > 0 || item.reminder_id > 0" class="sched-meta">
<text
v-if="item.priority > 0"
class="priority-tag"
:class="item.priority === 2 ? 'urgent' : 'important'"
>{{ item.priority === 2 ? '紧急' : '重要' }}</text>
<FaIcon v-if="item.reminder_id > 0" name="bell" color="#94a3b8" :size="11" />
<text v-if="item.reminder_id > 0">已设提醒</text>
</view>
</view>
<view v-if="item.remindChannels.length" class="sched-meta">
<FaIcon name="bell" color="#94a3b8" :size="11" />
<text>{{ getRemindMinutesLabel(item.remindMinutes) }}</text>
<text v-if="item.repeatIntervalMinutes > 0">· 每 {{ item.repeatIntervalMinutes }} 分钟</text>
</view>
</view>
</view>
<view class="action-row">
<view class="action-btn" @tap="onToggleComplete(item)">
<FaIcon :name="item.completed ? 'rotate-left' : 'check'" color="#8a94ad" :size="12" />
<text>{{ item.completed ? '标为待办' : '标记完成' }}</text>
</view>
<view class="action-btn" @tap="openEdit(item.id)">
<FaIcon name="pen" color="#8a94ad" :size="12" />
<text>编辑</text>
</view>
<view class="action-btn danger" @tap="onDelete(item)">
<FaIcon name="trash" color="#f56c6c" :size="12" />
<text>删除</text>
</view>
</view>
</view>
<view class="bottom-space" />
<view class="bottom-space" />
</view>
</scroll-view>
@@ -139,12 +128,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import {
fetchSchedules,
deleteSchedule,
toggleScheduleComplete,
getRemindMinutesLabel
} from '@/api/schedule.js'
import { fetchOaSchedules } from '@/api/oaSchedule.js'
import { pad, startOfDay, isToday, isTomorrow, dayLabel } from '@/utils/date.js'
import { isLoggedIn } from '@/utils/auth.js'
@@ -209,22 +193,25 @@ const monthCells = computed(() => {
const gridCells = computed(() => (expanded.value ? monthCells.value : weekCells.value))
/** 日程按日期聚合,key 为 YYYY-MM-DD */
/** 日程按日期聚合,key 为 YYYY-MM-DD(OA 日程的 schedule_date) */
const dayMap = computed(() => {
const map = {}
schedules.value.forEach((item) => {
if (!item.datetime) return
const key = dateKey(new Date(item.datetime))
if (!map[key]) map[key] = []
map[key].push(item)
if (!item.schedule_date) return
if (!map[item.schedule_date]) map[item.schedule_date] = []
map[item.schedule_date].push(item)
})
return map
})
/** 选中日期的日程,按时间升序 */
/** 选中日期的日程:全天在前,其余按开始时间升序 */
const dayItems = computed(() => {
const list = dayMap.value[selectedKey.value] || []
return list.slice().sort((a, b) => a.datetime - b.datetime)
return list.slice().sort((a, b) => {
if (a.allDay !== b.allDay) return a.allDay ? -1 : 1
const cmp = String(a.start_time || '').localeCompare(String(b.start_time || ''))
return cmp !== 0 ? cmp : Number(b.id) - Number(a.id)
})
})
/** 本周(周一~周日)范围与日程数 */
@@ -238,11 +225,11 @@ const weekRange = computed(() => {
const weekCount = computed(() => {
const { start, end } = weekRange.value
return schedules.value.filter((item) => {
if (!item.datetime) return false
const d = new Date(item.datetime)
return d >= start && d < end
}).length
const startKey = dateKey(start)
const endKey = dateKey(end)
return schedules.value.filter(
(item) => item.schedule_date >= startKey && item.schedule_date < endKey
).length
})
const heroRangeLabel = computed(() => {
@@ -263,27 +250,44 @@ function dotCount(key) {
return Math.min(3, (dayMap.value[key] || []).length)
}
function timeText(timestamp) {
const d = new Date(timestamp)
return `${pad(d.getHours())}:${pad(d.getMinutes())}`
/** 时间展示:全天显示「全天」,其余显示开始时间或起止区间 */
function timeRangeText(item) {
if (item.allDay) return '全天'
if (!item.start_time) return ''
return item.end_time ? `${item.start_time} - ${item.end_time}` : item.start_time
}
/** 数据加载区间:当前日历月前后各扩 7 天,覆盖周视图跨月格子 */
function currentRange() {
const year = monthCursor.value.getFullYear()
const month = monthCursor.value.getMonth()
const start = new Date(year, month, -6)
const end = new Date(year, month + 1, 7)
return { startDate: dateKey(start), endDate: dateKey(end) }
}
function shiftMonth(step) {
const cur = monthCursor.value
monthCursor.value = new Date(cur.getFullYear(), cur.getMonth() + step, 1)
loadSchedules()
}
function goToday() {
const now = new Date()
monthCursor.value = new Date(now.getFullYear(), now.getMonth(), 1)
selectedDate.value = startOfDay(now)
loadSchedules()
}
function pickDay(cell) {
const monthChanged =
cell.date.getFullYear() !== monthCursor.value.getFullYear() ||
cell.date.getMonth() !== monthCursor.value.getMonth()
selectedDate.value = startOfDay(cell.date)
monthCursor.value = new Date(cell.date.getFullYear(), cell.date.getMonth(), 1)
// 月历里选完日期后自动收回周栏
if (expanded.value) expanded.value = false
if (monthChanged) loadSchedules()
}
onMounted(() => {
@@ -301,8 +305,9 @@ onShow(() => {
async function loadSchedules() {
loading.value = true
try {
// 后端暂不支持按日期区间过滤,取最近一页在端上按月/按天聚合
const res = await fetchSchedules({ pageSize: 100 })
// OA 工作日程:按日历区间拉取(前后各扩 7 天),端上按天聚合
const { startDate, endDate } = currentRange()
const res = await fetchOaSchedules({ startDate, endDate, pageSize: 500 })
schedules.value = res.list
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
@@ -318,42 +323,6 @@ function createSchedule() {
function openDetail(id) {
uni.navigateTo({ url: `/pages/tools/schedule/detail?id=${id}` })
}
function openEdit(id) {
uni.navigateTo({ url: `/pages/tools/schedule/edit?id=${id}` })
}
async function onToggleComplete(item) {
try {
await toggleScheduleComplete(item.id)
await loadSchedules()
uni.showToast({
title: item.completed ? '已标为待办' : '已标记完成',
icon: 'none'
})
} catch (e) {
uni.showToast({ title: '操作失败', icon: 'none' })
}
}
function onDelete(item) {
uni.showModal({
title: '删除日程',
content: '删除后该日程及其提醒将一并移除,确定继续吗?',
confirmText: '删除',
cancelText: '取消',
success: async (res) => {
if (!res.confirm) return
try {
await deleteSchedule(item.id)
uni.showToast({ title: '已删除', icon: 'none' })
await loadSchedules()
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
})
}
</script>
<style lang="scss" scoped>
@@ -688,27 +657,19 @@ function onDelete(item) {
color: $color-text-muted;
}
.action-row {
display: flex;
border-top: 1rpx solid $color-divider;
}
.priority-tag {
padding: 2rpx 12rpx;
border-radius: $radius-full;
font-size: 21rpx;
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 18rpx 0;
font-size: 24rpx;
color: $color-text-secondary;
&:active {
background: $color-bg-page;
&.important {
background: #fff7ed;
color: #ea580c;
}
&.danger {
color: #f56c6c;
&.urgent {
background: #fef2f2;
color: #dc2626;
}
}