test

文章

前端开发流程

阶段 有什么 做什么 需要什么 缺失什么
meeting
plan 1.time 2.priority 3.dynamic sync
docs
developing
test

MoonAI-Web v1.0.0 前端开发总结

uml

1. 项目概况

  • 项目名称:MoonAI Web
  • 版本号:v1.0.0
  • 上线环境:测试环境 https://agent.moontestenv.com/
  • 目标平台:移动端为主,兼容桌面端
  • 项目角色:前端开发负责人

2. 技术栈

  • 框架:Vue 3 + <script setup> + TypeScript
  • 构建工具:Vite 6 + esbuild(启用原生 ESM)
  • 包管理工具:pnpm
  • 样式体系:UnoCSS(原子化)+ Less 自定义主题变量(light / dark)
  • 路由系统:unplugin-vue-router(基于文件自动生成)
  • 状态管理:Pinia + pinia-plugin-persistedstate(本地持久化)
  • 国际化支持:vue-i18n(支持英文 / 中文)
  • 网络层封装:Axios 封装统一请求
  • 组件库:Vant 4(移动优先设计)
  • 自动导入:unplugin-auto-import + unplugin-vue-components
  • 调试工具:vConsole(按需注入)
  • 单元测试:Vitest + @vitest/ui
  • Lint & 提交校验:ESLint + simple-git-hooks + commitlint + lint-staged

3. 关键实现

第三方登录整合

  • 支持 Google / Apple 登录,统一跳转逻辑封装
  • 动态生成 noncestate,防止 CSRF / Replay 攻击
  • 使用 id_token + response_mode=fragment 直接获取身份令牌
  • 判断系统:基于 UA 使用不同跳转策略(iOS / Android)
  • iOS 使用 Universal Link 唤起 App,未安装 fallback 到中间页或商店链接
  • Android 使用 Scheme + App Store 双跳兼容
  • 提供手动点击、自动 fallback、多渠道 fallback 安全机制

SVG 图标类型自动生成

  • 自定义 Node 脚本扫描 assets/icons 目录生成 svg-icon.d.ts 类型文件
  • 实现图标组件的类型提示与 IDE 补全支持

横向滚动组件封装

  • 使用 Splide + AutoScroll 插件构建支持无限循环的横滑卡片
  • 支持 hover 暂停、离开自动恢复、支持手动滑动不影响自动动画

移动 + 桌面端双适配策略

  • 使用 vw/vh + 响应式容器适配不同尺寸设备
  • 桌面端限定内容宽度为 390px 并居中显示,保持移动体验一致性

暗黑模式与主题切换

  • 使用 html.dark 控制暗黑模式样式激活
  • 支持根据系统偏好自动切换
  • 设置 <meta name="theme-color"> 动态响应主题
  • 支持 favicon 图标深浅切换

4. 文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
src/
├── api/ # API 接口封装
├── assets/ # 静态资源(icons / images)
├── components/ # 通用组件库(Button / Popup 等)
├── composables/ # 组合式函数逻辑封装
├── constants/ # 常量定义(环境、枚举)
├── directives/ # 自定义指令(如 v-focus-border)
├── locales/ # 国际化语言包(en-US / zh-CN)
├── pages/ # 页面模块(文件路由自动生成)
├── router/ # 路由注册与类型定义
├── stores/ # 状态管理模块(Pinia)
├── styles/ # 全局样式、变量与主题
├── types/ # 项目类型声明
├── utils/ # 工具函数(Clipboard / OAuth / Request 等)
└── main.ts # 项目入口

5. 开发与构建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
pnpm dev           # 启动本地开发环境
pnpm build:cf # Cloudflare Pages 构建(支持环境判断)
pnpm build:dev # 开发构建(含 debug 信息)
pnpm build:pro # 正式环境构建
pnpm preview # 本地预览 dist 内容
pnpm lint # ESLint 检查代码风格
pnpm lint:fix # 自动修复代码格式问题
pnpm typecheck # TS 类型检查(vue-tsc)
pnpm test # 单元测试(Vitest)
pnpm test:ui # 单元测试 UI 面板模式
pnpm release # 自动版本号变更 + Git Tag 发布
pnpm commitlint # 检查 Git 提交信息格式
pnpm prepare # 初始化 Git Hooks(pre-commit、commit-msg)

