整改一期:发布这条链路先跑顺

四条改动都在"发布一个代喂任务"这条路上:

定位拿不到不再把人卡死。地址可以不带坐标保存,界面标成"未核验定位",
这条地址的打卡照常,只是不比对距离——总比逼着用户放弃发布要好。客户端
另外给了两条退路:去系统设置开权限,或者地图选点。

价格从写死的 30 元改成服务端按物种、城市、每次时长算的推荐价,规则放在
配置里,运营改一次不用等发版;用户改过之后就以他填的为准,不再被覆盖。

发布与支付并成一步:底部常驻"合计 ¥x = n 次 × ¥y",主按钮是"支付并发布"。
没付成的任务留在"我的任务"里能接着付,超过 24 小时由后台关掉,不再变成
既看不见又删不掉的孤儿。

选宠物换成带头像的卡片,缺免疫记录或照片的当场标出来,不用等提交才知道。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-07 10:31:39 +08:00
co-authored by Claude Opus 5
parent 9345805133
commit ebf7189533
6 changed files with 287 additions and 15 deletions
+20 -12
View File
@@ -48,6 +48,7 @@ type feedTaskView struct {
TotalVisits int `json:"totalVisits"`
Done int `json:"completedVisits"`
Items []string `json:"items"`
Located bool `json:"addressLocated"`
Mine bool `json:"mine"`
IsSitter bool `json:"isSitter"`
CreatedAt string `json:"createdAt"`
@@ -98,10 +99,9 @@ func (a *App) createPetAddress(w http.ResponseWriter, r *http.Request) {
fail(w, http.StatusBadRequest, 20001, "地址内容过长")
return
}
if req.Latitude == 0 || req.Longitude == 0 {
fail(w, http.StatusBadRequest, 20001, "请先获取定位,打卡需要与地址比对")
return
}
// 定位可能因为权限、系统或没有地图 SDK 而拿不到。与其把人卡在这一步,
// 不如让他把地址存下来,并记住这条地址没有坐标:打卡照常,只是不做距离核验。
located := req.Latitude != 0 && req.Longitude != 0
// Door number and phone are encrypted at rest; only the coarse area is ever
// shown to people browsing the hall.
detail, err := a.encryptPhone(req.Detail)
@@ -122,19 +122,24 @@ func (a *App) createPetAddress(w http.ResponseWriter, r *http.Request) {
return
}
}
var latitude, longitude any
if located {
latitude, longitude = req.Latitude, req.Longitude
}
result, err := a.db.ExecContext(r.Context(), `INSERT INTO pet_addresses(user_id,city_code,city_name,area_text,detail_cipher,contact_cipher,emergency_cipher,vet_hospital,latitude,longitude)
VALUES(?,?,?,?,?,?,?,?,?,?)`, current(r).ID, strings.TrimSpace(req.CityCode), strings.TrimSpace(req.CityName), req.AreaText,
detail, contact, emergency, req.VetHospital, req.Latitude, req.Longitude)
detail, contact, emergency, req.VetHospital, latitude, longitude)
if err != nil {
fail(w, http.StatusInternalServerError, 50001, "保存失败")
return
}
id, _ := result.LastInsertId()
reply(w, map[string]any{"id": id})
reply(w, map[string]any{"id": id, "located": located})
}
func (a *App) petAddresses(w http.ResponseWriter, r *http.Request) {
rows, err := a.db.QueryContext(r.Context(), `SELECT id,city_name,area_text FROM pet_addresses WHERE user_id=? ORDER BY id DESC`, current(r).ID)
rows, err := a.db.QueryContext(r.Context(), `SELECT id,city_code,city_name,area_text,latitude IS NOT NULL
FROM pet_addresses WHERE user_id=? AND LENGTH(detail_cipher)>0 ORDER BY id DESC`, current(r).ID)
if err != nil {
fail(w, http.StatusInternalServerError, 50001, "查询地址失败")
return
@@ -143,9 +148,12 @@ func (a *App) petAddresses(w http.ResponseWriter, r *http.Request) {
items := []map[string]any{}
for rows.Next() {
var id int64
var city, area string
if rows.Scan(&id, &city, &area) == nil {
items = append(items, map[string]any{"id": id, "cityName": city, "areaText": area})
var cityCode, city, area string
var located bool
if rows.Scan(&id, &cityCode, &city, &area, &located) == nil {
items = append(items, map[string]any{
"id": id, "cityCode": cityCode, "cityName": city, "areaText": area, "located": located,
})
}
}
reply(w, map[string]any{"items": items})
@@ -345,7 +353,7 @@ func (a *App) createFeedTask(w http.ResponseWriter, r *http.Request) {
reply(w, map[string]any{"id": taskID, "grossCent": gross, "platformFeeCent": fee, "payoutCent": payout, "totalVisits": totalVisits})
}
const feedTaskColumns = `t.id,t.owner_user_id,owner.nickname,t.pet_id,p.name,p.species,p.avatar_url,addr.city_name,addr.area_text,
const feedTaskColumns = `t.id,t.owner_user_id,owner.nickname,t.pet_id,p.name,p.species,p.avatar_url,addr.city_name,addr.area_text,addr.latitude IS NOT NULL,
t.start_date,t.end_date,t.visits_per_day,t.min_minutes,t.price_cent,t.gross_cent,t.payout_cent,t.note,t.status,
COALESCE(t.sitter_user_id,0),COALESCE(sitter.nickname,''),COALESCE(t.conversation_id,0),t.applications_count,t.total_visits,t.completed_visits,t.created_at`
@@ -358,7 +366,7 @@ func (a *App) scanFeedTasks(ctx context.Context, rows *sql.Rows, viewer int64) [
var start, end time.Time
var created time.Time
if rows.Scan(&item.ID, &item.OwnerUserID, &item.OwnerName, &item.PetID, &item.PetName, &item.Species, &item.PetAvatar,
&item.CityName, &item.AreaText, &start, &end, &item.VisitsPerDay, &item.MinMinutes, &item.PriceCent,
&item.CityName, &item.AreaText, &item.Located, &start, &end, &item.VisitsPerDay, &item.MinMinutes, &item.PriceCent,
&item.GrossCent, &item.PayoutCent, &item.Note, &item.Status, &item.SitterUserID, &item.SitterName,
&item.Conversation, &item.Applications, &item.TotalVisits, &item.Done, &created) != nil {
continue