优化小程序报销模块
This commit is contained in:
1 parent
1e9666cefd
commit
48f3bd997c
21 files changed
+5738
-1750
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user