6. 问题记录与优化建议

问题 当前处理 后续建议
Universal Link 测试复杂 使用 schema 跳转 + fallback 到商店链接 引入 Universal Link 中转页,通过设置 href=universal link 跳转到独立页面,在该页面判断是否已安装 App,支持自动跳转 Scheme、手动触发 Scheme、或进入商店地址

7. 开发总结

  • 搭建完整 Vue 3 + Vite + UnoCSS 移动端前端架构,集成组件/路由/API 自动导入方案
  • 设计并实现第三方登录认证(Google / Apple)跳转与状态验证机制
  • 封装横向滚动组件,集成动画暂停/恢复与无缝轮播
  • 使用 Node 脚本自动生成 SVG 图标类型,保障开发时类型安全
  • 构建深色 / 浅色主题系统,支持 favicon 与系统颜色自动适配
  • 完善 CI 构建流程,支持测试 / 生产 / Cloudflare Pages 多环境构建与部署

8. 样式体系与 Vant 变量覆盖

  • 使用 Less 编写 light / dark 主题文件,分别通过 @import 引入

  • 使用 :root:root 提高变量优先级以覆盖 Vant 默认变量

  • 样式变量与设计系统映射,统一管理如文本、背景、边框、按钮等主题色

  • 定制组件样式:

    • 禁用默认滚动条(scrollbar-width / ::-webkit-scrollbar
    • 自定义 .van-toast, .van-button, .van-tabs 等核心组件外观
    • 设置动画类 .fade-enter-* 控制页面切换动画
    • 通过 mask-image, transform, -webkit-mask-smoothing 优化 .van-image 展示质量
  • 支持 viewport 高度单位 100dvh,处理 iOS 地址栏收缩带来的布局问题

记录vue3移动端框架搭建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env bash
set -e

APP_NAME="vue3-mobile-app"

echo "📁 Creating project folder..."
pnpm create vite "$APP_NAME" --template vue-ts
cd "$APP_NAME"

cat > packages.json << 'EOF'

EOF

echo "🔧 Installing dependencies..."
pnpm add vue-router@4 @vueuse/core pinia pinia-plugin-persistedstate vue-i18n@9 axios vant
pnpm add -D @tailwindcss/vite @vitejs/plugin-vue \
@intlify/unplugin-vue-i18n unplugin-auto-import unplugin-vue-components \
@vant/auto-import-resolver vite-tsconfig-paths vue-tsc \
eslint prettier stylelint husky lint-staged typescript

echo "📦 Setting up Vite config..."
cat > vite.config.ts << 'EOF'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import VueI18n from "@intlify/unplugin-vue-i18n/vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [
vue(),
tsconfigPaths(), // 同步 tsconfig.json 别名,避免在 vite.config 重复设置 :contentReference[oaicite:1]{index=1}
VueI18n({ include: path.resolve(__dirname, "src/i18n/locales/**") }),
AutoImport({
imports: [
"vue",
"vue-router",
"pinia",
"vue-i18n",
"@vueuse/core",
{
axios: [
["default", "axios"], // Automatically import axios as `axios`
],
},
],
dts: "src/auto-imports.d.ts",
vueTemplate: true, // 模板中允许直接使用 API 无需手动导入 :contentReference[oaicite:2]{index=2}
dirs: ["src/hooks", "src/composables"], // 自动导入你的 reusable 函数
eslintrc: {
enabled: true,
filepath: "./.eslintrc-auto-import.json",
globalsPropValue: true,
},
}),
Components({
dts: "src/components.d.ts",
resolvers: [VantResolver()],
}),
tailwindcss(),
],
resolve: {
alias: { "@": path.resolve(__dirname, "./src") },
},
});
EOF

echo "🗂️ Creating folders & example files..."
mkdir -p src/{router,store,api,i18n/locales}
touch src/assets/main.css

cat > src/i18n/locales/en.json << 'EOF'
{ "hello": "Hello", "welcome": "Welcome to the app!" }
EOF

cat > src/i18n/index.ts << 'EOF'
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
export const i18n = createI18n({ legacy: false, locale: 'en', fallbackLocale: 'en', messages: { en } })
EOF

cat > src/router/index.ts << 'EOF'
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'

