1
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 54s
Docker Build & Deploy / Deploy to Production (push) Successful in 9s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 54s
Docker Build & Deploy / Deploy to Production (push) Successful in 9s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
This commit is contained in:
325
.sisyphus/notepads/calendar-refactor/learnings.md
Normal file
325
.sisyphus/notepads/calendar-refactor/learnings.md
Normal file
@@ -0,0 +1,325 @@
|
||||
|
||||
## Vue 3 Composition API Research - Modular Architecture Best Practices
|
||||
|
||||
### 研究日期: 2026-02-03
|
||||
|
||||
---
|
||||
|
||||
## 1. 官方 Vue 3 组件组织原则
|
||||
|
||||
### 1.1 Composables 用于代码组织
|
||||
来源: Vue 官方文档 - https://vuejs.org/guide/reusability/composables
|
||||
|
||||
**核心原则:**
|
||||
- Composables 不仅用于复用,也用于**代码组织**
|
||||
- 当组件变得过于复杂时,应该将逻辑按**关注点分离**提取到更小的函数中
|
||||
- 可以将提取的 composables 视为**组件级别的服务**,它们可以相互通信
|
||||
|
||||
**官方示例模式:**
|
||||
```vue
|
||||
<script setup>
|
||||
import { useFeatureA } from './featureA.js'
|
||||
import { useFeatureB } from './featureB.js'
|
||||
import { useFeatureC } from './featureC.js'
|
||||
|
||||
const { foo, bar } = useFeatureA()
|
||||
const { baz } = useFeatureB(foo)
|
||||
const { qux } = useFeatureC(baz)
|
||||
</script>
|
||||
```
|
||||
|
||||
**关键洞察:**
|
||||
- Composables 应返回**普通对象**包含多个 refs,保持响应式
|
||||
- 避免返回 reactive 对象,因为解构会失去响应性
|
||||
- Composables 可以接收其他 composables 的返回值作为参数
|
||||
|
||||
---
|
||||
|
||||
## 2. 代码分割与懒加载
|
||||
|
||||
### 2.1 defineAsyncComponent 用于模块懒加载
|
||||
来源: Vue 官方文档 - https://github.com/vuejs/docs/blob/main/src/guide/best-practices/performance.md
|
||||
|
||||
**适用场景:**
|
||||
- 将大型组件树分割成独立的 chunks
|
||||
- 仅在组件渲染时才加载,改善初始加载时间
|
||||
|
||||
```js
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
// Foo.vue 及其依赖被单独打包成一个 chunk
|
||||
// 只有在组件被渲染时才会按需获取
|
||||
const Foo = defineAsyncComponent(() => import('./Foo.vue'))
|
||||
```
|
||||
|
||||
### 2.2 动态导入用于 JS 代码分割
|
||||
```js
|
||||
// lazy.js 及其依赖会被分割成单独的 chunk
|
||||
// 只在 loadLazy() 被调用时才加载
|
||||
function loadLazy() {
|
||||
return import('./lazy.js')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 真实世界的模块化架构模式
|
||||
|
||||
### 3.1 Dashboard 模块化架构 - 成功案例
|
||||
|
||||
**案例 1: Soybean Admin (MIT License)**
|
||||
来源: https://github.com/soybeanjs/soybean-admin/blob/main/src/views/home/index.vue
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import HeaderBanner from './modules/header-banner.vue';
|
||||
import CardData from './modules/card-data.vue';
|
||||
import LineChart from './modules/line-chart.vue';
|
||||
import PieChart from './modules/pie-chart.vue';
|
||||
import ProjectNews from './modules/project-news.vue';
|
||||
import CreativityBanner from './modules/creativity-banner.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
</script>
|
||||
```
|
||||
|
||||
**架构特点:**
|
||||
- Index.vue 作为**容器组件**,只负责布局和响应式计算
|
||||
- 每个 modules/*.vue 是**独立的功能模块**
|
||||
- 模块命名清晰: header-banner, card-data, line-chart 等
|
||||
- 使用 Pinia store 进行状态共享
|
||||
|
||||
**案例 2: Art Design Pro (MIT License)**
|
||||
来源: https://github.com/Daymychen/art-design-pro/blob/main/src/views/dashboard/ecommerce/index.vue
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import Banner from './modules/banner.vue'
|
||||
import TotalOrderVolume from './modules/total-order-volume.vue'
|
||||
import TotalProducts from './modules/total-products.vue'
|
||||
import SalesTrend from './modules/sales-trend.vue'
|
||||
import SalesClassification from './modules/sales-classification.vue'
|
||||
import TransactionList from './modules/transaction-list.vue'
|
||||
import HotCommodity from './modules/hot-commodity.vue'
|
||||
import RecentTransaction from './modules/recent-transaction.vue'
|
||||
import AnnualSales from './modules/annual-sales.vue'
|
||||
import ProductSales from './modules/product-sales.vue'
|
||||
import SalesGrowth from './modules/sales-growth.vue'
|
||||
import CartConversionRate from './modules/cart-conversion-rate.vue'
|
||||
import HotProductsList from './modules/hot-products-list.vue'
|
||||
|
||||
defineOptions({ name: 'Ecommerce' })
|
||||
</script>
|
||||
```
|
||||
|
||||
**架构特点:**
|
||||
- 电商 dashboard 包含 13 个独立模块
|
||||
- 每个模块代表一个业务功能卡片
|
||||
- Index.vue **不传递数据**,模块自治
|
||||
|
||||
---
|
||||
|
||||
## 4. 模块间通信模式
|
||||
|
||||
### 4.1 defineEmits 用于子到父通信
|
||||
来源: Vue 核心仓库 - https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiSetupHelpers.ts
|
||||
|
||||
**TypeScript 类型声明模式:**
|
||||
```ts
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string];
|
||||
'change': [event: Event];
|
||||
'custom-event': [payload: CustomPayload];
|
||||
}>();
|
||||
```
|
||||
|
||||
**Runtime 声明模式:**
|
||||
```js
|
||||
const emit = defineEmits(['change', 'update'])
|
||||
```
|
||||
|
||||
### 4.2 Props 模式 - 数据传递 vs 自取数据
|
||||
|
||||
**案例研究: Halo CMS (GPL-3.0)**
|
||||
来源: https://github.com/halo-dev/halo/blob/main/ui/console-src/modules/system/users/components/GrantPermissionModal.vue
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useFetchRoles, useFetchRoleTemplates } from "../composables/use-role";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
user?: User;
|
||||
}>(),
|
||||
{
|
||||
user: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
// 模块自己获取数据
|
||||
onMounted(async () => {
|
||||
await fetchRoles();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**模式总结:**
|
||||
- **Props 传递身份标识** (如 user ID),而非完整数据
|
||||
- **模块自己获取详细数据** (通过 composables)
|
||||
- 这样保持模块的**高内聚低耦合**
|
||||
|
||||
---
|
||||
|
||||
## 5. 何时模块应该自取数据 vs 接收 Props
|
||||
|
||||
### 5.1 自取数据的场景
|
||||
- 模块是**独立的业务单元**(如日历、统计卡片)
|
||||
- 数据获取逻辑属于模块内部关注点
|
||||
- 模块需要**定期刷新**或**重新加载**数据
|
||||
- 多个平行模块各自管理自己的状态
|
||||
|
||||
**示例:**
|
||||
```vue
|
||||
<!-- 统计卡片模块 - 自己获取数据 -->
|
||||
<script setup lang="ts">
|
||||
const { data, loading } = useBudgetStats()
|
||||
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 5.2 接收 Props 的场景
|
||||
- 模块是**展示组件**(Presentational Component)
|
||||
- 父组件需要**协调多个子组件**的数据
|
||||
- 数据来源于**全局状态管理**(如 Pinia store)
|
||||
- 需要在父组件层面做**数据聚合或转换**
|
||||
|
||||
**示例:**
|
||||
```vue
|
||||
<!-- 数据展示组件 - 接收 props -->
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
stats: BudgetStats
|
||||
loading: boolean
|
||||
}>()
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. TypeScript vs JavaScript 在 Vue 3 项目中
|
||||
|
||||
### 6.1 EmailBill 项目的选择
|
||||
**当前状况:**
|
||||
- ESLint 配置中禁用了 TypeScript 规则
|
||||
- 使用 `<script setup lang="ts">` 但不强制类型检查
|
||||
- 轻量级类型提示,不追求严格类型安全
|
||||
|
||||
**何时避免 TypeScript:**
|
||||
- 小型项目,团队更熟悉 JavaScript
|
||||
- 快速原型开发
|
||||
- 避免 TypeScript 配置和类型定义的复杂度
|
||||
- 保持构建速度和开发体验的流畅
|
||||
|
||||
**何时使用 TypeScript:**
|
||||
- 大型团队协作
|
||||
- 复杂的状态管理和数据流
|
||||
- 需要严格的 API 契约
|
||||
- 长期维护的企业级应用
|
||||
|
||||
---
|
||||
|
||||
## 7. 模块化架构的最佳实践总结
|
||||
|
||||
### 7.1 目录结构推荐
|
||||
```
|
||||
views/
|
||||
calendar/
|
||||
Index.vue # 容器组件,布局和协调
|
||||
modules/
|
||||
CalendarView.vue # 日历展示模块(自取数据)
|
||||
MonthlyStats.vue # 月度统计模块(自取数据)
|
||||
QuickActions.vue # 快捷操作模块(事件驱动)
|
||||
composables/
|
||||
useCalendarData.ts # 日历数据获取逻辑
|
||||
useMonthlyStats.ts # 统计数据获取逻辑
|
||||
```
|
||||
|
||||
### 7.2 组件职责划分
|
||||
|
||||
**Index.vue (容器组件):**
|
||||
- 布局管理和响应式设计
|
||||
- 协调模块间的通信(如果需要)
|
||||
- 全局状态初始化
|
||||
- **不应包含业务逻辑**
|
||||
|
||||
**modules/*.vue (功能模块):**
|
||||
- 独立的业务功能单元
|
||||
- 自己管理数据获取和状态
|
||||
- 通过 emits 向父组件通信
|
||||
- 高内聚,低耦合
|
||||
|
||||
**composables/*.ts (可复用逻辑):**
|
||||
- 数据获取逻辑
|
||||
- 业务规则计算
|
||||
- 状态管理辅助
|
||||
- 可在多个组件间共享
|
||||
|
||||
### 7.3 通信模式推荐
|
||||
|
||||
**模块向上通信 (Child → Parent):**
|
||||
```ts
|
||||
const emit = defineEmits<{
|
||||
'date-changed': [date: Date]
|
||||
'item-clicked': [item: CalendarItem]
|
||||
}>()
|
||||
```
|
||||
|
||||
**模块间通信 (Sibling ↔ Sibling):**
|
||||
- 通过**父组件中转**事件
|
||||
- 或使用**全局事件总线**(如 mitt)
|
||||
- 或使用**Pinia store** 共享状态
|
||||
|
||||
---
|
||||
|
||||
## 8. 关键洞察和建议
|
||||
|
||||
### 8.1 高内聚模块设计
|
||||
- 每个模块应该是**自治的**,包含自己的数据获取、状态管理和事件处理
|
||||
- Index.vue 应该是**轻量级的协调者**,而非数据的中央枢纽
|
||||
|
||||
### 8.2 Props vs 自取数据的平衡
|
||||
- **身份标识和配置通过 props** (如 userId, date, theme)
|
||||
- **业务数据通过模块自取** (如 stats, calendar items)
|
||||
|
||||
### 8.3 避免过度抽象
|
||||
- 不要为了复用而复用
|
||||
- 优先考虑**代码的清晰度**而非极致的 DRY
|
||||
- Composables 应该解决**真实的重复问题**,而非预测性的抽象
|
||||
|
||||
---
|
||||
|
||||
## 9. 参考资源
|
||||
|
||||
**官方文档:**
|
||||
- Vue 3 Composables: https://vuejs.org/guide/reusability/composables
|
||||
- Vue 3 Performance: https://github.com/vuejs/docs/blob/main/src/guide/best-practices/performance.md
|
||||
- Vue 3 State Management: https://vuejs.org/guide/scaling-up/state-management
|
||||
|
||||
**真实项目参考:**
|
||||
- Soybean Admin: https://github.com/soybeanjs/soybean-admin (MIT)
|
||||
- Art Design Pro: https://github.com/Daymychen/art-design-pro (MIT)
|
||||
- Halo CMS: https://github.com/halo-dev/halo (GPL-3.0)
|
||||
- DataEase: https://github.com/dataease/dataease (GPL-3.0)
|
||||
|
||||
36
.sisyphus/notepads/date-navigation/learnings.md
Normal file
36
.sisyphus/notepads/date-navigation/learnings.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Date Navigation Implementation Learnings
|
||||
|
||||
## Implementation Details
|
||||
- Added left/right arrow navigation to both `StatisticsView.vue` and `BudgetView.vue`
|
||||
- Used `<van-icon name="arrow-left" />` and `<van-icon name="arrow" />` for directional arrows
|
||||
- Text center remains clickable for date picker popup
|
||||
- Arrows use `@click.stop` to prevent event propagation
|
||||
|
||||
## StatisticsView.vue
|
||||
- `navigateDate(direction)` method handles both month and year modes
|
||||
- Month mode: handles year boundaries (Jan -> Dec of previous year, Dec -> Jan of next year)
|
||||
- Year mode: increments/decrements year directly
|
||||
- Resets `showAllExpense` state on navigation
|
||||
|
||||
## BudgetView.vue
|
||||
- `navigateDate(direction)` uses native Date object manipulation
|
||||
- `setMonth()` automatically handles month/year boundaries
|
||||
- Updates `selectedDate` ref which triggers data fetching via watcher
|
||||
|
||||
## Styling
|
||||
- Added `.nav-date-picker` with flex layout and 12px gap
|
||||
- `.nav-date-text` for clickable center text
|
||||
- `.nav-arrow` with 8px horizontal padding for touch-friendly targets
|
||||
- Active state opacity transition for visual feedback
|
||||
- Arrow font size: 18px for clear visibility
|
||||
|
||||
## Key Decisions
|
||||
- Used `@click.stop` on arrows to prevent opening date picker
|
||||
- Centered layout preserved (van-nav-bar default behavior)
|
||||
- Consistent styling across both views
|
||||
- Touch-friendly padding for mobile UX
|
||||
|
||||
## Verification
|
||||
- ESLint: No new errors introduced
|
||||
- Build: Successful compilation
|
||||
- Date math: Correctly handles month/year boundaries
|
||||
1
.sisyphus/notepads/date_nav_upgrade/issues.md
Normal file
1
.sisyphus/notepads/date_nav_upgrade/issues.md
Normal file
@@ -0,0 +1 @@
|
||||
No previous issues.
|
||||
1
.sisyphus/notepads/date_nav_upgrade/learnings.md
Normal file
1
.sisyphus/notepads/date_nav_upgrade/learnings.md
Normal file
@@ -0,0 +1 @@
|
||||
Use Vant UI icons for navigation.
|
||||
Reference in New Issue
Block a user