7ef3fc22 by 张猛

Merge remote-tracking branch 'origin/master'

2 parents ff58c49a bbc048f7
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
......
1 { 1 {
2 "easycom": {
3 "autoscan": true,
4 "custom": {
5 "^uni-(.*)": "uni_modules/uni-$1/components/uni-$1/uni-$1.vue"
6 }
7 },
2 "pages": [{ 8 "pages": [{
3 "path": "pages/index/index" 9 "path": "pages/index/index"
4 }, 10 },
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
24 <view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress"> 24 <view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
25 <progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4" 25 <progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
26 :backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" /> 26 :backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
27 </view> 27 </view>
28 <view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)"> 28 <view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
29 点击重试 29 点击重试
30 </view> 30 </view>
...@@ -69,10 +69,10 @@ ...@@ -69,10 +69,10 @@
69 borderStyle: {} 69 borderStyle: {}
70 } 70 }
71 } 71 }
72 }, 72 },
73 readonly:{ 73 readonly:{
74 type:Boolean, 74 type:Boolean,
75 default:false 75 default:false
76 } 76 }
77 }, 77 },
78 computed: { 78 computed: {
...@@ -114,7 +114,7 @@ ...@@ -114,7 +114,7 @@
114 let classles = '' 114 let classles = ''
115 for (let i in obj) { 115 for (let i in obj) {
116 classles += `${i}:${obj[i]};` 116 classles += `${i}:${obj[i]};`
117 } 117 }
118 return classles 118 return classles
119 }, 119 },
120 borderLineStyle() { 120 borderLineStyle() {
...@@ -133,19 +133,19 @@ ...@@ -133,19 +133,19 @@
133 } else { 133 } else {
134 width = width.indexOf('px') ? width : width + 'px' 134 width = width.indexOf('px') ? width : width + 'px'
135 } 135 }
136 obj['border-width'] = width 136 obj['border-width'] = width
137 137
138 if (typeof style === 'number') { 138 if (typeof style === 'number') {
139 style += 'px' 139 style += 'px'
140 } else { 140 } else {
141 style = style.indexOf('px') ? style : style + 'px' 141 style = style.indexOf('px') ? style : style + 'px'
142 } 142 }
143 obj['border-top-style'] = style 143 obj['border-top-style'] = style
144 } 144 }
145 let classles = '' 145 let classles = ''
146 for (let i in obj) { 146 for (let i in obj) {
147 classles += `${i}:${obj[i]};` 147 classles += `${i}:${obj[i]};`
148 } 148 }
149 return classles 149 return classles
150 } 150 }
151 }, 151 },
...@@ -176,9 +176,9 @@ ...@@ -176,9 +176,9 @@
176 </script> 176 </script>
177 177
178 <style lang="scss"> 178 <style lang="scss">
179 .uni-file-picker__files { 179 .uni-file-picker__files {
180 /* #ifndef APP-NVUE */ 180 /* #ifndef APP-NVUE */
181 display: flex; 181 display: flex;
182 /* #endif */ 182 /* #endif */
183 flex-direction: column; 183 flex-direction: column;
184 justify-content: flex-start; 184 justify-content: flex-start;
...@@ -194,9 +194,9 @@ ...@@ -194,9 +194,9 @@
194 overflow: hidden; 194 overflow: hidden;
195 } 195 }
196 196
197 .file-picker__mask { 197 .file-picker__mask {
198 /* #ifndef APP-NVUE */ 198 /* #ifndef APP-NVUE */
199 display: flex; 199 display: flex;
200 /* #endif */ 200 /* #endif */
201 justify-content: center; 201 justify-content: center;
202 align-items: center; 202 align-items: center;
...@@ -214,12 +214,12 @@ ...@@ -214,12 +214,12 @@
214 position: relative; 214 position: relative;
215 } 215 }
216 216
217 .uni-file-picker__item { 217 .uni-file-picker__item {
218 /* #ifndef APP-NVUE */ 218 /* #ifndef APP-NVUE */
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 }
...@@ -232,17 +232,17 @@ ...@@ -232,17 +232,17 @@
232 flex: 1; 232 flex: 1;
233 font-size: 14px; 233 font-size: 14px;
234 color: #666; 234 color: #666;
235 margin-right: 25px; 235 margin-right: 25px;
236 /* #ifndef APP-NVUE */ 236 /* #ifndef APP-NVUE */
237 word-break: break-all; 237 word-break: break-all;
238 word-wrap: break-word; 238 word-wrap: break-word;
239 /* #endif */ 239 /* #endif */
240 } 240 }
241 241
242 .icon-files { 242 .icon-files {
243 /* #ifndef APP-NVUE */ 243 /* #ifndef APP-NVUE */
244 position: static; 244 position: static;
245 background-color: initial; 245 background-color: initial;
246 /* #endif */ 246 /* #endif */
247 } 247 }
248 248
...@@ -288,10 +288,10 @@ ...@@ -288,10 +288,10 @@
288 transform: rotate(90deg); 288 transform: rotate(90deg);
289 } 289 }
290 290
291 .icon-del-box { 291 .icon-del-box {
292 /* #ifndef APP-NVUE */ 292 /* #ifndef APP-NVUE */
293 display: flex; 293 display: flex;
294 margin: auto 0; 294 margin: auto 0;
295 /* #endif */ 295 /* #endif */
296 align-items: center; 296 align-items: center;
297 justify-content: center; 297 justify-content: center;
...@@ -322,4 +322,4 @@ ...@@ -322,4 +322,4 @@
322 } 322 }
323 323
324 /* #endif */ 324 /* #endif */
325 </style> 325 </style>
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!