const routes = [{ path: '/', name: 'Home', component: Home }]
export const router = createRouter({ history: createWebHistory(), routes })
EOF

cat > src/store/user.ts << 'EOF'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ token: '' }),
persist: true,
actions: {
setToken(token: string) {
this.token = token
},
},
})
EOF

cat > src/api/index.ts << 'EOF'
import axios from 'axios'
export const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE || '/', timeout: 10000 })
api.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = \`Bearer \${token}\`
return config
})
EOF

cat > src/assets/main.css << 'EOF'
@import "tailwindcss";

/* 全局基础样式 */
@layer base {
body { @apply bg-gray-50 text-gray-900; }
}

/* 自定义组件样式 */
@layer components {
.btn-primary {
@apply px-4 py-2 bg-indigo-600 text-white rounded;
}
}

/* 自定义工具类 */
@layer utilities {
.no-scrollbar {
@apply scrollbar-hide;
}
}

/* 可选:CSS-first 主题覆盖 */
@theme inline {
--color-primary: #5c6ac4;
}
EOF

rm src/style.css

mkdir -p src/views

cat > src/App.vue << 'EOF'
<template>
<router-view />
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
if (!userStore.token) {
userStore.setToken('example-token') // Set a default token for demonstration
}
</script>
EOF

cat > src/views/Home.vue << 'EOF'
<template>
<div class="p-4">
<h1 class="text-xl font-bold">{{ $t('hello') }}</h1>
<p>{{ $t('welcome') }}</p>
<van-button type="primary" @click="click">{{ $t('welcome') }}</van-button>
</div>
</template>
<script setup lang="ts">
const click = () => console.log('Clicked!')
</script>
EOF

cat > src/main.ts << 'EOF'
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persistedstate'
import { i18n } from '@/i18n'
import { router } from '@/router'
import '@/assets/main.css'

const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersist)

app.use(pinia).use(router).use(i18n).mount('#app')
EOF

echo "🔧 Setting up husky and lint-staged..."
git init
pnpm dlx husky-init
pnpm install
pnpm exec husky add .husky/pre-commit "npx lint-staged"

cat > .lintstagedrc.json << 'EOF'
{
"*.ts": ["eslint --fix"],
"*.vue": ["eslint --fix"],
"*.{css,scss}": ["stylelint --fix"]
}
EOF

echo "✅ DONE! To start:"
echo "cd $APP_NAME && pnpm dev"

vue3-mobile-scaffold 模板说明

  • 克隆项目
    使用以下命令克隆模板仓库111:

    1
    git clone https://github.com/jyuhou-wong/vue3-mobile-scaffold
  • 安装依赖并启动开发环境
    进入项目目录后,运行以下命令安装依赖:

    1
    2
    3
    yarn
    # 然后启动开发环境:
    yarn dev
  • 模板用途说明
    此模板将作为团队开发的基础,所有同事都需基于此模板进行开发,以确保项目的一致性和高效协作。

vue3移动端架构

🌐 Modern Vue 3 Frontend Architecture (Using Yarn)

本架构适用于基于 Vite 构建的 Vue 3 前端项目,使用 Yarn 作为包管理工具,结合自动导入、自动组件注册、状态管理、路由、API 拦截、样式体系与代码规范,实现高性能与可维护性的现代化开发流程。

uml


🧩 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
my-vue-app/
├── public/
│ └── index.html
├── src/
│ ├── api/
│ │ └── index.ts
│ ├── assets/
│ │ └── main.css
│ ├── components/
│ ├── hooks/
│ ├── router/
│ │ └── index.ts
│ ├── store/
│ │ ├── index.ts
│ │ └── user.ts
│ ├── styles/
│ ├── utils/
│ ├── views/
│ │ └── Home.vue
│ ├── App.vue
│ └── main.ts
├── .eslintrc.js
├── .prettierrc
├── tailwind.config.js
└── vite.config.ts

⏳ 开发思路与生命周期

  1. 构建阶段:使用 Vite + Yarn 快速创建项目与安装依赖。
  2. 运行时初始化main.ts 中创建 Vue 应用,注入 Pinia、Router、Tailwind、Axios 等模块。
  3. 组件结构组织hooks/components/store/views/api/ 等分区构成清晰的模块划分。
  4. 路由管理:采用 Vue Router 4 实现懒加载与权限校验(Navigation Guards)。
  5. 状态管理:使用 Pinia + 持久化插件管理全局状态。
  6. API 通信:创建 Axios 实例,在 api/ 中统一编写接口调用。
  7. 样式体系:Tailwind CSS 实现原子化样式,集中管理设计变量。
  8. 开发规范:配置 ESLint、Prettier、Stylelint,结合 Husky 与 lint-staged 实现提交前自动检查。

🔧 手把手搭建步骤(Yarn 版)

1. 初始化项目

1
2
yarn create vite my-vue-app --template vue-ts
cd my-vue-app

Yarn 的 create vite 命令能快速创建 Vue 项目,支持框架选择和模板配置 (classic.yarnpkg.com, vuejs.org)。

2. 安装依赖

1
2
3
4
5
yarn
yarn add vue-router@4 pinia pinia-plugin-persistedstate axios vant
yarn add -D tailwindcss postcss autoprefixer \
unplugin-auto-import unplugin-vue-components \
eslint prettier stylelint husky lint-staged

3. 配置 Vite 插件

vite.config.ts 中配置自动导入与组件注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts',
vueTemplate: true,
}),
Components({
dirs: ['src/components'],
resolvers: [VantResolver()],
dts: 'src/components.d.ts',
}),
],
resolve: { alias: { '@': '/src' } },
})

4. 初始化 Tailwind

1
npx tailwindcss init -p

配置 tailwind.config.js

1
2
3
4
5
6
7
8
module.exports = {
content: ['./index.html', './src/**/*.{vue,ts,js}'],
theme: { extend: { colors: { primary: '#1E88E5' } } },
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}

5. 修改主入口 main.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { createRouter, createWebHistory } from 'vue-router'
import routes from './router'
import 'vant/es/index/style'
import './assets/main.css'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const router = createRouter({ history: createWebHistory(), routes })

createApp(App).use(pinia).use(router).mount('#app')

6. 补充目录代码

  • Router (src/router/index.ts):

    1
    2
    3
    4
    5
    import Home from '@/views/Home.vue'
    export default [
    { path: '/', component: Home },
    { path: '/about', component: () => import('@/views/About.vue') },
    ]
  • Store (src/store/user.ts):

    1
    2
    3
    4
    5
    6
    import { defineStore } from 'pinia'
    export const useUserStore = defineStore('user', {
    state: () => ({ token: '' }),
    persist: true,
    actions: { setToken(t: string) { this.token = t } },
    })
  • API (src/api/index.ts):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import axios from 'axios'
    export const api = axios.create({
    baseURL: import.meta.env.VITE_API_URL,
    timeout: 10000,
    })
    api.interceptors.request.use(cfg => {
    const t = localStorage.getItem('token')
    if (t) cfg.headers.Authorization = `Bearer ${t}`
    return cfg
    })
    api.interceptors.response.use(res => res.data, err => Promise.reject(err))

7. 设置代码规范与提交检查

1
npx husky-init && yarn

package.json 中加入:

1
2
3
4
"lint-staged": {
"*.{ts,vue}": "eslint --fix",
"*.{css,scss}": "stylelint --fix"
}

完成后每次提交时自动格式化代码。

8. 启动开发

执行:

1
yarn dev

访问 http://localhost:5173,自动导入、组件注册、状态持久、API 拦截、样式系统均已生效。


✅ 总结

  • Yarn 提供快速依赖安装与缓存能力,是项目推荐选择。
  • Vue 官方支持 yarn create vueyarn create vite 初始化。
  • 提供完整生产与开发流程:从初始化、代码组织、模块注册、规范检测到项目启动。
  • 完整生命周期覆盖构建→开发→提交保护。

可以复制本模块作为项目模板,或生成 README 与部署脚本。如有其它需求,欢迎继续交流!

flutter 开发思路

一、按「垂直切片」来开发

将一个完整的功能(Feature)作为最小单元,一次开发、一次测试、一次 UI 验证。例如“加载成员列表”这个切片,流程如下:

领域 + 仓库接口

  • 定义 MemberEntityMemberRepository 接口。

Data 层 + 映射 + RepoImpl

  • 创建 MemberDto、映射逻辑以及 MemberRepositoryImpl
  • 编写集成/单元测试,验证 JSON → DTO → Entity

UseCase

  • 实现 LoadMembersUseCase,并立即编写两个测试:
    • 正常返回列表。
    • 异常分支(仓库抛错)。

ViewModel

  • 编写 MembersViewModel.load(),并立即测试:
    • 成功时状态从 loading → data
    • 异常时状态变为 error

UI

  • 在页面中消费 ViewModel
  • 进行简单的手动验收,或编写轻量级的 Widget Test(可选)。

这种方式确保每个 Layer 的核心逻辑完成后立即测试并确认,再继续 UI 开发,避免出现“核心逻辑未完成”或“UI开发中后端逻辑变更”的问题。

二、测试要集中在“有业务逻辑的地方”

  • 必写:UseCase、复杂的 Mapper/转换、ViewModel 的状态机。
  • 可选:简单的 DTO、纯 UI 布局(手动验收或少量 Widget Test)。
  • 不覆盖:简单的 Getter/Setter、无分支的一行方法。

这样既能保护核心场景,又避免浪费精力在简单工具函数的测试上。

三、并行而非串行

可以同时进行 UseCase 测试和 UI 开发:

1
2
3
if (state.isLoading) showSpinner();
else if (state.isError) showError();
else showList(state.members);
  • UseCase 和 ViewModel 的测试先跑通。
  • UI 开发可以使用假数据或 Mock ViewModel 先行搭建,再接入真实逻辑。

四、典型节奏

  1. 先踩“刀”:编写 UseCase 和测试,确保业务逻辑正确。
  2. 再搭“台”:在 ViewModel 和 UI 上挂载 UseCase,初步展示页面。
  3. 再补“细节”:完善边界场景、多分支测试、Widget Test 或手动验收。

通过这种小循环的迭代方式,功能开发、测试覆盖和 UI 成型可以同步完成。

小结

  • 不必等到“每个函数都写完测试”,避免 UI 开发被测试进度拖慢。
  • 按切片并行开发:完成一个小功能的 Domain → Data → Application → Presentation,测试和 UI 同步进行。
  • 聚焦业务逻辑:测试核心场景,UI先手动验收,再补充 Widget Test。

这种迭代式、切片驱动的开发方式既能保证测试覆盖,又能确保 UI 开发与后端逻辑进度保持一致。

flutter ddd feature first 开发最佳实践

基于 分离 + CodeGen 映射 的最佳实践开发流程,涵盖从领域建模到 DTO、映射、仓库实现、UseCase、ViewModel、UI,再到各层单元/集成测试和 CI 集成。按此流程,既能保持清晰分层,又能用工具最大化减少样板、提高维护效率。

一、领域层(Domain)

定义实体(Entity)和值对象(Value Object)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// lib/features/members/domain/entities/member_entity.dart
class MemberEntity {
final String name;
final String accountId;
final String accountRole;
final int accountStatus;

MemberEntity({
required this.name,
required this.accountId,
required this.accountRole,
required this.accountStatus,
});

bool get isActive => accountStatus == 1;
}

定义领域请求/响应模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// lib/features/members/domain/entities/member_update_item.dart
class MemberUpdateItem {
final int? accountStatus;
final int? resetPassword;
MemberUpdateItem({this.accountStatus, this.resetPassword});
}

// lib/features/members/domain/entities/member_creation_request.dart
class ManagerCreationInfo {
final String accountId, name;
/*…*/
}
class SupportCreationInfo {
final int createNum;
/*…*/
}

定义仓库接口

1
2
3
4
5
6
7
8
9
10
// lib/features/members/domain/repositories/member_repository.dart
abstract class MemberRepository {
Future<List<MemberEntity>> fetchTeamMembers(int teamId);
Future<bool> updateMemberStatus(String accountId, MemberUpdateItem item);
Future<List<MemberEntity>> createTeamMembers({
required int teamId,
ManagerCreationInfo? manager,
SupportCreationInfo? support,
});
}

二、数据层(Data / Infrastructure)

1. 定义 DTO(只做 JSON 序列化)

lib/features/members/data/dto/ 下,用 @JsonSerializable(fieldRename: FieldRename.snake)

1
2
3
4
5
6
7
8
9
10
11
// member_dto.dart
@JsonSerializable(fieldRename: FieldRename.snake)
class MemberDto {
final String name;
final String accountId;
final String accountRole;
final int accountStatus;

factory MemberDto.fromJson(Map<String, dynamic> json) => _$MemberDtoFromJson(json);
Map<String, dynamic> toJson() => _$MemberDtoToJson(this);
}

同理定义 MemberUpdateItemDto, ManagerCreationInfoDto, SupportCreationInfoDto, MemberCreationResponseDto 等。

2. 生成序列化代码

1
flutter pub run build_runner build --delete-conflicting-outputs

3. 写 Mapper Extension(DTO → Entity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// lib/features/members/data/mappers/member_mapper.dart
extension MemberDtoX on MemberDto {
MemberEntity toEntity() => MemberEntity(
name: name,
accountId: accountId,
accountRole: accountRole,
accountStatus: accountStatus,
);
}

extension MemberCreationResponseDtoX on MemberCreationResponseDto {
List<MemberEntity> toMemberEntities() {
final out = <MemberEntity>[];
if (manager != null) out.add(manager!.toMemberEntity());
if (support != null) out.addAll(support!.toMemberEntities());
return out;
}
}

三、仓库实现(RepositoryImpl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// lib/features/members/data/repositories/member_repository_impl.dart
class MemberRepositoryImpl implements MemberRepository {
final MCRequestManager _api;
MemberRepositoryImpl(this._api);

@override
Future<List<MemberEntity>> fetchTeamMembers(int teamId) async {
final res = await _api.getTeamMemberListAsync(teamId);
final dtos = (res['members'] as List)
.map((e) => MemberDto.fromJson(e)).toList();
return dtos.map((d) => d.toEntity()).toList();
}

@override
Future<bool> updateMemberStatus(String accountId, MemberUpdateItem item) async {
final dto = MemberUpdateItemDto(
accountStatus: item.accountStatus, resetPassword: item.resetPassword);
await _api.updateTeamMemberAsync(accountId, dto.toJson());
return true;
}

@override
Future<List<MemberEntity>> createTeamMembers({
required int teamId,
ManagerCreationInfo? manager,
SupportCreationInfo? support,
}) async {
final managerDto = manager != null
? ManagerCreationInfoDto(accountId: manager.accountId, name: manager.name)
: null;
final supportDto = support != null
? SupportCreationInfoDto(createNum: support.createNum)
: null;

final data = await _api.createTeamMemberAsync(
teamId,
manager: managerDto?.toJson(),
support: supportDto?.toJson(),
);
final respDto = MemberCreationResponseDto.fromJson(data);
return respDto.toMemberEntities();
}
}

四、应用层(Application)

为每个业务场景写 UseCase。

1. LoadMembersUseCase

1
2
3
4
5
6
7
class LoadMembersUseCase {
final MemberRepository _repo;
LoadMembersUseCase(this._repo);

Future<List<MemberEntity>> execute(int teamId) =>
_repo.fetchTeamMembers(teamId);
}

2. UpdateMemberStatusUseCase

1
2
3
4
5
6
7
class UpdateMemberStatusUseCase {
final MemberRepository _repo;
UpdateMemberStatusUseCase(this._repo);

Future<bool> execute(String accountId, int newStatus) =>
_repo.updateMemberStatus(accountId, MemberUpdateItem(accountStatus: newStatus));
}

3. CreateMembersUseCase

1
2
3
4
5
6
7
8
9
10
class CreateMembersUseCase {
final MemberRepository _repo;
CreateMembersUseCase(this._repo);

Future<List<MemberEntity>> execute({
required int teamId,
ManagerCreationInfo? manager,
SupportCreationInfo? support,
}) => _repo.createTeamMembers(teamId: teamId, manager: manager, support: support);
}

五、表现层(Presentation)

1. State + ViewModel

定义状态类和 StateNotifier,注入 UseCase。

1
2
3
4
5
6
7
8
9
10
11
class MembersState { /* status, list, error */ }

class MembersViewModel extends StateNotifier<MembersState> {
final LoadMembersUseCase _load;
final UpdateMemberStatusUseCase _update;
final CreateMembersUseCase _create;
MembersViewModel(this._load, this._update, this._create) : super(MembersState());
Future<void> load(int teamId) async { /*…*/ }
Future<void> update(String id, int status) async { /*…*/ }
Future<void> create({…}) async { /*…*/ }
}

2. Provider 注册

1
2
3
4
5
6
final loadUseCaseProvider = Provider((ref) => LoadMembersUseCase(ref.read(repo)));
final viewModelProvider = StateNotifierProvider.family<MembersViewModel, MembersState, int>((ref, teamId) => MembersViewModel(
ref.read(loadUseCaseProvider),
ref.read(updateUseCaseProvider),
ref.read(createUseCaseProvider),
)..load(teamId));

3. Widget 调用

1
2
3
4
5
6
7
class MembersPage extends ConsumerWidget {
Widget build(...) {
final state = ref.watch(viewModelProvider(teamId));
ref.listen(viewModelProvider(teamId), (_, s) { if (s.isError) showError(...); });
return state.isLoading ? Loading() : CustomTable(items: state.list, ...);
}
}

六、测试策略

1. 单元测试(UseCase/Mapper/ViewModel)

  • UseCase:用 Mockito 生成 MockMemberRepository,测试 execute 成功/失败分支。
  • Mapper:给一个 DTO 测 dto.toEntity() 输出符合预期。
  • ViewModel:用 ProviderContainer override UseCase Provider,驱动 state 变化测试。

2. 集成测试(RepositoryImpl)

在专门的测试环境上跑 MemberRepositoryImpl.fetchTeamMembers 等方法,验证真实 API、JSON 解析、映射流程。

3. CI 集成

  • 阶段 1:CI 每次提交 flutter test --coverage,跑单元测试。
  • 阶段 2:定时或手动触发少量集成测试,保证后端兼容。
  • 阶段 3:可选 E2E/UI 测试,保证整个用户流畅。

七、工具 & 脚本

  • CodeGenbuild_runner + json_serializable +(可选)dart_mappable
  • Mockmockito + build_runner
  • Lint & CI:确保 build_runner build 无报错,所有测试覆盖率可观。

Hexo-GitHub VSCode Plugin

Hexo-GitHub VSCode Plugin

Hexo-GitHub 是一个 VSCode 插件,专注于通过直观的可视化操作简化 Hexo 博客的管理与 GitHub 集成。用户可以通过该插件轻松地创建、更新和部署他们的 Hexo 博客。

Demo

以下是使用该插件发布的博客的示例:

博客 Demo

功能

  • 可视化操作: 支持大多数 Hexo 命令的可视化操作,简化博客管理。
  • 登录到 GitHub: 使用 OAuth 流程安全地登录到 GitHub。
  • 拉取和推送: 从 GitHub 拉取最新的博客内容,或将本地更改推送到 GitHub。
  • 创建新博客: 通过简单的操作创建新的 Hexo 博客文章。
  • 启动和停止 Hexo 服务器: 在本地启动 Hexo 服务器以预览博客,或停止服务器。
  • 本地预览: 在浏览器中打开本地博客的预览。
  • 部署到 GitHub Pages: 将博客部署到 GitHub Pages,使其在线可访问。
  • 管理博客文件: 在 VSCode 中管理博客的文件结构。
  • 主题动态切换: 支持 Hexo 主题的动态切换和安装。
  • 配置支持: 在自定义视图中显示和修改配置。

安装

  1. 从 VSCode 插件市场安装: 在 VSCode 中搜索 “Hexo GitHub” 或访问 插件市场链接 进行安装。
  2. 下载 VSIX 文件: 前往 发布页面 下载最新的 VSIX 文件。
  3. 安装插件:
    • 在 VSCode 中,打开扩展视图(Ctrl+Shift+X)。
    • 点击右上角的三个点,选择 **Install from VSIX…**,然后选择下载的 VSIX 文件。

使用指南

可视化操作

插件提供了一个直观的可视化操作界面,用户可以通过上下文菜单轻松进行以下操作:

  • 拉取和推送: 通过导航菜单进行拉取和推送操作。
  • 新增站点: 通过导航菜单添加新的 Hexo 博客站点。
  • 打开源代码库: 快速访问 GitHub 上的源代码库。

上下文菜单操作

  • 部署博客: 将博客内容部署到 GitHub Pages。
  • 打开页面库: 访问 GitHub Pages 库。
  • 打开 GitHub Pages: 在浏览器中查看博客。
  • 删除站点: 删除博客站点。
  • 管理主题: 添加、应用或删除 Hexo 主题。
  • 本地预览: 预览 Markdown 文件。
  • 添加项目: 添加新页面、草稿或博客文章。
  • 发布草稿: 将草稿发布为正式文章。
  • 删除项目: 删除博客项目。

视图和菜单

Hexo GitHub for VSCode: Blogs

工作原理

  1. 登录和认证: 使用 startOAuthLogin 函数处理用户的 GitHub 登录请求,获取并存储访问令牌。
  2. 仓库管理: 使用 pullHexopushHexo 函数从 GitHub 拉取和推送博客内容。
  3. 博客创建与管理: 通过 createNewBlogPostaddItem 函数,用户可以创建新的博客文章或页面。
  4. Hexo 服务器管理: 使用 startHexoServerstopHexoServer 函数启动和停止本地 Hexo 服务器,支持草稿预览。
  5. 预览和部署: 使用 localPreview 函数在本地预览博客,通过 pushToGitHubPages 函数将博客部署到 GitHub Pages。
  6. 主题和配置管理: 支持在自定义视图中动态切换主题和显示配置。
  7. 树视图管理: BlogsTreeDataProvider 类实现了博客文章的树视图展示,支持文件系统的变化监控。

依赖关系

该插件依赖以下 npm 包:

  • express: 用于创建本地服务器以处理 OAuth 回调。
  • axios: 用于进行 HTTP 请求。
  • simple-git: 用于执行 Git 命令。
  • @octokit/rest: GitHub 的 REST API 客户端。
  • open: 用于在默认浏览器中打开 URL。
  • unzipper: 用于解压 Hexo Starter 模板。

前置条件

  • 用户已安装 Node.js 18 及以上版本,并且具有 npm 包管理器。

Change Log

All notable changes to the “vscode-hexo-github” extension will be documented in this file.

[3.0.1] - 2024-11-05

Refactor

  • Rename hexo-github to vscode-hexo-github.

[2.1.1] - 2024-11-05

Added

  • feat: add setGitUser method

Fixed

  • fix: curly issues

[2.0.11] - 2024-11-01

Added

  • Support for logout method.

[2.0.10] - 2024-10-29

Added

  • Updated installation instructions to include marketplace installation option.

[2.0.9] - 2024-10-29

Added

  • Added extension icon.

[2.0.8] - 2024-10-29

Changed

  • Changed display name.

[2.0.6] - 2024-10-28

Added

  • Added required modules installing method within the initialization of Hexo.

[2.0.5] - 2024-10-28

Fixed

  • Fixed default user page creation issue.
  • Fixed configuration creation and git initialization issue.

[2.0.3] - 2024-10-28

Added

  • Release of version 2.0.3.
  • viewsWelcome feature.

[2.0.2] - 2024-10-27

Added

  • Support for pull and push methods.

[2.0.1] - 2024-10-27

Added

  • Release of beta version.
  • Site creation support.
  • Multiple site support.

[1.0.7] - 2024-10-25

Changed

  • Refactored push and pull methods.

[1.0.6] - 2024-10-23

Added

  • Support for theme installation.

[1.0.5] - 2024-10-23

Fixed

  • Release issue.

[1.0.4] - 2024-10-23

Added

  • Support for dynamic theme switching.

[1.0.3] - 2024-10-22

Added

  • Dynamic theme switching.
  • Theme installation support.
  • Configuration display in custom explorer.

Fixed

  • Button display issue.

[1.0.2] - 2024-10-20

Added

  • URL shortcuts and icons.
  • Auto-locate target file method.
  • Blog explorer panel.

Fixed

  • TreeView and explorer layout issues.

[1.0.1] - 2024-10-19

Added

  • Initial features and fixes, including local preview support.

贡献

欢迎任何形式的贡献!请提交问题或拉取请求。

感谢

特别感谢 Hexo 团队的支持和贡献,使得博客管理变得如此简单。

许可证

此项目使用 MIT 许可证。请参阅 LICENSE 文件以获取更多信息。