修改名称,优化

This commit is contained in:
ytc1012
2026-02-13 15:43:40 +08:00
parent 0f3ee050dc
commit 36a2f15a88
12 changed files with 452 additions and 16 deletions

372
CLAUDE.md Normal file
View File

@@ -0,0 +1,372 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**"点线得面"** - A WeChat Mini Program for collaborative meeting point selection. Users create rooms, share with friends, each member adds their location, and the system calculates the optimal meeting venue based on everyone's coordinates. Maximum 8 members per room, and rooms expire after 30 days.
**Architecture**: WeChat Mini Program frontend + WeChat Cloud Functions backend + MeetSpot Python API (external service)
## Repository Structure
```
WoMenQuNaJu/
├── miniprogram/ # WeChat Mini Program frontend
│ ├── pages/ # Page components (index, create-room, room, result)
│ ├── app.js/json/wxss # Application entry
│ └── images/ # Static assets
├── cloudfunctions/ # WeChat Cloud Functions (Node.js)
│ ├── calculateMeetSpot/ # Core: calls MeetSpot API to calculate venues
│ ├── createRoom/ # Create a new room
│ ├── joinRoom/ # Add member to room (max 8 people)
│ ├── updateLocation/ # Update current user's location
│ ├── updateMemberLocation/ # Update test member's location (dev only)
│ ├── updateRoomInfo/ # Update room metadata
│ ├── removeMember/ # Remove member from room
│ ├── deleteRoom/ # Delete entire room
│ ├── cleanExpiredRooms/ # Scheduled: delete rooms older than 30 days
│ ├── addTestMember/ # Add test member (dev only)
│ └── getOpenId/ # Get user's WeChat OpenID
└── MeetSpot/ # Python FastAPI backend (has its own CLAUDE.md)
```
**Key Insight**: The WeChat Mini Program is a lightweight frontend/coordinator. The heavy lifting (geocoding, POI search, venue ranking) is delegated to the MeetSpot FastAPI service via the `calculateMeetSpot` cloud function.
## WeChat Mini Program Development
### Setup
```bash
# Install WeChat Developer Tools
# Download from: https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
# Open project in WeChat Developer Tools
# Project directory: d:\download\WoMenQuNaJu
# AppID: wxb7e6344198f6526f (from project.config.json)
```
**Cloud Functions**: Each function in `cloudfunctions/` needs dependencies installed separately:
```bash
# For cloud functions that use axios (like calculateMeetSpot)
cd cloudfunctions/calculateMeetSpot
npm install
# Repeat for other functions with package.json
cd ../createRoom
npm install
```
**Deploy Cloud Functions**: Use WeChat Developer Tools GUI to upload cloud functions, or use CLI:
```bash
# Upload a single cloud function
miniprogram-ci cloud uploadFunction --functionName calculateMeetSpot
```
### Testing
**Local Testing**: Use WeChat Developer Tools simulator. The simulator supports:
- Cloud function calls
- Cloud database operations
- Location services (mocked)
**Backend URL Configuration**:
- `calculateMeetSpot/index.js:78` contains `BACKEND_URL` environment variable
- Default: `https://meetspot.onrender.com` (production MeetSpot API)
- For local testing: Set cloud function env var to `http://localhost:8000` (requires ngrok or similar for Mini Program to reach local machine)
### Data Model
**Cloud Database Collection**: `rooms`
**Data Retention**: Rooms are automatically deleted 30 days after creation. A scheduled cloud function `cleanExpiredRooms` runs periodically to clean up expired data.
```javascript
{
_id: "auto-generated",
_openid: "creator's openid",
createdAt: ServerDate,
expireTime: Date, // Auto-delete 30 days after creation
status: "active" | "calculating" | "calculated",
name: "聚会名称",
meetTime: "2026-02-10 18:00",
keywords: "咖啡馆",
requirements: "parking, quiet",
members: [
{
openid: "wx-openid",
avatarUrl: "https://...",
nickName: "用户昵称",
location: {
lng: 116.32,
lat: 39.99,
address: "北京大学",
name: "北京大学"
},
joinedAt: Date,
isTestMember: false // true for dev test members
}
],
result: {
success: true,
venues: [...],
center: { lat: 39.99, lng: 116.32 },
html_url: "/workspace/js_src/recommendation_xxx.html"
},
lastCalculateTime: ServerDate,
lastCalculatedMembers: "openid1_lat_lng,openid2_lat_lng,..." // Snapshot for duplicate detection
}
```
**Member Limit**: Maximum 8 members per room. Enforced in `joinRoom` cloud function.
## Architecture Patterns
### Status Locking Pattern
**Problem**: Multiple users clicking "Calculate" simultaneously could trigger duplicate API calls.
**Solution**: `calculateMeetSpot` uses database-level status locking:
```javascript
// 1. Check if status is "calculating" (early return if locked)
if (data.status === 'calculating') return { msg: '正在计算中' }
// 2. Acquire lock
await db.collection('rooms').doc(roomId).update({ status: 'calculating' })
// 3. Call backend API
const result = await axios.post(BACKEND_URL, ...)
// 4. Release lock and save result
await db.collection('rooms').doc(roomId).update({
status: 'calculated',
result: result.data
})
```
**Exception Handling**: If API call fails, the `catch` block releases the lock by setting `status: 'active'`.
### Duplicate Calculation Prevention
**Problem**: Users might click "Calculate" multiple times without changing member locations.
**Solution**: `calculateMeetSpot/index.js:43-61` compares current member positions against `lastCalculatedMembers` snapshot:
```javascript
const currentSnapshot = validMembers
.map(m => `${m.openid}_${m.location.lat}_${m.location.lng}`)
.sort()
.join(',')
if (currentSnapshot === data.lastCalculatedMembers) {
return { isDuplicate: true, existingResult: data.result }
}
```
### Real-time Synchronization
**Pattern**: Database Watch API for live updates
**Implementation**: `miniprogram/pages/room/room.js:125-179`
```javascript
db.collection('rooms').doc(roomId).watch({
onChange: snapshot => {
// Sync room state, member list, calculation status
// Auto-navigate to result page when status changes from 'calculating' to 'calculated'
}
})
```
**Critical Detail**: Uses `previousStatus` tracking to only auto-navigate when calculation **just finished**, preventing navigation loops when users return to the room page.
### Test Member System
**Purpose**: Allow room creators to add mock members for testing without needing multiple WeChat accounts.
**Identification**: Test members have `isTestMember: true` and openid format `test_<timestamp>_<random>`
**Special Handling**:
- `addTestMember`: Creates member with random location (Beijing area)
- `updateMemberLocation`: Allows creator to update test member locations
- `removeMember`: Can delete test members
- UI shows "测试" badge and allows creator to edit their locations
## Cloud Functions
### Core Function: calculateMeetSpot
**Responsibility**: Bridge between Mini Program and MeetSpot API
**Flow**:
1. Validate: At least 2 members with locations
2. Check: Duplicate calculation detection
3. Lock: Set status to `calculating`
4. Transform: Extract `{ lng, lat, address, name }` from members
5. Call: POST to `BACKEND_URL/api/miniprogram/calculate`
6. Store: Save result to database, update status to `calculated`
**Environment Variables**: Set in WeChat Cloud Console
- `BACKEND_URL`: MeetSpot API endpoint (default: `https://meetspot.onrender.com`)
**Timeout**: 30 seconds (axios timeout). MeetSpot API typically responds in 3-15 seconds.
### Database Mutating Functions
| Function | Mutation | Permission Check |
|----------|----------|------------------|
| `createRoom` | Add document to `rooms` | Anyone (creator = caller's openid) |
| `joinRoom` | Push to `members` array | Anyone (duplicate check by openid) |
| `updateLocation` | Update member's location in array | Own openid only |
| `updateMemberLocation` | Update test member location | Creator only |
| `updateRoomInfo` | Update room metadata | Creator only (checked via `_openid`) |
| `removeMember` | Remove from `members` array | Self-exit OR creator removing others |
| `deleteRoom` | Delete entire document | Creator only |
**Security Note**: Cloud Functions automatically add `_openid` field (caller's WeChat ID) to database writes. Permission checks compare `wxContext.OPENID` against stored `_openid`.
## MeetSpot Backend Integration
The `MeetSpot/` subdirectory is a separate Python/FastAPI project. See [MeetSpot/CLAUDE.md](MeetSpot/CLAUDE.md) for details.
**Key Integration Point**: `calculateMeetSpot` cloud function calls `POST /api/miniprogram/calculate` endpoint.
**Expected Request**:
```json
{
"locations": [
{ "lng": 116.32, "lat": 39.99, "address": "北京大学", "name": "北京大学" }
],
"keywords": "咖啡馆",
"requirements": "parking, quiet"
}
```
**Expected Response**:
```json
{
"success": true,
"venues": [ /* Array of ranked venues */ ],
"center": { "lat": 39.99, "lng": 116.32 },
"html_url": "/workspace/js_src/recommendation_xxx.html"
}
```
**To run MeetSpot locally**:
```bash
cd MeetSpot
python web_server.py
# Backend will be at http://localhost:8000
```
## Common Development Tasks
### Adding a New Cloud Function
1. Create directory: `cloudfunctions/myFunction/`
2. Add `index.js`:
```javascript
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
// Your logic here
return { success: true }
}
```
3. Add `package.json` if using npm dependencies
4. Install dependencies: `npm install` (in function directory)
5. Upload via WeChat Developer Tools
### Modifying Room Schema
**Important**: WeChat Cloud Database is schemaless, but for consistency:
1. Update `createRoom/index.js` for new fields in initial data
2. Add migration logic in `calculateMeetSpot` or other functions if transforming existing data
3. Update TypeScript interfaces if using typed Mini Program (this project uses plain JS)
### Changing Keywords/Requirements Options
**Keywords Picker**: `miniprogram/pages/room/room.js:10`
```javascript
keywordOptions: ['咖啡馆', '餐厅', '公园', ...]
```
**Requirements**: Free-text input, no predefined options. MeetSpot API parses natural language.
### Debugging Cloud Functions
**Method 1**: WeChat Developer Tools Console
- Right-click function → "Upload and Debug"
- Use `console.log()` - logs appear in Cloud Development Console
**Method 2**: Local Cloud Function Emulation
- Install `miniprogram-ci`
- Use `npx miniprogram-ci cloud emulator`
## Performance Considerations
**Cold Start**: Cloud functions have ~500ms-2s cold start. First calculation in a room may feel slow.
**Database Watch**: Each page maintains one watch connection. WeChat limits 5 concurrent watches per user. Close watches in `onUnload()`.
**Calculation Time**: Typical flow takes 5-15 seconds:
- Cloud function startup: 0.5-2s
- MeetSpot API call: 3-12s (includes geocoding, POI search, ranking)
- Database write: 0.1-0.5s
**Optimization**: Duplicate calculation prevention saves ~90% of redundant API calls.
## Deployment
### Mini Program
1. Update version in WeChat Developer Tools
2. Click "Upload" → Enter version number and description
3. Submit for review in WeChat Public Platform
4. After approval, release to production
### Cloud Functions
**Individual Upload**: Right-click function → "Upload and Deploy: Cloud Installation Dependencies"
**Batch Upload**: Cloud Development Console → Cloud Functions → Batch upload
**CI/CD**: Use `miniprogram-ci` for automated deployment:
```bash
miniprogram-ci upload --project /path/to/project \
--appid wxb7e6344198f6526f \
--version 1.0.0 \
--desc "Update description"
```
## Debugging Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| "未找到云函数" | Function not uploaded | Upload via Developer Tools |
| "正在计算中" stuck | Lock not released after error | Manually set `status: 'active'` in database |
| Empty result page | BACKEND_URL unreachable | Check cloud function logs, verify MeetSpot API is running |
| Location picker fails | Missing permission | Check `app.json``permission``scope.userLocation` |
| Watch connection lost | Network interruption | Page will auto-reconnect on next data change |
| "至少需要2人" error | Only 1 member has location | Add more member locations or use test members |
**Logs**: Cloud Development Console → Cloud Functions → Function logs (search by `requestId`)
## Code Style
- **JavaScript**: 4-space indent, ES6+ syntax, no semicolons (Mini Program convention)
- **Cloud Functions**: Use `async/await`, avoid callbacks
- **Naming**: `camelCase` for variables/functions, `PascalCase` for pages/components
- **Comments**: Chinese for user-facing strings, English for technical comments acceptable
## Related Documentation
- WeChat Mini Program Docs: https://developers.weixin.qq.com/miniprogram/dev/framework/
- Cloud Functions Guide: https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/capabilities.html
- MeetSpot Backend: See `MeetSpot/CLAUDE.md` for Python/FastAPI development guide

View File

@@ -0,0 +1,35 @@
// 云函数入口文件 - 定时清理过期聚会
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
// 云函数入口函数
exports.main = async (event, context) => {
try {
const now = new Date()
// 删除30天前创建的聚会
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
const result = await db.collection('rooms').where({
createdAt: _.lt(thirtyDaysAgo)
}).remove()
console.log(`清理完成,删除了 ${result.stats.removed} 个过期聚会`)
return {
success: true,
deleted: result.stats.removed,
msg: `清理完成,删除了 ${result.stats.removed} 个过期聚会`
}
} catch (err) {
console.error('清理过期聚会失败:', err)
return {
success: false,
msg: '清理失败',
error: err
}
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "cleanExpiredRooms",
"version": "1.0.0",
"description": "定时清理过期聚会数据",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}

View File

@@ -12,10 +12,14 @@ exports.main = async (event, context) => {
const { userInfo, keywords, requirements, name, meetTime } = event
try {
// 计算过期时间30天后
const expireTime = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
const res = await db.collection('rooms').add({
data: {
_openid: openid, // 创建者openid
createdAt: db.serverDate(),
expireTime: expireTime, // 过期时间
status: 'active',
name: name || '未命名聚会',
meetTime: meetTime || '',

View File

@@ -27,7 +27,15 @@ exports.main = async (event, context) => {
const members = room.data.members || []
const isMember = members.some(m => m.openid === openid)
if (!isMember) {
if (isMember) {
return { success: true, msg: '已在房间内' }
}
// 检查人数限制最多8人
if (members.length >= 8) {
return { success: false, msg: '房间人数已满最多8人' }
}
// 加入房间
const newMember = {
openid: openid,

View File

@@ -8,7 +8,7 @@
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "我们去哪聚",
"navigationBarTitleText": "点线得面",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json",

View File

@@ -1,4 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "我们去哪聚"
"navigationBarTitleText": "点线得面"
}

View File

@@ -1,7 +1,7 @@
<view class="container">
<view class="header">
<view class="title">我们去哪聚</view>
<view class="subtitle">找到你们的完美聚会点</view>
<view class="title">点线得面</view>
<view class="subtitle">找到大家的聚会好去处</view>
<button class="primary-btn" bindtap="onCreateRoom">发起聚会</button>
</view>
@@ -50,6 +50,6 @@
</view>
<view class="desc-area">
<view class="desc">分享给好友,共同寻找中间点</view>
<view class="desc">分享给好友,共同寻找聚会好去处</view>
</view>
</view>

View File

@@ -100,7 +100,7 @@ Page({
wx.hideLoading()
if (!res.result.success) {
wx.showModal({ title: '错误', content: res.result.msg, showCancel: false })
wx.showModal({ title: '提示', content: res.result.msg, showCancel: false })
return false
}
@@ -247,7 +247,7 @@ Page({
onShareAppMessage() {
return {
title: '快来填位置,我们去找个中间点聚会',
title: '点线得面 - 找到大家的聚会好去处',
path: `/pages/room/room?roomId=${this.data.roomId}`
}
},
@@ -431,9 +431,9 @@ Page({
onEditRoomName() {
wx.showModal({
title: '修改聚会名称',
content: '请输入新的聚会名称',
content: this.data.roomName,
editable: true,
placeholderText: this.data.roomName,
placeholderText: '请输入聚会名称',
success: (res) => {
if (res.confirm && res.content.trim()) {
const newName = res.content.trim()

View File

@@ -68,6 +68,7 @@
<text class="stat-divider">|</text>
<text class="stat-item ready-count">已就位 {{readyCount}}人</text>
</view>
<view class="limit-tip">最多8人</view>
</view>
<!-- 成员列表 -->
@@ -94,10 +95,10 @@
</block>
</scroll-view>
<!-- 测试按钮(浮动) -->
<view class="test-float-btn" bindtap="onAddTestMember" wx:if="{{true}}">
<!-- 测试按钮(浮动) - 已注释 -->
<!-- <view class="test-float-btn" bindtap="onAddTestMember" wx:if="{{true}}">
<text>+</text>
</view>
</view> -->
<!-- 底部操作按钮 -->
<view class="footer-actions">

View File

@@ -126,6 +126,13 @@
color: #07c160;
}
.limit-tip {
font-size: 24rpx;
color: #999;
text-align: center;
margin-top: 8rpx;
}
/* 成员列表 */
.member-list {
flex: 1;

View File

@@ -29,7 +29,7 @@
}
},
"appid": "wxb7e6344198f6526f",
"projectname": "我们去哪聚",
"projectname": "点线得面",
"libVersion": "3.14.1",
"condition": {},
"compileType": "miniprogram",