主要更新uniapp相关功能

This commit is contained in:
hero920103 committed 2026-10-07 00:59:05 +08:00
1 parent aa92ca40a2
commit 5b4dbb2b46
65 files changed
+8532 -1921

No files matched your search

+441
View File
@@ -0,0 +1,441 @@
<template>
<view class="page">
<!-- 本周概览 -->
<view class="hero-card">
<view class="hero-left">
<text class="hero-sub">查看每周日程提醒</text>
<view class="hero-num-row">
<text class="hero-label">本周日程(条)</text>
<text class="hero-num">{{ weekItems.length }}</text>
</view>
</view>
<view class="hero-illus">
<FaIcon name="bell" color="#ffffff" :size="26" />
</view>
</view>
<!-- 周历条 -->
<view class="week-card">
<view
v-for="d in weekDays"
:key="d.key"
class="week-item"
@tap="pickWeekDay(d)"
>
<text class="week-name">{{ d.name }}</text>
<view
class="week-date"
:class="{ today: d.isToday, selected: d.key === selectedKey }"
>{{ d.day }}</view>
<view class="week-dot" :class="{ show: d.count > 0 }" />
</view>
</view>
<!-- 我的日程 -->
<view class="section-head">
<text class="section-title">我的日程({{ shownItems.length }})</text>
<view class="range-tabs">
<text class="range-tab" :class="{ active: range === 'day' }" @tap="showToday">今日</text>
<text class="range-sep">|</text>
<text class="range-tab" :class="{ active: range === 'week' }" @tap="range = 'week'">本周</text>
</view>
</view>
<u-empty
v-if="!loading && shownItems.length === 0"
mode="list"
:icon-size="72"
:text="range === 'week' ? '本周暂无日程提醒' : '当天暂无日程提醒'"
/>
<view v-else class="rem-list">
<view v-for="item in shownItems" :key="item.id" class="rem-card" @tap="openDetail(item.id)">
<view class="rem-time">
<text class="rem-day">{{ shortDayLabel(item.datetime) }}</text>
<text class="rem-clock">{{ timeText(item.datetime) }}</text>
</view>
<view class="rem-body">
<text class="rem-title">{{ item.content || item.title }}</text>
<view class="rem-meta">
<FaIcon name="clock" color="#94a3b8" :size="11" />
<text class="rem-meta-text">{{ fullTime(item.datetime) }}</text>
</view>
<view class="rem-meta">
<FaIcon name="bell" color="#94a3b8" :size="11" />
<text class="rem-meta-text">{{ remindLine(item) }}</text>
</view>
</view>
</view>
</view>
<view class="bottom-space" />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { fetchSchedules, getRemindChannelLabel, getRemindMinutesLabel } from '@/api/schedule.js'
import { pad, startOfDay, isToday } from '@/utils/date.js'
import { isLoggedIn } from '@/utils/auth.js'
/** 周一为一周第一天 */
const WEEK_LABELS = ['一', '二', '三', '四', '五', '六', '日']
const schedules = ref([])
const loading = ref(false)
const range = ref('day')
const selectedDate = ref(startOfDay(new Date()))
function dateKey(value) {
const d = value instanceof Date ? value : new Date(value)
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
}
function startOfWeek(base = new Date()) {
const d = startOfDay(base)
const offset = (d.getDay() + 6) % 7
return new Date(d.getFullYear(), d.getMonth(), d.getDate() - offset)
}
const weekStart = computed(() => startOfWeek(selectedDate.value))
const selectedKey = computed(() => dateKey(selectedDate.value))
/** 只保留已设置提醒渠道的日程 */
const reminders = computed(() =>
schedules.value.filter((item) => item.remindChannels && item.remindChannels.length > 0)
)
/** 提醒按日期聚合,key 为 YYYY-MM-DD */
const reminderMap = computed(() => {
const map = {}
reminders.value.forEach((item) => {
if (!item.datetime) return
const key = dateKey(new Date(item.datetime))
if (!map[key]) map[key] = []
map[key].push(item)
})
return map
})
const weekDays = computed(() => {
const base = weekStart.value
return Array.from({ length: 7 }, (_, i) => {
const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + i)
const key = dateKey(d)
return {
key,
date: d,
name: '周' + WEEK_LABELS[i],
day: d.getDate(),
isToday: isToday(d),
count: (reminderMap.value[key] || []).length
}
})
})
/** 本周(周一~周日)的提醒,按时间升序 */
const weekItems = computed(() => {
const base = weekStart.value
const startTs = base.getTime()
const endTs = startTs + 7 * 24 * 60 * 60 * 1000
return reminders.value
.filter((item) => item.datetime >= startTs && item.datetime < endTs)
.sort((a, b) => a.datetime - b.datetime)
})
const shownItems = computed(() => {
if (range.value === 'week') return weekItems.value
return (reminderMap.value[selectedKey.value] || [])
.slice()
.sort((a, b) => a.datetime - b.datetime)
})
function timeText(timestamp) {
const d = new Date(timestamp)
return `${pad(d.getHours())}:${pad(d.getMinutes())}`
}
function fullTime(timestamp) {
const d = new Date(timestamp)
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${timeText(timestamp)}`
}
function shortDayLabel(timestamp) {
if (isToday(timestamp)) return '今天'
const d = new Date(timestamp)
return '周' + WEEK_LABELS[(d.getDay() + 6) % 7]
}
function remindLine(item) {
const channels = (item.remindChannels || [])
.map((ch) => getRemindChannelLabel(ch))
.join('、')
const parts = [getRemindMinutesLabel(item.remindMinutes)]
if (channels) parts.push(channels)
return parts.join(' · ')
}
function pickWeekDay(d) {
selectedDate.value = startOfDay(d.date)
range.value = 'day'
}
function showToday() {
selectedDate.value = startOfDay(new Date())
range.value = 'day'
}
function openDetail(id) {
uni.navigateTo({ url: `/pages/tools/schedule/detail?id=${id}` })
}
onMounted(() => {
if (!isLoggedIn()) {
uni.reLaunch({ url: '/pages/login/login' })
return
}
loadReminders()
})
async function loadReminders() {
loading.value = true
try {
const res = await fetchSchedules({ pageSize: 100 })
schedules.value = res.list
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
@import '@/src/styles/page-common.scss';
.page {
min-height: 100vh;
padding: 24rpx $page-padding-x 0;
background: $color-bg-page;
box-sizing: border-box;
}
/* ---------- 本周概览 ---------- */
.hero-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-radius: $radius-lg;
background: linear-gradient(135deg, #1e3a8a, #2563eb);
box-shadow: 0 12rpx 30rpx rgba(37, 99, 235, 0.28);
}
.hero-left {
flex: 1;
min-width: 0;
}
.hero-sub {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.78);
}
.hero-num-row {
display: flex;
align-items: baseline;
gap: 16rpx;
margin-top: 12rpx;
}
.hero-label {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
.hero-num {
font-size: 56rpx;
font-weight: 700;
color: #ffffff;
line-height: 1;
}
.hero-illus {
flex-shrink: 0;
width: 96rpx;
height: 96rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.18);
display: flex;
align-items: center;
justify-content: center;
}
/* ---------- 周历条 ---------- */
.week-card {
display: flex;
margin-top: 20rpx;
padding: 20rpx 8rpx 16rpx;
background: $color-card;
border-radius: $radius-lg;
box-shadow: $shadow-card;
}
.week-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.week-name {
font-size: 22rpx;
color: $color-text-muted;
}
.week-date {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: $color-text;
&.today {
color: $color-primary;
font-weight: 600;
}
&.selected {
background: $color-primary;
color: #ffffff;
font-weight: 600;
}
}
.week-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: transparent;
&.show {
background: $color-primary;
}
}
/* ---------- 我的日程 ---------- */
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
margin: 32rpx 0 16rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: $color-text;
}
.range-tabs {
display: flex;
align-items: center;
gap: 14rpx;
}
.range-tab {
font-size: 26rpx;
color: $color-text-muted;
&.active {
color: $color-primary;
font-weight: 600;
}
}
.range-sep {
font-size: 22rpx;
color: $color-border;
}
.rem-list {
display: flex;
flex-direction: column;
}
.rem-card {
@include card;
display: flex;
gap: 24rpx;
padding: 26rpx 24rpx;
margin-bottom: 16rpx;
&:active {
background: $color-bg-page;
}
}
.rem-time {
flex-shrink: 0;
width: 110rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6rpx;
}
.rem-day {
font-size: 24rpx;
color: $color-text-muted;
}
.rem-clock {
font-size: 32rpx;
font-weight: 700;
color: $color-text;
}
.rem-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10rpx;
border-left: 1rpx solid $color-divider;
padding-left: 24rpx;
}
.rem-title {
font-size: 29rpx;
font-weight: 600;
color: $color-text;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.rem-meta {
display: flex;
align-items: center;
gap: 10rpx;
}
.rem-meta-text {
flex: 1;
min-width: 0;
font-size: 23rpx;
color: $color-text-muted;
word-break: break-all;
}
.bottom-space {
height: 60rpx;
}
</style>