diff --git a/docs/src-directory-convention.md b/docs/src-directory-convention.md new file mode 100644 index 0000000..9c1cd7e --- /dev/null +++ b/docs/src-directory-convention.md @@ -0,0 +1,262 @@ +# src 目录结构规范 + +本文档定义 `src` 目录下的文件组织方式与命名规范。**后续新增与修改的代码必须严格遵循本规范。** + +## 目录总览 + +``` +src/ +├── pages/ # 页面组件 +├── components/ # 全局公共组件 +├── business-cmp/ # 业务组件(页面专用) +├── static/ # 静态资源 +├── api/ # 网络请求接口 +├── stores/ # 状态管理(Pinia) +├── utils/ # 工具函数 +├── types/ # TypeScript 类型定义 +├── hooks/ # Vue Composition API Hooks +└── styles/ # 全局样式 +``` + +--- + +## 各目录说明 + +### pages — 页面组件 + +存放应用页面级组件,每个页面独立一个文件夹。 + +| 规则 | 说明 | +|------|------| +| 文件夹命名 | **kebab-case**(小写 + 连字符) | +| 示例 | `user-login/`、`home-index/` | + +**推荐结构:** + +``` +pages/ +├── user-login/ +│ ├── index.vue # 页面入口 +│ └── components/ # 仅该页面使用的组件(可选) +│ └── LoginForm.vue +└── home-index/ + └── index.vue +``` + +> 页面专属组件优先放在 `pages//components/` 下;若业务组件较多、需跨页面复用但又不属于全局公共组件,可使用 `business-cmp/`。 + +--- + +### components — 全局公共组件 + +存放可在多个页面复用的通用 UI 组件。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **PascalCase**(大驼峰) | +| 文件扩展名 | `.vue` | +| 示例 | `AppFooter.vue`、`InputEntry.vue` | + +**适用场景:** + +- 按钮、输入框、弹窗等基础 UI 封装 +- 布局类组件(Header、Footer、Sidebar 等) +- 与具体业务无关、可在任意页面引用的组件 + +--- + +### business-cmp — 业务组件 + +存放与特定业务逻辑绑定、**不对外全局复用**的组件。 + +| 规则 | 说明 | +|------|------| +| 组织方式 | 按业务模块或页面分子目录 | +| 文件命名 | **PascalCase** | +| 示例 | `business-cmp/order/OrderCard.vue` | + +**与 `components` 的区别:** + +| 目录 | 复用范围 | 示例 | +|------|----------|------| +| `components/` | 全局、跨业务复用 | `AppFooter.vue` | +| `business-cmp/` | 特定业务/页面 | `OrderCard.vue` | +| `pages/xxx/components/` | 仅单个页面 | `LoginForm.vue` | + +**选择原则:** + +1. 仅一个页面使用 → `pages//components/` +2. 同一业务模块内多页面复用 → `business-cmp//` +3. 跨业务、通用 UI → `components/` + +--- + +### static — 静态资源 + +存放图片、字体、图标等静态文件。 + +| 规则 | 说明 | +|------|------| +| 文件/文件夹命名 | **小写**(可用连字符分隔) | +| 示例 | `logo.png`、`github.svg`、`icon-arrow.png` | + +**推荐结构:** + +``` +static/ +├── images/ +│ └── banner.png +├── icons/ +│ └── home.svg +└── fonts/ + └── custom-font.woff2 +``` + +--- + +### api — 网络请求接口 + +封装所有后端 API 调用,按业务模块拆分文件。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **kebab-case** 或按模块命名 | +| 文件扩展名 | `.ts` | +| 示例 | `user.ts`、`order.ts` | + +**示例:** + +```ts +// api/user.ts +export function login(data: LoginParams) { + return request.post('/user/login', data) +} +``` + +--- + +### stores — 状态管理 + +使用 **Pinia** 管理全局状态,按功能模块拆分 Store。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **kebab-case** 或 `useXxxStore` 风格 | +| 文件扩展名 | `.ts` | +| 示例 | `user.ts`、`cart.ts` | + +**示例:** + +```ts +// stores/user.ts +export const useUserStore = defineStore('user', () => { + // ... +}) +``` + +--- + +### utils — 工具函数 + +存放与业务无关的纯函数工具。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **kebab-case** | +| 文件扩展名 | `.ts` | +| 示例 | `format-date.ts`、`storage.ts` | + +**适用场景:** + +- 日期/金额格式化 +- 本地存储封装 +- 通用校验、防抖节流等 + +--- + +### types — TypeScript 类型定义 + +集中管理项目中的 TypeScript 类型与接口。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **kebab-case** | +| 文件扩展名 | **`.ts`**(必须) | +| 示例 | `user.ts`、`api-response.ts` | + +**示例:** + +```ts +// types/user.ts +export interface UserInfo { + id: number + name: string +} +``` + +--- + +### hooks — Composition API Hooks + +封装可复用的 Vue Composition API 逻辑。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **kebab-case**,以 `use` 开头 | +| 文件扩展名 | `.ts` | +| 示例 | `use-auth.ts`、`use-pagination.ts` | + +**示例:** + +```ts +// hooks/use-auth.ts +export function useAuth() { + // ... +} +``` + +--- + +### styles — 全局样式 + +存放全局样式、变量、混入等。 + +| 规则 | 说明 | +|------|------| +| 文件命名 | **kebab-case** | +| 文件扩展名 | `.scss` / `.css` | +| 示例 | `variables.scss`、`reset.scss` | + +--- + +## 命名规范速查表 + +| 类型 | 命名风格 | 示例 | +|------|----------|------| +| 页面文件夹 | kebab-case | `user-login/` | +| 全局组件文件 | PascalCase | `AppFooter.vue` | +| 业务/页面组件文件 | PascalCase | `OrderCard.vue` | +| 静态资源 | 小写 | `logo.png` | +| API / Store / Utils / Types / Hooks | kebab-case | `user.ts`、`use-auth.ts` | +| 样式文件 | kebab-case | `variables.scss` | + +--- + +## 导入路径约定 + +- 全局组件:从 `@/components/` 引入 +- 业务组件:从 `@/business-cmp/` 或页面相对路径引入 +- API:从 `@/api/` 引入 +- 类型:从 `@/types/` 引入 +- 工具:从 `@/utils/` 引入 +- Hooks:从 `@/hooks/` 引入 + +--- + +## 注意事项 + +1. **禁止**在 `components/` 中放置与具体业务强耦合的组件。 +2. **禁止**在页面目录外散落页面专属组件。 +3. 新增目录或调整规范前,需同步更新本文档。 +4. 所有 TypeScript 类型定义统一放在 `types/`,避免在业务文件中重复定义相同类型。 +5. 网络请求逻辑统一通过 `api/` 层调用,页面与组件中不直接写请求 URL。 diff --git a/package-lock.json b/package-lock.json index 069bc3d..fecaf23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@dcloudio/uni-mp-weixin": "3.0.0-5000720260410001", "@dcloudio/uni-mp-xhs": "3.0.0-5000720260410001", "@dcloudio/uni-quickapp-webview": "3.0.0-5000720260410001", + "@fontpkg/alimama-fang-yuan-ti-vf": "^1.0.5", "vue": "3.4.21", "vue-i18n": "9.6.2", "vue-router": "4.5.1" @@ -3807,6 +3808,12 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@fontpkg/alimama-fang-yuan-ti-vf": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@fontpkg/alimama-fang-yuan-ti-vf/-/alimama-fang-yuan-ti-vf-1.0.5.tgz", + "integrity": "sha512-lwNA/hF0tlJuqEvCZc7Y6sXLtO5IKYOmvEQkJlvRBMlTLO9jVojnkqDbLiTXCnrHQRUUgYOJTjbX1DKnbhudSg==", + "license": "SEE LICENSE IN README.md" + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", diff --git a/package.json b/package.json index 6c945dc..fe39758 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@dcloudio/uni-mp-weixin": "3.0.0-5000720260410001", "@dcloudio/uni-mp-xhs": "3.0.0-5000720260410001", "@dcloudio/uni-quickapp-webview": "3.0.0-5000720260410001", + "@fontpkg/alimama-fang-yuan-ti-vf": "^1.0.5", "vue": "3.4.21", "vue-i18n": "9.6.2", "vue-router": "4.5.1" @@ -53,4 +54,4 @@ "vite": "5.2.8", "vue-tsc": "^3.2.7" } -} \ No newline at end of file +} diff --git a/src/App.vue b/src/App.vue index 297d679..dc0a09c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,4 +4,11 @@ import { onLaunch } from '@dcloudio/uni-app' onLaunch(() => {}) - + diff --git a/src/pages.json b/src/pages.json index 8fc7021..7a7be15 100644 --- a/src/pages.json +++ b/src/pages.json @@ -1,18 +1,57 @@ { "pages": [ { - "path": "pages/index", - "type": "home" + "path": "pages/Login/splash/index", + "type": "home", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/Login/user-login/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/Login/user-forgot-password/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/Login/user-register/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/Login/user-register-success/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/Login/user-sms-login/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/Login/user-agreement/index", + "style": { + "navigationStyle": "custom" + } } ], "globalStyle": { - "backgroundColor": "@bgColor", - "backgroundColorBottom": "@bgColorBottom", - "backgroundColorTop": "@bgColorTop", - "backgroundTextStyle": "@bgTxtStyle", - "navigationBarBackgroundColor": "#000000", - "navigationBarTextStyle": "@navTxtStyle", - "navigationBarTitleText": "Uni Creator", + "backgroundColor": "#ffffff", + "backgroundColorBottom": "#ffffff", + "backgroundColorTop": "#ffffff", + "backgroundTextStyle": "dark", + "navigationBarBackgroundColor": "#ffffff", + "navigationBarTextStyle": "black", + "navigationBarTitleText": "", "navigationStyle": "custom" }, "subPackages": [] diff --git a/src/pages/Login/splash/index.vue b/src/pages/Login/splash/index.vue new file mode 100644 index 0000000..348ca75 --- /dev/null +++ b/src/pages/Login/splash/index.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/pages/Login/user-agreement/index.vue b/src/pages/Login/user-agreement/index.vue new file mode 100644 index 0000000..d07c69d --- /dev/null +++ b/src/pages/Login/user-agreement/index.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/pages/Login/user-forgot-password/components/CodeInput.vue b/src/pages/Login/user-forgot-password/components/CodeInput.vue new file mode 100644 index 0000000..3ddc55e --- /dev/null +++ b/src/pages/Login/user-forgot-password/components/CodeInput.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/pages/Login/user-forgot-password/components/ForgotInput.vue b/src/pages/Login/user-forgot-password/components/ForgotInput.vue new file mode 100644 index 0000000..755527e --- /dev/null +++ b/src/pages/Login/user-forgot-password/components/ForgotInput.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/pages/Login/user-forgot-password/components/PasswordField.vue b/src/pages/Login/user-forgot-password/components/PasswordField.vue new file mode 100644 index 0000000..efb43e6 --- /dev/null +++ b/src/pages/Login/user-forgot-password/components/PasswordField.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/pages/Login/user-forgot-password/index.vue b/src/pages/Login/user-forgot-password/index.vue new file mode 100644 index 0000000..2b3eeb8 --- /dev/null +++ b/src/pages/Login/user-forgot-password/index.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/src/pages/Login/user-login/components/LoginInput.vue b/src/pages/Login/user-login/components/LoginInput.vue new file mode 100644 index 0000000..c4f8574 --- /dev/null +++ b/src/pages/Login/user-login/components/LoginInput.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/pages/Login/user-login/index.vue b/src/pages/Login/user-login/index.vue new file mode 100644 index 0000000..e9a92ef --- /dev/null +++ b/src/pages/Login/user-login/index.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/src/pages/Login/user-register-success/index.vue b/src/pages/Login/user-register-success/index.vue new file mode 100644 index 0000000..e98aa37 --- /dev/null +++ b/src/pages/Login/user-register-success/index.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/pages/Login/user-register/components/CodeInput.vue b/src/pages/Login/user-register/components/CodeInput.vue new file mode 100644 index 0000000..1556e8f --- /dev/null +++ b/src/pages/Login/user-register/components/CodeInput.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/pages/Login/user-register/components/PasswordField.vue b/src/pages/Login/user-register/components/PasswordField.vue new file mode 100644 index 0000000..cd4dcb6 --- /dev/null +++ b/src/pages/Login/user-register/components/PasswordField.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/pages/Login/user-register/components/PasswordStrength.vue b/src/pages/Login/user-register/components/PasswordStrength.vue new file mode 100644 index 0000000..194c0cc --- /dev/null +++ b/src/pages/Login/user-register/components/PasswordStrength.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/pages/Login/user-register/components/RegisterInput.vue b/src/pages/Login/user-register/components/RegisterInput.vue new file mode 100644 index 0000000..62d7361 --- /dev/null +++ b/src/pages/Login/user-register/components/RegisterInput.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/pages/Login/user-register/index.vue b/src/pages/Login/user-register/index.vue new file mode 100644 index 0000000..621882f --- /dev/null +++ b/src/pages/Login/user-register/index.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/pages/Login/user-sms-login/components/CodeInput.vue b/src/pages/Login/user-sms-login/components/CodeInput.vue new file mode 100644 index 0000000..1556e8f --- /dev/null +++ b/src/pages/Login/user-sms-login/components/CodeInput.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/pages/Login/user-sms-login/components/LoginInput.vue b/src/pages/Login/user-sms-login/components/LoginInput.vue new file mode 100644 index 0000000..c4f8574 --- /dev/null +++ b/src/pages/Login/user-sms-login/components/LoginInput.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/pages/Login/user-sms-login/index.vue b/src/pages/Login/user-sms-login/index.vue new file mode 100644 index 0000000..a3dcae9 --- /dev/null +++ b/src/pages/Login/user-sms-login/index.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/src/static/fonts/AlimamaFangYuanTiVF-Thin.woff2 b/src/static/fonts/AlimamaFangYuanTiVF-Thin.woff2 new file mode 100644 index 0000000..4173403 Binary files /dev/null and b/src/static/fonts/AlimamaFangYuanTiVF-Thin.woff2 differ diff --git a/src/static/icons/icon-back.svg b/src/static/icons/icon-back.svg new file mode 100644 index 0000000..8cf4045 --- /dev/null +++ b/src/static/icons/icon-back.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/icons/icon-eye-close.svg b/src/static/icons/icon-eye-close.svg new file mode 100644 index 0000000..fde5b6d --- /dev/null +++ b/src/static/icons/icon-eye-close.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/static/icons/icon-eye-open.svg b/src/static/icons/icon-eye-open.svg new file mode 100644 index 0000000..605f6ed --- /dev/null +++ b/src/static/icons/icon-eye-open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/static/icons/icon-lock.png b/src/static/icons/icon-lock.png new file mode 100644 index 0000000..ecfaebf Binary files /dev/null and b/src/static/icons/icon-lock.png differ diff --git a/src/static/icons/icon-login-failed.svg b/src/static/icons/icon-login-failed.svg new file mode 100644 index 0000000..bac1c27 --- /dev/null +++ b/src/static/icons/icon-login-failed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/static/icons/icon-mark.png b/src/static/icons/icon-mark.png new file mode 100644 index 0000000..6ef287b Binary files /dev/null and b/src/static/icons/icon-mark.png differ diff --git a/src/static/icons/icon-password-login.png b/src/static/icons/icon-password-login.png new file mode 100644 index 0000000..d59d8bb Binary files /dev/null and b/src/static/icons/icon-password-login.png differ diff --git a/src/static/icons/icon-phone.png b/src/static/icons/icon-phone.png new file mode 100644 index 0000000..0902b37 Binary files /dev/null and b/src/static/icons/icon-phone.png differ diff --git a/src/static/icons/icon-register-success.png b/src/static/icons/icon-register-success.png new file mode 100644 index 0000000..b49efb8 Binary files /dev/null and b/src/static/icons/icon-register-success.png differ diff --git a/src/static/icons/icon-shield-check.png b/src/static/icons/icon-shield-check.png new file mode 100644 index 0000000..5b9fc44 Binary files /dev/null and b/src/static/icons/icon-shield-check.png differ diff --git a/src/static/icons/icon-sms-login.png b/src/static/icons/icon-sms-login.png new file mode 100644 index 0000000..b4ddddf Binary files /dev/null and b/src/static/icons/icon-sms-login.png differ diff --git a/src/static/icons/logo.png b/src/static/icons/logo.png new file mode 100644 index 0000000..f86a936 Binary files /dev/null and b/src/static/icons/logo.png differ diff --git a/src/styles/fonts.scss b/src/styles/fonts.scss new file mode 100644 index 0000000..b84c46c --- /dev/null +++ b/src/styles/fonts.scss @@ -0,0 +1,12 @@ +@font-face { + font-family: 'Alimama FangYuanTi VF'; + src: url('/static/fonts/AlimamaFangYuanTiVF-Thin.woff2') format('woff2'); + font-display: swap; +} + +@font-face { + font-family: 'Alimama FangYuanTi VF-Bold'; + src: url('/static/fonts/AlimamaFangYuanTiVF-Thin.woff2') format('woff2'); + font-display: swap; + font-weight: 700; +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..3feaf13 --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,15 @@ +// 设计规范色值 +$color-primary: #2563EB; +$color-primary-light: #e8f0ff; + +$color-text-primary: #1a1a1a; +$color-text-secondary: #999999; +$color-text-placeholder: #b0b0b0; +$color-text-link: #2563EB; + +$color-bg-page: #ffffff; +$color-bg-input: #f2f3f5; + +$color-splash-gradient-start: #f0f7ff; +$color-splash-gradient-end: #deedff; +$color-splash-circle: rgba(180, 210, 255, 0.35); diff --git a/src/types/result-page.ts b/src/types/result-page.ts new file mode 100644 index 0000000..ecbd74f --- /dev/null +++ b/src/types/result-page.ts @@ -0,0 +1,40 @@ +export type ResultPageType = 'register' | 'resetPassword' | 'loginFailed' + +export interface ResultPageConfig { + title: string + subtitleTarget: string + icon: string + redirectUrl: string +} + +export const RESULT_PAGE_CONFIG: Record = { + register: { + title: '注册成功,请登录', + subtitleTarget: '登录页面', + icon: '/static/icons/icon-register-success.png', + redirectUrl: '/pages/Login/user-login/index', + }, + resetPassword: { + title: '修改成功,请登录', + subtitleTarget: '登录页面', + icon: '/static/icons/icon-register-success.png', + redirectUrl: '/pages/Login/user-login/index', + }, + loginFailed: { + title: '登录失败,请先注册账号', + subtitleTarget: '注册页面', + icon: '/static/icons/icon-login-failed.svg', + redirectUrl: '/pages/Login/user-register/index', + }, +} + +export const RESULT_PAGE_ROUTE = '/pages/Login/user-register-success/index' + +export const RESULT_REDIRECT_DELAY = 3000 + +// 兼容旧命名 +export type SuccessPageType = ResultPageType +export const SUCCESS_PAGE_CONFIG = RESULT_PAGE_CONFIG +export const SUCCESS_PAGE_ROUTE = RESULT_PAGE_ROUTE +export const SUCCESS_REDIRECT_URL = RESULT_PAGE_CONFIG.register.redirectUrl +export const SUCCESS_REDIRECT_DELAY = RESULT_REDIRECT_DELAY diff --git a/src/utils/navigate-result.ts b/src/utils/navigate-result.ts new file mode 100644 index 0000000..f08263f --- /dev/null +++ b/src/utils/navigate-result.ts @@ -0,0 +1,18 @@ +import { + RESULT_PAGE_ROUTE, + type ResultPageType, +} from '@/types/result-page' + +export function navigateToResultPage(type: ResultPageType) { + const url = `${RESULT_PAGE_ROUTE}?type=${type}` + + uni.redirectTo({ + url, + fail: () => { + uni.navigateTo({ url }) + }, + }) +} + +// 兼容旧命名 +export { navigateToResultPage as navigateToSuccessPage } diff --git a/src/utils/password-strength.ts b/src/utils/password-strength.ts new file mode 100644 index 0000000..4135352 --- /dev/null +++ b/src/utils/password-strength.ts @@ -0,0 +1,42 @@ +export type PasswordStrengthLevel = 0 | 1 | 2 | 3 | 4 + +export interface PasswordStrengthResult { + level: PasswordStrengthLevel + label: string + color: string + isTooWeak: boolean +} + +export function getPasswordStrength(password: string): PasswordStrengthResult { + if (!password) { + return { level: 0, label: '', color: '', isTooWeak: false } + } + + const hasLower = /[a-z]/.test(password) + const hasUpper = /[A-Z]/.test(password) + const hasDigit = /\d/.test(password) + const hasSpecial = /[^a-zA-Z0-9]/.test(password) + const typeCount = [hasLower, hasUpper, hasDigit, hasSpecial].filter(Boolean).length + + let level: PasswordStrengthLevel = 1 + + if (password.length >= 8 && typeCount >= 3 && hasDigit && (hasLower || hasUpper)) { + level = password.length >= 12 ? 4 : 3 + } + else if (password.length >= 8 && typeCount >= 2) { + level = 2 + } + + if (password.length < 8 || /^\d+$/.test(password) || /^[a-zA-Z]+$/.test(password)) { + level = 1 + } + + const config: Record, Omit> = { + 1: { label: '弱', color: '#FF4D4F', isTooWeak: true }, + 2: { label: '中', color: '#FAAD14', isTooWeak: false }, + 3: { label: '强', color: '#52C41A', isTooWeak: false }, + 4: { label: '很强', color: '#52C41A', isTooWeak: false }, + } + + return { level, ...config[level] } +}