diff --git a/app.json b/app.json index 8bded97..47e22b0 100644 --- a/app.json +++ b/app.json @@ -7,11 +7,11 @@ "pages/defaults/index" ], "window": { - "navigationBarBackgroundColor": "#F4F8FE", + "navigationBarBackgroundColor": "#F4F7FB", "navigationBarTextStyle": "black", "navigationBarTitleText": "教学反馈助手", - "backgroundColor": "#F4F8FE", - "backgroundTextStyle": "light" + "backgroundColor": "#F4F7FB", + "backgroundTextStyle": "dark" }, "permission": { "scope.record": { @@ -20,9 +20,9 @@ }, "tabBar": { "custom": true, - "color": "#7B8796", - "selectedColor": "#2B67E8", - "backgroundColor": "#FFFFFF", + "color": "#8E8E93", + "selectedColor": "#007AFF", + "backgroundColor": "#F8FAFC", "borderStyle": "white", "list": [ { "pagePath": "pages/feedback/index", "text": "反馈生成" }, diff --git a/app.wxss b/app.wxss index d42a395..cf43bdb 100644 --- a/app.wxss +++ b/app.wxss @@ -1,15 +1,18 @@ page { min-height: 100%; box-sizing: border-box; - background: #f4f8fe; - color: #17233a; + background: #f4f7fb; + color: #1c1c1e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } view, text, input, -button { +button, +textarea, +picker, +scroll-view { box-sizing: border-box; } @@ -19,50 +22,42 @@ button::after { .page-shell { min-height: 100vh; - padding: 40rpx 32rpx 164rpx; + padding: 44rpx 32rpx 184rpx; } .eyebrow { - color: #2b67e8; - font-size: 28rpx; + color: #007aff; + font-size: 26rpx; font-weight: 700; - line-height: 1.4; + line-height: 1.35; } .page-heading { display: flex; align-items: center; - gap: 14rpx; - margin-top: 12rpx; -} - -.heading-icon { - color: #2b67e8; - font-size: 46rpx; - font-weight: 500; - line-height: 1; + margin-top: 10rpx; } .page-title { - color: #111b32; - font-size: 52rpx; + color: #1c1c1e; + font-size: 50rpx; font-weight: 800; - line-height: 1.2; + line-height: 1.15; } .page-subtitle { - margin-top: 18rpx; - color: #718096; - font-size: 30rpx; - line-height: 1.55; + margin-top: 16rpx; + color: #6e6e73; + font-size: 28rpx; + line-height: 1.5; } .surface { overflow: hidden; - border: 2rpx solid #d7e5f4; + border: 1rpx solid #e5e5ea; border-radius: 16rpx; background: #ffffff; - box-shadow: 0 8rpx 22rpx rgba(45, 78, 120, 0.08); + box-shadow: 0 4rpx 18rpx rgba(60, 60, 67, 0.06); } .primary-button, @@ -76,19 +71,40 @@ button::after { font-size: 30rpx; font-weight: 700; line-height: 1; + transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1); + transform-origin: center; } .primary-button { - border: 2rpx solid #2b67e8; - background: #2b67e8; + border: 0; + background: #007aff; color: #ffffff; } .secondary-button, .icon-button { - border: 2rpx solid #c9dced; + border: 1rpx solid #d1d1d6; background: #ffffff; - color: #2b67e8; + color: #007aff; +} + +.primary-button:active, +.secondary-button:active, +.icon-button:active { + transform: scale(0.98); +} + +.primary-button:active { + background: #006ee6; +} + +.secondary-button:active, +.icon-button:active { + background: #f2f7ff; +} + +button[disabled]:active { + transform: none; } .button-icon { @@ -98,21 +114,21 @@ button::after { } .helper-text { - color: #7b8796; + color: #6e6e73; font-size: 26rpx; line-height: 1.55; } .field-label { - color: #2b67e8; - font-size: 28rpx; + color: #6e6e73; + font-size: 26rpx; font-weight: 700; } .field-value { - margin-top: 18rpx; - color: #17233a; - font-size: 32rpx; + margin-top: 14rpx; + color: #1c1c1e; + font-size: 30rpx; font-weight: 700; } @@ -123,9 +139,9 @@ button::after { gap: 20rpx; margin-top: 24rpx; padding: 20rpx 22rpx; - border: 2rpx solid #efc7c7; + border: 1rpx solid #f1c4c4; border-radius: 12rpx; - background: #fff7f7; + background: #fff2f2; color: #a83d3d; font-size: 25rpx; line-height: 1.45; @@ -133,13 +149,27 @@ button::after { .status-action { flex: none; - color: #2b67e8; + color: #007aff; font-weight: 700; } .loading-state { padding: 70rpx 32rpx; - color: #718096; + color: #6e6e73; font-size: 28rpx; text-align: center; } + +@media (prefers-reduced-motion: reduce) { + .primary-button, + .secondary-button, + .icon-button { + transition: none; + } + + .primary-button:active, + .secondary-button:active, + .icon-button:active { + transform: none; + } +} diff --git a/custom-tab-bar/index.wxss b/custom-tab-bar/index.wxss index 3a03f73..6b09d89 100644 --- a/custom-tab-bar/index.wxss +++ b/custom-tab-bar/index.wxss @@ -7,8 +7,10 @@ display: flex; min-height: 112rpx; padding-bottom: env(safe-area-inset-bottom); - border-top: 1rpx solid #edf1f6; - background: #ffffff; + border-top: 1rpx solid rgba(209, 209, 214, 0.72); + background: rgba(248, 250, 252, 0.92); + -webkit-backdrop-filter: blur(24rpx) saturate(180%); + backdrop-filter: blur(24rpx) saturate(180%); } .tab-item { @@ -19,11 +21,17 @@ justify-content: center; gap: 6rpx; min-height: 112rpx; - color: #8995a5; + color: #8e8e93; + transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1); + transform-origin: center; } .tab-item-active { - color: #2b67e8; + color: #007aff; +} + +.tab-item:active { + transform: scale(0.96); } .tab-glyph { @@ -36,3 +44,21 @@ font-size: 24rpx; line-height: 1.2; } + +@media (prefers-reduced-motion: reduce) { + .tab-item { + transition: none; + } + + .tab-item:active { + transform: none; + } +} + +@media (prefers-reduced-transparency: reduce) { + .tabbar { + background: #f8fafc; + -webkit-backdrop-filter: none; + backdrop-filter: none; + } +} diff --git a/pages/defaults/index.json b/pages/defaults/index.json index 02d30dd..879fa2e 100644 --- a/pages/defaults/index.json +++ b/pages/defaults/index.json @@ -1,3 +1,3 @@ { - "navigationBarTitleText": "学生档案预设项" + "navigationBarTitleText": "设置预设项" } diff --git a/pages/defaults/index.ts b/pages/defaults/index.ts index 5b01e18..93ac5d6 100644 --- a/pages/defaults/index.ts +++ b/pages/defaults/index.ts @@ -3,6 +3,10 @@ import type { StudentProfileDefaults } from '../../utils/types' const grades = ['初一', '初二', '初三'] const subjects = ['美术', '书法', '音乐', '舞蹈', '语文', '数学', '英语'] +const durationPresets = [45, 60, 90, 120] +const durationStep = 15 +const minimumDuration = 15 +const maximumDuration = 360 const initialDefaults: StudentProfileDefaults = { grade: '初一', subject: '数学', @@ -10,11 +14,10 @@ const initialDefaults: StudentProfileDefaults = { } type PickerEvent = { detail: { value: string | number } } -type InputEvent = { detail: { value: string } } function normalizeDuration(value: number): number { if (Number.isNaN(value)) return 60 - return Math.min(360, Math.max(15, Math.round(value / 15) * 15)) + return Math.min(maximumDuration, Math.max(minimumDuration, Math.round(value / durationStep) * durationStep)) } Page({ @@ -22,6 +25,9 @@ Page({ defaults: initialDefaults, grades, subjects, + durationPresets, + minimumDuration, + maximumDuration, loading: false, saving: false, errorMessage: '' @@ -34,7 +40,13 @@ Page({ async loadDefaults(showSuccess = false) { this.setData({ loading: true, errorMessage: '' }) try { - this.setData({ defaults: await getProfileDefaults() }) + const defaults = await getProfileDefaults() + this.setData({ + defaults: { + ...defaults, + lessonDurationMinutes: normalizeDuration(defaults.lessonDurationMinutes) + } + }) if (showSuccess) wx.showToast({ title: '预设已刷新', icon: 'success' }) } catch (error) { this.setData({ errorMessage: getApiErrorMessage(error) }) @@ -55,12 +67,25 @@ Page({ this.setData({ 'defaults.subject': subjects[Number(event.detail.value)] }) }, - onDurationInput(event: InputEvent) { - this.setData({ 'defaults.lessonDurationMinutes': normalizeDuration(Number(event.detail.value)) }) + decreaseDuration() { + this.setData({ + 'defaults.lessonDurationMinutes': normalizeDuration( + this.data.defaults.lessonDurationMinutes - durationStep + ) + }) }, - onDurationChange(event: PickerEvent) { - this.setData({ 'defaults.lessonDurationMinutes': normalizeDuration(Number(event.detail.value)) }) + increaseDuration() { + this.setData({ + 'defaults.lessonDurationMinutes': normalizeDuration( + this.data.defaults.lessonDurationMinutes + durationStep + ) + }) + }, + + selectDurationPreset(event: WechatMiniprogram.TouchEvent) { + const { value } = event.currentTarget.dataset as { value: number | string } + this.setData({ 'defaults.lessonDurationMinutes': normalizeDuration(Number(value)) }) }, async save() { diff --git a/pages/defaults/index.wxml b/pages/defaults/index.wxml index 2c317af..6191c45 100644 --- a/pages/defaults/index.wxml +++ b/pages/defaults/index.wxml @@ -3,12 +3,13 @@ 档案预设 - - 学生档案预设项 + 新增学生默认值 - 用于新增学生时自动带出 + 新增学生时自动带出以下信息 - + @@ -18,41 +19,70 @@ - - 年级 - {{defaults.grade}} + + + + 年级 + {{defaults.grade}} + + + - - 学科 - {{defaults.subject}} + + + + 学科 + {{defaults.subject}} + + + - 上课时长 - - - 分钟 + + 上课时长 + 15 分钟间隔 - - - 15 分钟 - 360 分钟 + + + + + {{defaults.lessonDurationMinutes}} + 分钟 + + + + + + - 新增学生时会按开始时间自动推算结束时间。 - + + + 新增学生时会根据开始时间自动推算结束时间 + + + + diff --git a/pages/defaults/index.wxss b/pages/defaults/index.wxss index 8cb586c..4ac2562 100644 --- a/pages/defaults/index.wxss +++ b/pages/defaults/index.wxss @@ -1,5 +1,5 @@ .defaults-page { - padding-top: 38rpx; + padding-top: 44rpx; } .top-row { @@ -27,93 +27,287 @@ } .reload-button { + display: flex; flex: none; - width: 148rpx; - min-width: 148rpx; - max-width: 148rpx; - min-height: 70rpx; + align-items: center; + justify-content: center; + width: 72rpx; + min-width: 72rpx; + max-width: 72rpx; + height: 72rpx; margin: 0; padding: 0; - font-size: 26rpx; + border: 0; + border-radius: 50%; + background: #f1f3f6; + color: #007aff; + line-height: 1; + transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.reload-button:active { + transform: scale(0.94); + background: #e5e5ea; +} + +.reload-button[disabled] { + opacity: 1; +} + +.reload-icon { + display: block; + font-size: 36rpx; + line-height: 1; +} + +.reload-button-loading .reload-icon { + animation: reload-spin 800ms linear infinite; +} + +@keyframes reload-spin { + to { + transform: rotate(360deg); + } } .defaults-card { - margin-top: 34rpx; - padding: 24rpx; + margin-top: 28rpx; + padding: 0; } .defaults-grid { display: flex; - gap: 16rpx; + gap: 0; } .defaults-grid .select-field { - width: calc((100% - 16rpx) / 2); + width: 50%; } .select-field, .duration-field { - padding: 24rpx; - border: 2rpx solid #d8e5f2; - border-radius: 14rpx; - background: #fbfdff; + background: transparent; } .select-field { - min-height: 158rpx; + min-height: 112rpx; + transition: opacity 140ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.select-field-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14rpx; + min-height: 112rpx; + padding: 18rpx 20rpx; +} + +.select-field + .select-field { + border-left: 1rpx solid #e5e5ea; +} + +.select-field:active { + opacity: 0.68; +} + +.select-copy { + min-width: 0; +} + +.select-field .field-label, +.select-field .field-value { + display: block; +} + +.select-field .field-label { + color: #6e6e73; + font-size: 22rpx; + line-height: 1.25; +} + +.select-field .field-value { + margin-top: 7rpx; + color: #1c1c1e; + font-size: 29rpx; + font-weight: 700; + line-height: 1.25; +} + +.select-chevron { + flex: none; + color: #8e8e93; + font-size: 36rpx; + line-height: 1; } .duration-field { - margin-top: 16rpx; + padding: 24rpx; + border-top: 1rpx solid #e5e5ea; } -.duration-input-row { +.duration-heading { display: flex; align-items: center; - gap: 12rpx; - margin-top: 20rpx; + justify-content: space-between; + gap: 16rpx; } -.duration-input { - width: 150rpx; - height: 64rpx; - padding: 0 16rpx; - border: 2rpx solid #bed4e8; - border-radius: 12rpx; - background: #ffffff; - color: #17233a; +.duration-step-label { + color: #8e8e93; + font-size: 22rpx; +} + +.duration-stepper { + display: grid; + grid-template-columns: 72rpx minmax(0, 1fr) 72rpx; + width: 330rpx; + height: 76rpx; + margin: 18rpx auto 0; + overflow: hidden; + border: 1rpx solid #e5e5ea; + border-radius: 14rpx; + background: #f1f3f6; +} + +.duration-step-button { + display: flex; + align-items: center; + justify-content: center; + width: 72rpx; + min-width: 72rpx; + max-width: 72rpx; + height: 76rpx; + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: #007aff; font-size: 38rpx; + font-weight: 500; + line-height: 1; + transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.duration-step-button:active { + transform: scale(0.9); +} + +.duration-step-button[disabled] { + color: #c7c7cc; + opacity: 1; +} + +.duration-value { + display: flex; + align-items: center; + justify-content: center; + gap: 8rpx; + min-width: 0; + border-right: 1rpx solid #d1d1d6; + border-left: 1rpx solid #d1d1d6; +} + +.duration-number { + color: #1c1c1e; + font-size: 34rpx; font-weight: 800; - text-align: center; + line-height: 1; } .duration-unit { - color: #65758a; - font-size: 28rpx; + color: #6e6e73; + font-size: 22rpx; } -.duration-slider { - margin: 12rpx -12rpx 0; -} - -.duration-bounds { +.duration-presets { display: flex; - justify-content: space-between; - color: #8a97a6; - font-size: 23rpx; + align-items: stretch; + gap: 4rpx; + margin-top: 18rpx; + padding: 4rpx; + border-radius: 12rpx; + background: #f1f3f6; +} + +.duration-preset { + display: flex; + flex: 1 1 0; + align-items: center; + justify-content: center; + width: 0; + min-width: 0; + max-width: none; + min-height: 58rpx; + margin: 0; + padding: 0; + border: 0; + border-radius: 9rpx; + background: transparent; + color: #6e6e73; + font-size: 24rpx; + font-weight: 600; + line-height: 1; + transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.duration-preset-active { + background: #ffffff; + box-shadow: 0 2rpx 8rpx rgba(60, 60, 67, 0.12); + color: #007aff; +} + +.duration-preset:active { + transform: scale(0.96); } .time-help { - margin-top: 18rpx; - padding: 20rpx; - border: 2rpx dashed #c6ddea; - border-radius: 12rpx; - color: #65758a; - font-size: 25rpx; + display: flex; + align-items: flex-start; + gap: 10rpx; + margin: 0 24rpx; + padding: 2rpx 0 0; + color: #6e6e73; + font-size: 24rpx; line-height: 1.5; } -.save-defaults { - width: 420rpx; - margin: 26rpx auto 4rpx; +.time-help-icon { + flex: none; + margin-top: 3rpx; +} + +.save-area { + padding: 24rpx; +} + +.save-defaults { + width: 100%; + min-width: 0; + max-width: none; + margin: 0; +} + +@media (prefers-reduced-motion: reduce) { + .reload-button, + .select-field, + .duration-step-button, + .duration-preset { + transition: none; + } + + .reload-button:active, + .duration-step-button:active, + .duration-preset:active { + transform: none; + } + + .select-field:active { + opacity: 1; + } + + .reload-button-loading .reload-icon { + animation: none; + } } diff --git a/pages/feedback/index.wxml b/pages/feedback/index.wxml index 1b81878..1550255 100644 --- a/pages/feedback/index.wxml +++ b/pages/feedback/index.wxml @@ -1,7 +1,6 @@