密码
Showing
9 changed files
with
157 additions
and
17 deletions
CLAUDE.md
0 → 100644
| 1 | # CLAUDE.md | ||
| 2 | |||
| 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
| 4 | |||
| 5 | ## Project Overview | ||
| 6 | |||
| 7 | This is a **uni-app** (Vue 3) WeChat mini-program project for the **China Taekwondo Association (中国跆拳道协会)** membership management system. | ||
| 8 | |||
| 9 | - **Platform**: mp-weixin (WeChat Mini-Program) | ||
| 10 | - **Vue Version**: Vue 3 with `<script setup>` syntax | ||
| 11 | - **State Management**: Pinia | ||
| 12 | - **UI Framework**: uni-ui (components in `uni_modules/`) | ||
| 13 | - **Key Dependencies**: dayjs, underscore, lodash, crypto-js | ||
| 14 | |||
| 15 | ## Development Commands | ||
| 16 | |||
| 17 | ### Running the Project | ||
| 18 | - Use **HBuilderX** to open this project | ||
| 19 | - Or use CLI: `npm run dev:mp-weixin` | ||
| 20 | - Build: `npm run build:mp-weixin` | ||
| 21 | |||
| 22 | ### Key Configurations | ||
| 23 | - `config.js` - API base URLs (dev/prod switching) | ||
| 24 | - `pages.json` - Page routing and global settings | ||
| 25 | - `manifest.json` - App configuration (appid, platform settings) | ||
| 26 | |||
| 27 | ## Architecture | ||
| 28 | |||
| 29 | ### Directory Structure | ||
| 30 | ``` | ||
| 31 | ├── common/ # Shared utilities | ||
| 32 | │ ├── api.js # API functions (imported as @/common/api.js) | ||
| 33 | │ ├── request.js # HTTP request wrapper | ||
| 34 | │ ├── login.js # Login logic | ||
| 35 | │ ├── utils.js # Utility functions | ||
| 36 | │ └── pay.js # Payment logic | ||
| 37 | ├── config.js # Environment config (API endpoints) | ||
| 38 | ├── pages/ # Main package pages (index, exam, invoice, rank, webview) | ||
| 39 | ├── login/ # Login sub-package | ||
| 40 | ├── personal/ # Personal member sub-package | ||
| 41 | ├── personalVip/ # VIP member sub-package | ||
| 42 | ├── group/ # Group/Organization sub-package | ||
| 43 | ├── level/ # Level examination sub-package | ||
| 44 | ├── myCenter/ # User center sub-package | ||
| 45 | ├── uni_modules/ # uni-ui components | ||
| 46 | └── static/ # Static assets | ||
| 47 | ``` | ||
| 48 | |||
| 49 | ### API Pattern | ||
| 50 | All API functions are defined in `common/api.js` using a consistent pattern: | ||
| 51 | ```js | ||
| 52 | export function apiFunctionName(data) { | ||
| 53 | return request({ | ||
| 54 | url: '/path/to/endpoint', | ||
| 55 | method: 'post', // or 'get', 'put', 'delete' | ||
| 56 | params: data | ||
| 57 | }) | ||
| 58 | } | ||
| 59 | ``` | ||
| 60 | |||
| 61 | ### Page Sub-packages | ||
| 62 | Pages are organized into sub-packages defined in `pages.json` to optimize loading. Each sub-package has its own root directory (e.g., `level/`, `myCenter/`). | ||
| 63 | |||
| 64 | ### Component Auto-import | ||
| 65 | uni-ui components are auto-imported via easycom in `pages.json`: | ||
| 66 | ```json | ||
| 67 | "easycom": { | ||
| 68 | "autoscan": true, | ||
| 69 | "custom": { | ||
| 70 | "^uni-(.*)": "uni_modules/uni-$1/components/uni-$1/uni-$1.vue" | ||
| 71 | } | ||
| 72 | } | ||
| 73 | ``` | ||
| 74 | |||
| 75 | ### Global Data | ||
| 76 | `app.globalData` contains shared state (from `App.vue`): | ||
| 77 | - `memberInfo` - Current member info | ||
| 78 | - `isLogin` - Login status | ||
| 79 | - `deptType` - Department type | ||
| 80 | |||
| 81 | Access via: `const app = getApp(); app.globalData.memberInfo` | ||
| 82 | |||
| 83 | ### Form Handling Pattern | ||
| 84 | For forms with `uni-forms`, use `Object.assign(form.value, data)` instead of `form.value = data` to preserve reactive bindings. | ||
| 85 | |||
| 86 | ### DateTime Picker | ||
| 87 | `uni-datetime-picker` requires ISO format strings or timestamps for v-model values. | ||
| 88 | |||
| 89 | ### File Upload Pattern | ||
| 90 | Use `api.uploadFile(e)` or `api.uploadImg(e)` from `common/api.js` which return `{ code, msg }` - the file URL is in `res.msg`. | ||
| 91 | |||
| 92 | ## Common Issues | ||
| 93 | |||
| 94 | ### uni-data-select not working | ||
| 95 | If `uni-data-select` dropdowns don't appear, ensure: | ||
| 96 | 1. easycom is properly configured in `pages.json` | ||
| 97 | 2. The component is not blocked by CSS `overflow: hidden` on parent containers | ||
| 98 | |||
| 99 | ### Responsive Data Binding | ||
| 100 | When updating form data from API responses, use `Object.assign()` to maintain Vue 3 reactivity: | ||
| 101 | ```js | ||
| 102 | // Instead of: form.value = res.data | ||
| 103 | Object.assign(form.value, res.data) | ||
| 104 | ``` |
| ... | @@ -61,7 +61,14 @@ function getCodeImg() { | ... | @@ -61,7 +61,14 @@ function getCodeImg() { |
| 61 | method: 'get' | 61 | method: 'get' |
| 62 | }) | 62 | }) |
| 63 | } | 63 | } |
| 64 | 64 | function getSmsCodeImg(data) { | |
| 65 | return request({ | ||
| 66 | url: '/captchaSmsWithCaptchaImage', | ||
| 67 | // url: '/captchaSmsWithCaptchaImageForMiniApp', | ||
| 68 | method: 'post', | ||
| 69 | params: data | ||
| 70 | }) | ||
| 71 | } | ||
| 65 | // 代退图形认证的获取手机验证码 | 72 | // 代退图形认证的获取手机验证码 |
| 66 | function getSmsCode(data) { | 73 | function getSmsCode(data) { |
| 67 | return request({ | 74 | return request({ |
| ... | @@ -241,6 +248,7 @@ export { | ... | @@ -241,6 +248,7 @@ export { |
| 241 | pcLogin, | 248 | pcLogin, |
| 242 | getCodeImg, | 249 | getCodeImg, |
| 243 | getSmsCode, | 250 | getSmsCode, |
| 251 | getSmsCodeImg, | ||
| 244 | h5Login, | 252 | h5Login, |
| 245 | h5LoginAuto, | 253 | h5LoginAuto, |
| 246 | loginByPhone, | 254 | loginByPhone, | ... | ... |
| ... | @@ -4,13 +4,13 @@ page { | ... | @@ -4,13 +4,13 @@ page { |
| 4 | background: #ecf0f6; | 4 | background: #ecf0f6; |
| 5 | } | 5 | } |
| 6 | /* uni-data-checkbox 选中色全局覆盖为红色 */ | 6 | /* uni-data-checkbox 选中色全局覆盖为红色 */ |
| 7 | uni-data-checkbox .checklist-box.is-checked { | 7 | // uni-data-checkbox .checklist-box.is-checked { |
| 8 | border-color: #C4121B !important; | 8 | // border-color: #C4121B !important; |
| 9 | background-color: #C4121B !important; | 9 | // background-color: #C4121B !important; |
| 10 | } | 10 | // } |
| 11 | uni-data-checkbox .checklist-box.is-checked .checklist-text { | 11 | // uni-data-checkbox .checklist-box.is-checked .checklist-text { |
| 12 | color: #fff !important; | 12 | // color: #fff !important; |
| 13 | } | 13 | // } |
| 14 | .wBox{box-sizing: border-box;} | 14 | .wBox{box-sizing: border-box;} |
| 15 | .h3 {font-weight: bold;line-height: 2;} | 15 | .h3 {font-weight: bold;line-height: 2;} |
| 16 | .text-center{text-align: center;} | 16 | .text-center{text-align: center;} | ... | ... |
| ... | @@ -933,7 +933,7 @@ | ... | @@ -933,7 +933,7 @@ |
| 933 | padding: 20rpx 0; | 933 | padding: 20rpx 0; |
| 934 | background: #fff; | 934 | background: #fff; |
| 935 | border-top: 1rpx solid #f0f0f0; | 935 | border-top: 1rpx solid #f0f0f0; |
| 936 | z-index: 999; | 936 | z-index: 98; |
| 937 | 937 | ||
| 938 | .btn-red { | 938 | .btn-red { |
| 939 | background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%); | 939 | background: linear-gradient(135deg, #AD181F 0%, #c42a2a 100%); | ... | ... |
| ... | @@ -68,7 +68,7 @@ import { | ... | @@ -68,7 +68,7 @@ import { |
| 68 | } from 'vue' | 68 | } from 'vue' |
| 69 | import { | 69 | import { |
| 70 | getCodeImg, | 70 | getCodeImg, |
| 71 | getSmsCode, | 71 | getSmsCodeImg, |
| 72 | groupMemberRegister | 72 | groupMemberRegister |
| 73 | } from '@/common/login.js' | 73 | } from '@/common/login.js' |
| 74 | import config from '@/config.js' | 74 | import config from '@/config.js' |
| ... | @@ -113,6 +113,14 @@ function register() { | ... | @@ -113,6 +113,14 @@ function register() { |
| 113 | }) | 113 | }) |
| 114 | return | 114 | return |
| 115 | } | 115 | } |
| 116 | // 密码强度校验:8~18位大小写字母加数字加特殊符号组合 | ||
| 117 | if (!validPassword(registerForm.value.password)) { | ||
| 118 | uni.showToast({ | ||
| 119 | title: '密码必须为8~18位大小写字母、数字和特殊符号组合', | ||
| 120 | icon: 'none' | ||
| 121 | }) | ||
| 122 | return | ||
| 123 | } | ||
| 116 | if (registerForm.value.password != registerForm.value.password2) { | 124 | if (registerForm.value.password != registerForm.value.password2) { |
| 117 | uni.showToast({ | 125 | uni.showToast({ |
| 118 | title: '两次密码不一致,请重新输入', | 126 | title: '两次密码不一致,请重新输入', |
| ... | @@ -131,13 +139,27 @@ function register() { | ... | @@ -131,13 +139,27 @@ function register() { |
| 131 | groupMemberRegister(registerForm.value) | 139 | groupMemberRegister(registerForm.value) |
| 132 | .then((res) => { | 140 | .then((res) => { |
| 133 | uni.showToast({ | 141 | uni.showToast({ |
| 134 | title: `恭喜你,您的账号 ${registerForm.value.telNo} 注册成功!` | 142 | title: `恭喜你,您的账号 ${registerForm.value.telNo} 注册成功!`, |
| 143 | icon: 'none' | ||
| 135 | }) | 144 | }) |
| 136 | registerForm.value = {} | 145 | registerForm.value = {} |
| 137 | setTimeout(goLogin, 2000) | 146 | setTimeout(goLogin, 2000) |
| 138 | }) | 147 | }) |
| 139 | } | 148 | } |
| 140 | 149 | ||
| 150 | // 密码校验:8~18位大小写字母加数字加特殊符号组合 | ||
| 151 | function validPassword(pwd) { | ||
| 152 | if (!pwd || pwd.length < 8 || pwd.length > 18) { | ||
| 153 | return false | ||
| 154 | } | ||
| 155 | const lowerRegex = /[a-z]+/ | ||
| 156 | const upperRegex = /[A-Z]+/ | ||
| 157 | const digitRegex = /[0-9]+/ | ||
| 158 | const symbolRegex = /[\W_]+/ | ||
| 159 | const specific = /.*[~!@#$%^&*()_+`\-={}:";'<>?,.\/].*/ | ||
| 160 | return (lowerRegex.test(pwd) && upperRegex.test(pwd) && digitRegex.test(pwd) && symbolRegex.test(pwd) && specific.test(pwd)) | ||
| 161 | } | ||
| 162 | |||
| 141 | function goLogin() { | 163 | function goLogin() { |
| 142 | let path = '/login/loginC'; | 164 | let path = '/login/loginC'; |
| 143 | uni.navigateTo({ | 165 | uni.navigateTo({ |
| ... | @@ -169,7 +191,7 @@ function getCaptchaSms() { | ... | @@ -169,7 +191,7 @@ function getCaptchaSms() { |
| 169 | return | 191 | return |
| 170 | } | 192 | } |
| 171 | 193 | ||
| 172 | getSmsCode({ | 194 | getSmsCodeImg({ |
| 173 | uuid: registerForm.value.uuid, | 195 | uuid: registerForm.value.uuid, |
| 174 | telNo: registerForm.value.telNo, | 196 | telNo: registerForm.value.telNo, |
| 175 | code: registerForm.value.captcha | 197 | code: registerForm.value.captcha | ... | ... |
| ... | @@ -264,10 +264,10 @@ | ... | @@ -264,10 +264,10 @@ |
| 264 | getTree() | 264 | getTree() |
| 265 | form.value.deptType = res.data.dept.deptType | 265 | form.value.deptType = res.data.dept.deptType |
| 266 | form.value.parentId = form.value.parentId.toString() | 266 | form.value.parentId = form.value.parentId.toString() |
| 267 | creditCode.value = form.value.creditCode | 267 | // creditCode.value = form.value.creditCode |
| 268 | companyName.value = form.value.companyName | 268 | // form.value.companyName = res.data.memberInfo.companyName |
| 269 | belongProvinceId.value = form.value.belongProvinceId | 269 | // belongProvinceId.value = form.value.belongProvinceId |
| 270 | parentId.value = form.value.parentId | 270 | // parentId.value = form.value.parentId |
| 271 | 271 | ||
| 272 | if (form.value.regionId) { | 272 | if (form.value.regionId) { |
| 273 | form.value.coordinates1 = form.value.regionId | 273 | form.value.coordinates1 = form.value.regionId | ... | ... |
This diff is collapsed.
Click to expand it.
| ... | @@ -219,7 +219,7 @@ | ... | @@ -219,7 +219,7 @@ |
| 219 | display: flex; | 219 | display: flex; |
| 220 | /* #endif */ | 220 | /* #endif */ |
| 221 | align-items: center; | 221 | align-items: center; |
| 222 | padding: 8px 10px; | 222 | padding: 18px 10px; |
| 223 | padding-right: 5px; | 223 | padding-right: 5px; |
| 224 | padding-left: 10px; | 224 | padding-left: 10px; |
| 225 | } | 225 | } | ... | ... |
-
Please register or sign in to post a comment