feat: 每日任务顺延与增删改 + 计划到期续期 + 靠谱养护模板 + 图片URL修复

后端
- 每日任务惰性生成:某天为空则从最近一天顺延复制(done 重置),首天用模板
- 任务增删改 API(POST /pets/:id/tasks、PUT/DELETE /tasks/:id),改动自动顺延
- 30 天计划到期自动归档并按当前阶段生成新一轮
- 内置养护模板重写为兽医常识向:狗每阶段含遛狗/牵引/狂犬,猫含猫砂/梳毛/饮水
- 文件 URL 改为按 object_name + 当前配置动态拼接,换 IP/域名不再有旧地址

小程序
- 首页今日任务加「管理」入口:增删改任务弹层
- 记录时间轴显示照片缩略图(可全屏预览)+ 拍照成功提示
- 社区发布条精简为单个「发布图文」按钮,去掉头像/输入框误触

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-06 10:06:13 +08:00
parent 2f5dd6eefc
commit 28b95a6468
21 changed files with 478 additions and 81 deletions
+62
View File
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sundynix/pets-be/internal/middleware" "github.com/sundynix/pets-be/internal/middleware"
"github.com/sundynix/pets-be/internal/service"
"github.com/sundynix/pets-be/pkg/response" "github.com/sundynix/pets-be/pkg/response"
) )
@@ -25,6 +26,67 @@ func (h *Handler) ListTasks(c *gin.Context) {
response.OK(c, tasks) response.OK(c, tasks)
} }
type taskReq struct {
Title string `json:"title"`
Description string `json:"description"`
Priority string `json:"priority"`
SheetType string `json:"sheet_type"`
Date string `json:"date"` // 仅新增用,YYYY-MM-DD,默认今天
}
func (r taskReq) toInput() service.TaskInput {
in := service.TaskInput{Title: r.Title, Description: r.Description, Priority: r.Priority, SheetType: r.SheetType}
if r.Date != "" {
if t, err := time.Parse("2006-01-02", r.Date); err == nil {
in.Date = &t
}
}
return in
}
// CreateTask POST /api/pets/:id/tasks
func (h *Handler) CreateTask(c *gin.Context) {
var req taskReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailParams(c, err.Error())
return
}
if req.Title == "" {
response.FailParams(c, "任务标题不能为空")
return
}
t, err := h.svc.CreateTask(middleware.UserID(c), idParam(c, "id"), req.toInput())
if err != nil {
respondErr(c, err)
return
}
response.OK(c, t)
}
// UpdateTask PUT /api/tasks/:id
func (h *Handler) UpdateTask(c *gin.Context) {
var req taskReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailParams(c, err.Error())
return
}
t, err := h.svc.UpdateTask(middleware.UserID(c), idParam(c, "id"), req.toInput())
if err != nil {
respondErr(c, err)
return
}
response.OK(c, t)
}
// DeleteTask DELETE /api/tasks/:id
func (h *Handler) DeleteTask(c *gin.Context) {
if err := h.svc.DeleteTask(middleware.UserID(c), idParam(c, "id")); err != nil {
respondErr(c, err)
return
}
response.OK(c, gin.H{"deleted": true})
}
// ToggleTask POST /api/tasks/:id/toggle // ToggleTask POST /api/tasks/:id/toggle
func (h *Handler) ToggleTask(c *gin.Context) { func (h *Handler) ToggleTask(c *gin.Context) {
task, err := h.svc.ToggleTask(middleware.UserID(c), idParam(c, "id")) task, err := h.svc.ToggleTask(middleware.UserID(c), idParam(c, "id"))
+3
View File
@@ -59,8 +59,11 @@ func registerUserAPI(api *gin.RouterGroup, h *handler.Handler, jm *appjwt.Manage
g.DELETE("/records/:id", h.DeleteRecord) g.DELETE("/records/:id", h.DeleteRecord)
g.GET("/pets/:id/tasks", h.ListTasks) g.GET("/pets/:id/tasks", h.ListTasks)
g.POST("/pets/:id/tasks", h.CreateTask)
g.POST("/pets/:id/tasks/complete-all", h.CompleteAllTasks) g.POST("/pets/:id/tasks/complete-all", h.CompleteAllTasks)
g.POST("/tasks/:id/toggle", h.ToggleTask) g.POST("/tasks/:id/toggle", h.ToggleTask)
g.PUT("/tasks/:id", h.UpdateTask)
g.DELETE("/tasks/:id", h.DeleteTask)
g.GET("/pets/:id/home-summary", h.HomeSummary) g.GET("/pets/:id/home-summary", h.HomeSummary)
+98 -39
View File
@@ -205,72 +205,131 @@ func careReminder(rtype, title string, offset int, freq string) model.CareTempla
return model.CareTemplateItem{Kind: model.CareKindReminder, ReminderType: rtype, Title: title, OffsetDays: offset, Frequency: freq} return model.CareTemplateItem{Kind: model.CareKindReminder, ReminderType: rtype, Title: title, OffsetDays: offset, Frequency: freq}
} }
// builtinCareItems 内置默认模板(8 套),既作开箱数据也作兜底 // builtinCareItems 内置默认模板(猫/狗 × 4 阶段,共 8 套)。
// 兽医常识向:狗每个阶段都含遛狗/牵引,猫含猫砂盆/梳毛/饮水;疫苗驱虫节奏按物种阶段区分。
func builtinCareItems(species, stage string) []model.CareTemplateItem { func builtinCareItems(species, stage string) []model.CareTemplateItem {
isDog := species == model.SpeciesDog if species == model.SpeciesDog {
dewormAdult := 90 return builtinDog(stage)
if isDog {
dewormAdult = 60 // 狗驱虫间隔通常比猫短
} }
return builtinCat(stage)
}
func builtinCat(stage string) []model.CareTemplateItem {
switch stage { switch stage {
case "刚到家 0-30 天": case "刚到家 0-30 天":
return []model.CareTemplateItem{ return []model.CareTemplateItem{
careTask("记录一次体重", "到家后每周记录,建立基线", "重要", "weight"), careTask("记录一次体重", "到家先称个底,之后每周一次看增长", "重要", "weight"),
careTask("观察饮水与排便", "换环境应激常见软便,连续观察几天", "", "poop"), careTask("清理猫砂盆并观察", "换环境应激常见软便,留意颜色和次数", "", "poop"),
careTask("布置安静休息角", "固定饮食、厕所与休息区,减少应激", "", ""), careTask("陪玩 15 分钟", "逗猫棒建立信任,缓解应激,别强抱", "", ""),
carePlan(0, "今天", "安顿与环境适应", "固定饮食、厕所、休息区位置", "taskDetail"), carePlan(0, "今天", "安顿与环境适应", "猫砂盆、食盆、水碗、猫窝固定位置,给它安静空间", "taskDetail"),
carePlan(3, "第3天", "观察排便与食欲", "记录应激缓解情况", "poop"), carePlan(3, "第3天", "观察进食与排便", "食欲和便便恢复正常说明适应良好", "poop"),
carePlan(7, "第7天", "第一次体重记录", "作为后续趋势基线", "weight"), carePlan(7, "第7天", "第一次体重记录", "作为后续趋势基线", "weight"),
carePlan(14, "第14天", "预约首次体检/疫苗", "确认医院与时间", "vaccine"), carePlan(14, "第14天", "预约首次疫苗/体检", "确认医院、驱虫和免疫计划", "vaccine"),
careReminder("vaccine", "首次疫苗接种", 14, ""), careReminder("vaccine", "首次疫苗接种", 14, ""),
careReminder("deworm", "首次体内外驱虫", 7, ""), careReminder("deworm", "首次体内外驱虫", 7, ""),
careReminder("weight", "体重记录", 0, "每周一次"), careReminder("weight", "体重记录", 0, "每周一次"),
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"), careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
} }
case "幼年期": case "幼年期":
vaccineTitle := "第 2 针疫苗"
if isDog {
vaccineTitle = "下一针联苗"
}
return []model.CareTemplateItem{ return []model.CareTemplateItem{
careTask("记录一次体重", "幼年期每周至少记录 2 次", "重要", "weight"), careTask("记录一次体重", "幼猫长得快,每周记 2 次", "重要", "weight"),
careTask("观察饮水与排便", "换粮、应激都可能影响排便状态", "", "poop"), careTask("梳毛并陪玩", "每天梳毛+两次互动,帮助社会化、减少毛球", "", ""),
careTask("检查疫苗预约", "按免疫程序别漏针", "重要", "vaccine"), careTask("检查疫苗计划", "幼猫三针联苗按时打,别漏针", "重要", "vaccine"),
carePlan(0, "今天", "观察排便状态", "记录颜色、形态、次数", "taskDetail"), carePlan(0, "今天", "确认免疫与驱虫计划", "对照月龄核对疫苗针次和驱虫时间", "vaccine"),
carePlan(1, "天", "检查疫苗预约", "确认下一针医院与时间", "vaccine"), carePlan(3, "第3天", "观察饮水与排便", "换粮期留意软便,7 天过渡换粮", "poop"),
carePlan(7, "第7天", "体重趋势检查", "每周称重,观察稳定增长", "weight"), carePlan(7, "第7天", "体重趋势检查", "每周称重,稳定增长是关键指标", "weight"),
carePlan(14, "第14天", "复盘饮食与便便", "换粮记录与异常一起看", "taskDetail"), carePlan(14, "第14天", "驱虫与饮食复盘", "确认驱虫到期、复盘换粮情况", "medicine"),
careReminder("vaccine", vaccineTitle, 18, ""), careReminder("vaccine", "第 2 针疫苗", 18, ""),
careReminder("deworm", "体内外驱虫", 12, ""), careReminder("deworm", "体内外驱虫", 30, ""),
careReminder("weight", "体重记录", 0, "每周二、周五"), careReminder("weight", "体重记录", 0, "每周二、周五"),
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"), careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
} }
case "老年期": case "老年期":
return []model.CareTemplateItem{ return []model.CareTemplateItem{
careTask("记录一次体重", "老年期体重波动要重视", "重要", "weight"), careTask("记录一次体重", "老年猫消瘦要警惕,别忽视", "重要", "weight"),
careTask("观察精神与食欲", "明显变差要尽快就医", "重要", "symptom"), careTask("关注饮水量与排尿", "多饮多尿常是肾病/糖尿信号,异常尽快就医", "重要", "symptom"),
careTask("关注饮水量", "多饮多尿可能是慢病信号", "", ""), careTask("清理猫砂盆并观察", "关注尿量、尿色和排尿是否困难", "", "poop"),
carePlan(0, "今天", "老年健康自查", "精神、食欲、饮水一起看", "taskDetail"), carePlan(0, "今天", "老年健康自查", "精神、食欲、饮水、体重整体过一遍", "taskDetail"),
carePlan(7, "第7天", "体重与饮水记录", "留意慢病早期信号", "weight"), carePlan(7, "第7天", "体重与饮水记录", "两项一起看,慢病早发现", "weight"),
carePlan(14, "第14天", "用药与体检回顾", "慢病管理与用药记录", "medicine"), carePlan(14, "第14天", "用药与慢病回顾", "整理长期用药和体检结果", "medicine"),
carePlan(30, "第30天", "生成月度报告", "整理给医生看的记录", "report"), carePlan(30, "第30天", "月度报告 / 建议体检", "老年猫建议每半年一次体检", "report"),
careReminder("vaccine", "年度疫苗加强", 30, ""), careReminder("vaccine", "年度疫苗加强", 30, ""),
careReminder("deworm", "体内外驱虫", dewormAdult, ""), careReminder("deworm", "体内外驱虫", 90, ""),
careReminder("weight", "体重记录", 0, "每两周一次"), careReminder("weight", "体重记录", 0, "每两周一次"),
careReminder("monthlyReport", "月度报告 / 建议体检", 0, "每月 1 日"), careReminder("monthlyReport", "月度报告 / 半年体检", 0, "每月 1 日"),
} }
default: // 成年期 default: // 成年期
return []model.CareTemplateItem{ return []model.CareTemplateItem{
careTask("记录一次体重", "成年每月称重一次即可", "", "weight"), careTask("记录一次体重", "成年每月一次,防止肥胖", "", "weight"),
careTask("观察饮水与排便", "发现软便可连续观察几天", "", "poop"), careTask("清理猫砂盆并观察", "泌尿问题早发现,公猫尤其注意尿闭", "", "poop"),
careTask("梳毛与口腔检查", "关注牙结石皮肤状态", "", ""), careTask("梳毛与口腔检查", "关注毛球、牙结石皮肤状态", "", ""),
carePlan(0, "今天", "全面健康自查", "体重、精神、食欲、被毛过一遍", "taskDetail"), carePlan(0, "今天", "全面健康自查", "体重、精神、食欲、饮水、被毛过一遍", "taskDetail"),
carePlan(7, "第7天", "体重趋势检查", "对比上月,观察是否稳定", "weight"), carePlan(7, "第7天", "体重趋势检查", "对比上月,稳定为宜", "weight"),
carePlan(14, "第14天", "驱虫记录复盘", "确认体内外驱虫是否到期", "medicine"), carePlan(14, "第14天", "驱虫记录复盘", "确认体内外驱虫是否到期", "medicine"),
carePlan(30, "第30天", "生成月度报告", "回顾本月记录,导出成长卡片", "report"), carePlan(30, "第30天", "生成月度报告", "回顾本月记录,导出成长卡片", "report"),
careReminder("vaccine", "年度疫苗加强", 30, ""), careReminder("vaccine", "年度疫苗加强", 30, ""),
careReminder("deworm", "体内外驱虫", dewormAdult, ""), careReminder("deworm", "体内外驱虫", 90, ""),
careReminder("weight", "体重记录", 0, "每月一次"),
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
}
}
}
func builtinDog(stage string) []model.CareTemplateItem {
switch stage {
case "刚到家 0-30 天":
return []model.CareTemplateItem{
careTask("记录一次体重", "到家先称个底,之后每周一次", "重要", "weight"),
careTask("定点排便训练", "固定厕所位置,成功就表扬,别惩罚", "", "poop"),
careTask("室内活动 / 自家院子", "疫苗未打全前少去人多狗多的地方,先在家熟悉环境", "", ""),
carePlan(0, "今天", "安顿与环境适应", "食盆、水碗、狗窝、厕所固定位置", "taskDetail"),
carePlan(3, "第3天", "观察进食与排便", "应激期常见软便,留意精神状态", "poop"),
carePlan(7, "第7天", "第一次体重记录", "作为后续趋势基线", "weight"),
carePlan(14, "第14天", "预约疫苗 / 体检", "确认联苗、狂犬和驱虫计划", "vaccine"),
careReminder("vaccine", "首针疫苗接种", 14, ""),
careReminder("deworm", "首次体内外驱虫", 7, ""),
careReminder("weight", "体重记录", 0, "每周一次"),
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
}
case "幼年期":
return []model.CareTemplateItem{
careTask("遛狗与基础训练", "每天 2 次,牵引、坐下、召回,社会化关键期", "重要", ""),
careTask("记录一次体重", "幼犬长得快,每周记 2 次", "重要", "weight"),
careTask("观察饮水与排便", "换粮、驱虫期留意软便", "", "poop"),
carePlan(0, "今天", "确认免疫与驱虫计划", "联苗+狂犬按月龄打,别漏针", "vaccine"),
carePlan(1, "明天", "定点排便 + 牵引训练", "打好如厕和牵引基础", "taskDetail"),
carePlan(7, "第7天", "体重趋势检查", "每周称重,稳定增长", "weight"),
carePlan(14, "第14天", "驱虫与社会化复盘", "确认驱虫到期、多接触人和环境", "medicine"),
careReminder("vaccine", "下一针联苗", 18, ""),
careReminder("deworm", "体内外驱虫", 30, ""),
careReminder("weight", "体重记录", 0, "每周二、周五"),
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
}
case "老年期":
return []model.CareTemplateItem{
careTask("温和遛狗", "缩短时长、避免剧烈跳跃,保护关节", "重要", ""),
careTask("记录一次体重", "骤增骤减都要重视", "重要", "weight"),
careTask("观察关节 / 精神 / 饮水", "后腿无力、多饮多尿要及时就医", "", "symptom"),
carePlan(0, "今天", "老年健康自查", "精神、食欲、饮水、关节活动一起看", "taskDetail"),
carePlan(7, "第7天", "体重与饮水记录", "慢病早发现", "weight"),
carePlan(14, "第14天", "用药与慢病回顾", "整理长期用药和体检结果", "medicine"),
carePlan(30, "第30天", "月度报告 / 建议体检", "老年犬建议每半年一次体检", "report"),
careReminder("vaccine", "年度疫苗 + 狂犬加强", 30, ""),
careReminder("deworm", "体内外驱虫", 60, ""),
careReminder("weight", "体重记录", 0, "每两周一次"),
careReminder("monthlyReport", "月度报告 / 半年体检", 0, "每月 1 日"),
}
default: // 成年期
return []model.CareTemplateItem{
careTask("遛狗", "每天 2 次共 30-60 分钟,消耗精力、防拆家", "重要", ""),
careTask("记录一次体重", "每月一次,控制体重防肥胖", "", "weight"),
careTask("刷牙 / 口腔检查", "每周 2-3 次,狗牙结石高发", "", ""),
carePlan(0, "今天", "全面健康自查", "体重、精神、食欲、被毛、指甲过一遍", "taskDetail"),
carePlan(7, "第7天", "体重趋势检查", "对比上月,稳定为宜", "weight"),
carePlan(14, "第14天", "驱虫记录复盘", "遛狗接触多,确认驱虫是否到期", "medicine"),
carePlan(30, "第30天", "生成月度报告", "回顾本月记录,导出成长卡片", "report"),
careReminder("vaccine", "年度疫苗 + 狂犬加强", 30, ""),
careReminder("deworm", "体内外驱虫", 60, ""),
careReminder("weight", "体重记录", 0, "每月一次"), careReminder("weight", "体重记录", 0, "每月一次"),
careReminder("monthlyReport", "月度报告", 0, "每月 1 日"), careReminder("monthlyReport", "月度报告", 0, "每月 1 日"),
} }
+7 -6
View File
@@ -27,9 +27,10 @@ func (s *Service) UploadFile(reader io.Reader, filename, contentType string) (*m
sum := md5.Sum(data) sum := md5.Sum(data)
md5hex := hex.EncodeToString(sum[:]) md5hex := hex.EncodeToString(sum[:])
// 已有相同内容 → 直接复用 // 已有相同内容 → 直接复用URL 按当前配置重建,避免旧地址)
var existing model.File var existing model.File
if err := s.db.Where("md5 = ?", md5hex).First(&existing).Error; err == nil { if err := s.db.Where("md5 = ?", md5hex).First(&existing).Error; err == nil {
existing.URL = s.storage.PublicURL(existing.ObjectName)
return &existing, nil return &existing, nil
} }
@@ -55,16 +56,16 @@ func (s *Service) UploadFile(reader io.Reader, filename, contentType string) (*m
return &f, nil return &f, nil
} }
// fileURL 按 file_id 取 URL(空 id 或不存在返回空串) // fileURL 按 file_id 取 URL由 object_name + 当前配置拼出,空 id 或不存在返回空串)
func (s *Service) fileURL(id string) string { func (s *Service) fileURL(id string) string {
if id == "" { if id == "" {
return "" return ""
} }
var f model.File var f model.File
if err := s.db.Select("url").First(&f, "id = ?", id).Error; err != nil { if err := s.db.Select("object_name").First(&f, "id = ?", id).Error; err != nil {
return "" return ""
} }
return f.URL return s.storage.PublicURL(f.ObjectName)
} }
// fileURLs 批量按 file_id 取 URL // fileURLs 批量按 file_id 取 URL
@@ -80,9 +81,9 @@ func (s *Service) fileURLs(ids []string) map[string]string {
return out return out
} }
var files []model.File var files []model.File
s.db.Where("id IN ?", clean).Find(&files) s.db.Select("id, object_name").Where("id IN ?", clean).Find(&files)
for _, f := range files { for _, f := range files {
out[f.ID] = f.URL out[f.ID] = s.storage.PublicURL(f.ObjectName)
} }
return out return out
} }
+41 -3
View File
@@ -24,14 +24,52 @@ func (s *Service) TogglePlanTask(userID, taskID string) error {
return s.db.Model(&model.PlanTask{}).Where("id = ?", taskID).Update("done", !pt.Done).Error return s.db.Model(&model.PlanTask{}).Where("id = ?", taskID).Update("done", !pt.Done).Error
} }
// GetPlan 宠物 30 天计划(含明细 // newStagePlan 宠物当前阶段的养护模板生成一份 30 天计划(未保存
func (s *Service) newStagePlan(pet *model.Pet, start time.Time) model.Plan {
var planTasks []model.PlanTask
for _, it := range s.careTemplateItems(SpeciesOf(pet.Type), pet.Stage) {
if it.Kind == model.CareKindPlan {
planTasks = append(planTasks, model.PlanTask{
Day: it.Day, DayLabel: it.DayLabel, Title: it.Title, Description: it.Description, SheetType: it.SheetType,
})
}
}
end := start.AddDate(0, 0, 30)
return model.Plan{
PetID: pet.ID, UserID: pet.UserID, Kind: model.PlanThirtyDay, Stage: pet.Stage,
StartDate: &start, EndDate: &end, CompletionPct: 0, Status: "active", Tasks: planTasks,
}
}
// ensurePlan 计划到期自动续期:active 计划过期则归档并按当前阶段生成新一轮;无 active 则补建
func (s *Service) ensurePlan(pet *model.Pet) {
now := time.Now()
var plan model.Plan
err := s.db.Where("pet_id = ? AND kind = ? AND status = ?", pet.ID, model.PlanThirtyDay, "active").
Order("id desc").First(&plan).Error
if err == gorm.ErrRecordNotFound {
p := s.newStagePlan(pet, now)
s.db.Create(&p)
return
}
if err == nil && plan.EndDate != nil && plan.EndDate.Before(now) {
s.db.Model(&model.Plan{}).Where("id = ?", plan.ID).Update("status", "archived")
p := s.newStagePlan(pet, now)
s.db.Create(&p)
}
}
// GetPlan 取宠物的 30 天计划(含明细);过期自动续期
func (s *Service) GetPlan(userID, petID string) (*model.Plan, error) { func (s *Service) GetPlan(userID, petID string) (*model.Plan, error) {
if _, err := s.ownedPet(userID, petID); err != nil { pet, err := s.ownedPet(userID, petID)
if err != nil {
return nil, err return nil, err
} }
s.ensurePlan(pet)
var plan model.Plan var plan model.Plan
err := s.db.Preload("Tasks"). err = s.db.Preload("Tasks").
Where("pet_id = ? AND kind = ?", petID, model.PlanThirtyDay). Where("pet_id = ? AND kind = ?", petID, model.PlanThirtyDay).
Where("status = ?", "active").
Order("id desc").First(&plan).Error Order("id desc").First(&plan).Error
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return nil, ErrNotFound return nil, ErrNotFound
+4 -4
View File
@@ -39,18 +39,18 @@ func (s *Service) ListRecords(userID, petID string, recordType string, offset, l
} }
var records []model.HealthRecord var records []model.HealthRecord
err := q.Order("occurred_at desc, id desc").Offset(offset).Limit(limit).Find(&records).Error err := q.Order("occurred_at desc, id desc").Offset(offset).Limit(limit).Find(&records).Error
// file_id 回填图片 URL // file_id 的记录,图片 URL 一律按当前配置重建(避免存旧地址)
ids := make([]string, 0) ids := make([]string, 0)
for i := range records { for i := range records {
if records[i].ImageFileID != "" && records[i].ImageURL == "" { if records[i].ImageFileID != "" {
ids = append(ids, records[i].ImageFileID) ids = append(ids, records[i].ImageFileID)
} }
} }
if len(ids) > 0 { if len(ids) > 0 {
urls := s.fileURLs(ids) urls := s.fileURLs(ids)
for i := range records { for i := range records {
if records[i].ImageURL == "" { if u := urls[records[i].ImageFileID]; u != "" {
records[i].ImageURL = urls[records[i].ImageFileID] records[i].ImageURL = u
} }
} }
} }
+121 -9
View File
@@ -8,20 +8,132 @@ import (
"github.com/sundynix/pets-be/internal/model" "github.com/sundynix/pets-be/internal/model"
) )
// ListTasks 列出宠物任务(date 为空则取今天) func dayStart(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// ensureDayTasks 惰性生成某天的任务:今天/未来若为空,则从「最近一次有任务的那天」顺延复制,
// 无历史则用养护模板。过去的日子不补(保留历史真实)。
func (s *Service) ensureDayTasks(pet *model.Pet, day time.Time) {
start := dayStart(day)
if start.Before(dayStart(time.Now())) {
return
}
end := start.AddDate(0, 0, 1)
var cnt int64
s.db.Model(&model.DailyTask{}).Where("pet_id = ? AND task_date >= ? AND task_date < ?", pet.ID, start, end).Count(&cnt)
if cnt > 0 {
return
}
var defs []model.DailyTask
var latest model.DailyTask
if err := s.db.Where("pet_id = ? AND task_date < ?", pet.ID, start).Order("task_date desc, id desc").First(&latest).Error; err == nil {
ls := dayStart(latest.TaskDate)
var prev []model.DailyTask
s.db.Where("pet_id = ? AND task_date >= ? AND task_date < ?", pet.ID, ls, ls.AddDate(0, 0, 1)).Order("id asc").Find(&prev)
for _, t := range prev {
defs = append(defs, model.DailyTask{
PetID: pet.ID, UserID: pet.UserID, TaskDate: start,
Title: t.Title, Description: t.Description, Priority: t.Priority, SheetType: t.SheetType,
})
}
} else {
for _, it := range s.careTemplateItems(SpeciesOf(pet.Type), pet.Stage) {
if it.Kind == model.CareKindTask {
defs = append(defs, model.DailyTask{
PetID: pet.ID, UserID: pet.UserID, TaskDate: start,
Title: it.Title, Description: it.Description, Priority: it.Priority, SheetType: it.SheetType,
})
}
}
}
if len(defs) > 0 {
s.db.Create(&defs)
}
}
// ListTasks 列出宠物某天任务(date 为空则取今天);今天/未来为空会惰性生成
func (s *Service) ListTasks(userID, petID string, date *time.Time) ([]model.DailyTask, error) { func (s *Service) ListTasks(userID, petID string, date *time.Time) ([]model.DailyTask, error) {
pet, err := s.ownedPet(userID, petID)
if err != nil {
return nil, err
}
target := time.Now()
if date != nil {
target = *date
}
s.ensureDayTasks(pet, target)
start := dayStart(target)
end := start.AddDate(0, 0, 1)
var tasks []model.DailyTask
err = s.db.Where("pet_id = ? AND task_date >= ? AND task_date < ?", petID, start, end).
Order("id asc").Find(&tasks).Error
return tasks, err
}
// TaskInput 增/改任务入参
type TaskInput struct {
Title string
Description string
Priority string
SheetType string
Date *time.Time // 仅新增用,默认今天
}
// CreateTask 为某天新增一条任务(默认今天)
func (s *Service) CreateTask(userID, petID string, in TaskInput) (*model.DailyTask, error) {
if _, err := s.ownedPet(userID, petID); err != nil { if _, err := s.ownedPet(userID, petID); err != nil {
return nil, err return nil, err
} }
q := s.db.Where("pet_id = ?", petID) day := time.Now()
if date != nil { if in.Date != nil {
start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) day = *in.Date
end := start.AddDate(0, 0, 1)
q = q.Where("task_date >= ? AND task_date < ?", start, end)
} }
var tasks []model.DailyTask t := model.DailyTask{
err := q.Order("id asc").Find(&tasks).Error PetID: petID, UserID: userID, TaskDate: dayStart(day),
return tasks, err Title: in.Title, Description: in.Description, Priority: in.Priority, SheetType: in.SheetType,
}
if err := s.db.Create(&t).Error; err != nil {
return nil, err
}
return &t, nil
}
// UpdateTask 修改任务文案/优先级/关联弹层
func (s *Service) UpdateTask(userID, taskID string, in TaskInput) (*model.DailyTask, error) {
var t model.DailyTask
if err := s.db.Where("id = ? AND user_id = ?", taskID, userID).First(&t).Error; err != nil {
return nil, ErrNotFound
}
if err := s.db.Model(&model.DailyTask{}).Where("id = ?", taskID).Updates(map[string]any{
"title": in.Title, "description": in.Description, "priority": in.Priority, "sheet_type": in.SheetType,
}).Error; err != nil {
return nil, err
}
return s.UpdateTaskReload(taskID)
}
// UpdateTaskReload 重新读取任务
func (s *Service) UpdateTaskReload(taskID string) (*model.DailyTask, error) {
var t model.DailyTask
if err := s.db.First(&t, "id = ?", taskID).Error; err != nil {
return nil, ErrNotFound
}
return &t, nil
}
// DeleteTask 删除任务
func (s *Service) DeleteTask(userID, taskID string) error {
res := s.db.Where("id = ? AND user_id = ?", taskID, userID).Delete(&model.DailyTask{})
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return ErrNotFound
}
return nil
} }
// ToggleTask 切换任务完成状态 // ToggleTask 切换任务完成状态
+6 -1
View File
@@ -81,5 +81,10 @@ func (s *Storage) Upload(objectName string, reader io.Reader, size int64, conten
if err != nil { if err != nil {
return "", fmt.Errorf("put object: %w", err) return "", fmt.Errorf("put object: %w", err)
} }
return fmt.Sprintf("%s/%s/%s", s.publicBaseURL, s.bucket, objectName), nil return s.PublicURL(objectName), nil
}
// PublicURL 由对象名拼出可访问 URL(用当前配置的 public_base_url,避免存旧地址)
func (s *Storage) PublicURL(objectName string) string {
return fmt.Sprintf("%s/%s/%s", s.publicBaseURL, s.bucket, objectName)
} }
+1
View File
@@ -57,6 +57,7 @@ page{scrollbar-width:none;-ms-overflow-style:none}
.link{ .link{
color:var(--primary-dark);font-weight:900;font-size:26rpx; color:var(--primary-dark);font-weight:900;font-size:26rpx;
} }
.head-links{display:flex;align-items:center;gap:26rpx}
/* 页面标题 */ /* 页面标题 */
.page-title{margin:4rpx 0 28rpx} .page-title{margin:4rpx 0 28rpx}
@@ -20,6 +20,18 @@ function ageFromBirthday(bday) {
const GENDERS = ['男孩', '女孩', '不确定']; const GENDERS = ['男孩', '女孩', '不确定'];
const STAGES = ['刚到家 0-30 天', '幼年期', '成年期', '老年期']; const STAGES = ['刚到家 0-30 天', '幼年期', '成年期', '老年期'];
// 任务可关联的记录弹层类型
const TASK_SHEETS = [
{ key: '', label: '不关联' },
{ key: 'weight', label: '体重' },
{ key: 'poop', label: '便便' },
{ key: 'food', label: '饮食' },
{ key: 'symptom', label: '异常' },
{ key: 'vaccine', label: '疫苗' },
{ key: 'medicine', label: '用药' },
];
const TASK_SHEET_LABELS = TASK_SHEETS.map((s) => s.label);
const POOP = ['正常', '软便', '拉稀']; const POOP = ['正常', '软便', '拉稀'];
const FOOD = ['正常', '偏少', '不吃']; const FOOD = ['正常', '偏少', '不吃'];
const COST = ['食品', '医疗', '用品']; const COST = ['食品', '医疗', '用品'];
@@ -44,6 +56,9 @@ Component({
costAmount: '', costAmount: '',
postContent: '', postContent: '',
postImages: [], postImages: [],
manageTasks: [],
taskForm: { id: '', title: '', description: '', priority: '', sheetIdx: 0 },
taskSheetLabels: TASK_SHEET_LABELS,
addName: '', addName: '',
addBirthday: '', addBirthday: '',
addWeight: '', addWeight: '',
@@ -85,6 +100,8 @@ Component({
patch.costAmount = ''; patch.costAmount = '';
patch.postContent = ''; patch.postContent = '';
patch.postImages = []; patch.postImages = [];
patch.manageTasks = [];
patch.taskForm = { id: '', title: '', description: '', priority: '', sheetIdx: 0 };
patch.addName = ''; patch.addName = '';
patch.addBirthday = ''; patch.addBirthday = '';
patch.addWeight = ''; patch.addWeight = '';
@@ -127,7 +144,9 @@ Component({
// 按弹层类型拉取真实数据 // 按弹层类型拉取真实数据
loadSheetData(type) { loadSheetData(type) {
const id = store.currentPetId(); const id = store.currentPetId();
if (type === 'comments' && this.data.postId) { if (type === 'manageTasks') {
this.loadManageTasks();
} else if (type === 'comments' && this.data.postId) {
api.listComments(this.data.postId).then((page) => this.setData({ comments: page.list || [] })).catch(() => {}); api.listComments(this.data.postId).then((page) => this.setData({ comments: page.list || [] })).catch(() => {});
} else if (type === 'reminders' && id) { } else if (type === 'reminders' && id) {
api api
@@ -195,6 +214,53 @@ Component({
onWNote(e) { this.setData({ wNote: e.detail.value }); }, onWNote(e) { this.setData({ wNote: e.detail.value }); },
onCostInput(e) { this.setData({ costAmount: e.detail.value }); }, onCostInput(e) { this.setData({ costAmount: e.detail.value }); },
onPostInput(e) { this.setData({ postContent: e.detail.value }); }, onPostInput(e) { this.setData({ postContent: e.detail.value }); },
// ---- 管理任务 ----
loadManageTasks() {
const id = store.currentPetId();
if (!id) return;
api.getTasks(id).then((tasks) => this.setData({ manageTasks: tasks || [] })).catch(() => {});
},
onTaskTitle(e) { this.setData({ 'taskForm.title': e.detail.value }); },
onTaskDesc(e) { this.setData({ 'taskForm.description': e.detail.value }); },
onTaskPriority() {
this.setData({ 'taskForm.priority': this.data.taskForm.priority === '重要' ? '' : '重要' });
},
onTaskSheet(e) { this.setData({ 'taskForm.sheetIdx': Number(e.detail.value) }); },
onEditTaskItem(e) {
const t = this.data.manageTasks[e.currentTarget.dataset.index];
let sheetIdx = TASK_SHEETS.findIndex((s) => s.key === (t.sheet_type || ''));
if (sheetIdx < 0) sheetIdx = 0;
this.setData({
taskForm: { id: t.id, title: t.title, description: t.description || '', priority: t.priority || '', sheetIdx },
});
},
onSaveTask() {
const f = this.data.taskForm;
const title = (f.title || '').trim();
if (!title) return wx.showToast({ title: '填个任务名', icon: 'none' });
const body = { title, description: f.description, priority: f.priority, sheet_type: TASK_SHEETS[f.sheetIdx].key };
const id = store.currentPetId();
const p = f.id ? api.updateTask(f.id, body) : api.createTask(id, body);
p.then(() => {
this.setData({ taskForm: { id: '', title: '', description: '', priority: '', sheetIdx: 0 } });
this.loadManageTasks();
this.triggerEvent('saved');
}).catch((e) => wx.showToast({ title: e.message || '保存失败', icon: 'none' }));
},
onDeleteTaskItem(e) {
const t = this.data.manageTasks[e.currentTarget.dataset.index];
wx.showModal({
title: '删除任务',
content: '确定删除「' + t.title + '」?',
success: (r) => {
if (!r.confirm) return;
api.deleteTask(t.id).then(() => {
this.loadManageTasks();
this.triggerEvent('saved');
}).catch((err) => wx.showToast({ title: err.message || '删除失败', icon: 'none' }));
},
});
},
onPickPostImages() { onPickPostImages() {
const left = 9 - this.data.postImages.length; const left = 9 - this.data.postImages.length;
if (left <= 0) return wx.showToast({ title: '最多 9 张', icon: 'none' }); if (left <= 0) return wx.showToast({ title: '最多 9 张', icon: 'none' });
@@ -326,6 +392,7 @@ Component({
api.createRecord(id, { type: 'photo', icon: '📷', title: '成长照片', image_file_id: f.id, image_url: f.url }), api.createRecord(id, { type: 'photo', icon: '📷', title: '成长照片', image_file_id: f.id, image_url: f.url }),
) )
.then((saved) => { .then((saved) => {
wx.showToast({ title: '照片已保存', icon: 'success' });
this.triggerEvent('saved', saved); this.triggerEvent('saved', saved);
this.close(); this.close();
}) })
@@ -273,6 +273,34 @@ module.exports.sel = function (map, key, index, def) {
<button class="btn btn-primary btn-block" bindtap="onCreatePost">发布到宠友圈</button> <button class="btn btn-primary btn-block" bindtap="onCreatePost">发布到宠友圈</button>
</block> </block>
<!-- 管理任务 -->
<block wx:elif="{{innerType === 'manageTasks'}}">
<view class="sheet-h3">管理任务</view>
<view class="sheet-p">增删改今日任务,改动会自动顺延到以后每天。</view>
<view wx:for="{{manageTasks}}" wx:key="id" class="mtask-row">
<view class="mtask-body">
<text class="mtask-b">{{item.title}}<text wx:if="{{item.priority}}" class="mtask-pri">{{item.priority}}</text></text>
<view wx:if="{{item.description}}" class="mtask-p">{{item.description}}</view>
</view>
<view class="mtask-op" catchtap="onEditTaskItem" data-index="{{index}}">改</view>
<view class="mtask-op del" catchtap="onDeleteTaskItem" data-index="{{index}}">删</view>
</view>
<view wx:if="{{!manageTasks.length}}" class="sheet-p">今天还没有任务,下面加一条吧。</view>
<view class="field" style="margin-top:12rpx"><label>{{taskForm.id ? '修改任务' : '新增任务'}}</label>
<input class="input" placeholder="任务名,例如:喂驱虫药" placeholder-class="placeholder" value="{{taskForm.title}}" bindinput="onTaskTitle"/></view>
<view class="field"><input class="input" placeholder="说明(可选)" placeholder-class="placeholder" value="{{taskForm.description}}" bindinput="onTaskDesc"/></view>
<view class="field"><label>优先级 / 关联记录</label>
<view class="mtask-form-row">
<view class="seg-btn {{taskForm.priority === '重要' ? 'selected' : ''}}" catchtap="onTaskPriority">重要</view>
<picker mode="selector" range="{{taskSheetLabels}}" value="{{taskForm.sheetIdx}}" bindchange="onTaskSheet">
<view class="picker-box">关联:{{taskSheetLabels[taskForm.sheetIdx]}}</view>
</picker>
</view>
</view>
<button class="btn btn-primary btn-block" bindtap="onSaveTask">{{taskForm.id ? '保存修改' : '添加任务'}}</button>
</block>
<!-- 评论 --> <!-- 评论 -->
<block wx:elif="{{innerType === 'comments'}}"> <block wx:elif="{{innerType === 'comments'}}">
<view class="sheet-h3">评论</view> <view class="sheet-h3">评论</view>
@@ -35,6 +35,18 @@
.picker-box{color:var(--text)} .picker-box{color:var(--text)}
.del-btn{background:var(--red-soft);color:var(--red)} .del-btn{background:var(--red-soft);color:var(--red)}
/* 管理任务 */
.mtask-row{display:flex;align-items:center;gap:16rpx;padding:20rpx 0;border-bottom:1rpx solid var(--line)}
.mtask-body{flex:1;min-width:0}
.mtask-b{font-size:28rpx;font-weight:700}
.mtask-pri{margin-left:12rpx;font-size:20rpx;font-weight:700;color:var(--primary-dark);background:#FFF2DC;padding:2rpx 12rpx;border-radius:999rpx}
.mtask-p{margin-top:6rpx;font-size:24rpx;color:var(--muted)}
.mtask-op{flex:none;font-size:26rpx;font-weight:700;color:var(--primary-dark);padding:8rpx 18rpx;background:#FBFAF8;border-radius:16rpx}
.mtask-op.del{color:var(--red)}
.mtask-form-row{display:flex;align-items:center;gap:16rpx}
.mtask-form-row .seg-btn{flex:none;padding:0 28rpx;height:72rpx;line-height:72rpx}
.mtask-form-row .picker-box{flex:1}
/* 图片选择器 */ /* 图片选择器 */
.img-picker{display:flex;flex-wrap:wrap;gap:16rpx} .img-picker{display:flex;flex-wrap:wrap;gap:16rpx}
.img-thumb{position:relative;width:160rpx;height:160rpx;border-radius:20rpx;overflow:hidden} .img-thumb{position:relative;width:160rpx;height:160rpx;border-radius:20rpx;overflow:hidden}
+2 -4
View File
@@ -15,10 +15,8 @@ module.exports.isUrl = function (s) { return s && s.indexOf('http') === 0; }
class="feed-tab {{feedIdx === index ? 'active' : ''}}" data-index="{{index}}" bindtap="switchFeedTab">{{item}}</view> class="feed-tab {{feedIdx === index ? 'active' : ''}}" data-index="{{index}}" bindtap="switchFeedTab">{{item}}</view>
</scroll-view> </scroll-view>
<view class="compose-card" data-type="createPost" bindtap="openSheet"> <view class="compose-card">
<view class="post-avatar">{{pet.emoji}}</view> <button class="btn btn-primary btn-block" bindtap="openSheet" data-type="createPost"> 发布图文</button>
<view class="compose-input">分享一下今天和 {{pet.name}} 的日常...</view>
<button class="btn btn-primary btn-small" catchtap="openSheet" data-type="createPost">发布</button>
</view> </view>
<view wx:for="{{posts}}" wx:key="id" class="post-card"> <view wx:for="{{posts}}" wx:key="id" class="post-card">
+1 -9
View File
@@ -10,15 +10,7 @@
} }
.feed-tab.active{color:var(--primary-dark);background:#FFF4E4;border-color:#FFD39A} .feed-tab.active{color:var(--primary-dark);background:#FFF4E4;border-color:#FFD39A}
.compose-card{ .compose-card{margin-bottom:28rpx}
display:flex;gap:20rpx;align-items:center;background:#fff;border-radius:44rpx;
box-shadow:var(--shadow);padding:24rpx;margin-bottom:28rpx;
}
.compose-input{
flex:1;height:76rpx;line-height:76rpx;border-radius:999rpx;background:#FBFAF8;
border:1rpx solid var(--line);color:var(--muted);padding:0 26rpx;font-size:26rpx;
overflow:hidden;white-space:nowrap;text-overflow:ellipsis;
}
.post-card{background:#fff;border-radius:48rpx;box-shadow:var(--shadow);padding:30rpx;margin-bottom:28rpx} .post-card{background:#fff;border-radius:48rpx;box-shadow:var(--shadow);padding:30rpx;margin-bottom:28rpx}
.post-head{display:flex;align-items:center;gap:20rpx;margin-bottom:22rpx} .post-head{display:flex;align-items:center;gap:20rpx;margin-bottom:22rpx}
+4 -1
View File
@@ -133,7 +133,10 @@ Page({
this.setData({ sheetShow: false }); this.setData({ sheetShow: false });
this.loadAll(); this.loadAll();
}, },
onSheetSave() {}, onSheetSave() {
this.loadTasks();
this.loadSummary();
},
onAddPet() { onAddPet() {
this.openSheetType('addPet'); this.openSheetType('addPet');
}, },
+5 -2
View File
@@ -42,8 +42,11 @@
<view class="card"> <view class="card">
<view class="section-head"> <view class="section-head">
<view class="sh-title">{{taskLabel}}</view> <view class="sh-title">{{taskLabel}}</view>
<view wx:if="{{selectedDate && selectedDate !== todayDate}}" class="link" bindtap="backToToday">回今天</view> <view class="head-links">
<view wx:else class="link" bindtap="completeTasks">全部完成</view> <view class="link" data-type="manageTasks" bindtap="openSheet">管理</view>
<view wx:if="{{selectedDate && selectedDate !== todayDate}}" class="link" bindtap="backToToday">回今天</view>
<view wx:else class="link" bindtap="completeTasks">全部完成</view>
</view>
</view> </view>
<view class="task-list"> <view class="task-list">
<view wx:for="{{tasks}}" wx:key="title" class="task {{item.done ? 'done' : ''}}" data-index="{{index}}" bindtap="onTapTask"> <view wx:for="{{tasks}}" wx:key="title" class="task {{item.done ? 'done' : ''}}" data-index="{{index}}" bindtap="onTapTask">
+5
View File
@@ -19,6 +19,7 @@ function mapRecord(r) {
icon: r.icon || '✍️', icon: r.icon || '✍️',
title: r.title, title: r.title,
desc: fmtTime(r.occurred_at) + (r.description ? '' + r.description : ''), desc: fmtTime(r.occurred_at) + (r.description ? '' + r.description : ''),
image: r.image_url || '',
}; };
} }
@@ -151,6 +152,10 @@ Page({
const i = e.currentTarget.dataset.index; const i = e.currentTarget.dataset.index;
this.setData({ selDot: this.data.trendDots[i], selIdx: i }); this.setData({ selDot: this.data.trendDots[i], selIdx: i });
}, },
previewImage(e) {
const src = e.currentTarget.dataset.src;
if (src) wx.previewImage({ urls: [src], current: src });
},
openSheet(e) { openSheet(e) {
this.openSheetType(e.currentTarget.dataset.type); this.openSheetType(e.currentTarget.dataset.type);
}, },
+4 -1
View File
@@ -53,7 +53,10 @@
<view class="health-timeline"> <view class="health-timeline">
<view wx:for="{{timeline}}" wx:key="index" class="health-event"> <view wx:for="{{timeline}}" wx:key="index" class="health-event">
<view class="event-dot">{{item.icon}}</view> <view class="event-dot">{{item.icon}}</view>
<view><text class="he-b">{{item.title}}</text><view class="he-p">{{item.desc}}</view></view> <view class="he-body">
<text class="he-b">{{item.title}}</text><view class="he-p">{{item.desc}}</view>
<image wx:if="{{item.image}}" class="he-img" src="{{item.image}}" mode="aspectFill" bindtap="previewImage" data-src="{{item.image}}"></image>
</view>
</view> </view>
<view wx:if="{{timeline.length === 0}}" class="tl-empty">还没有记录,用上面的按钮记一条吧</view> <view wx:if="{{timeline.length === 0}}" class="tl-empty">还没有记录,用上面的按钮记一条吧</view>
</view> </view>
+2
View File
@@ -44,6 +44,8 @@
} }
.he-b{font-size:28rpx;font-weight:700} .he-b{font-size:28rpx;font-weight:700}
.he-p{color:var(--muted);font-size:24rpx;line-height:1.45;margin-top:6rpx} .he-p{color:var(--muted);font-size:24rpx;line-height:1.45;margin-top:6rpx}
.he-body{flex:1;min-width:0}
.he-img{margin-top:14rpx;width:220rpx;height:220rpx;border-radius:20rpx}
/* 页面不自身滚动,改由 page-scroll 承载滚动(隐藏滚动条)*/ /* 页面不自身滚动,改由 page-scroll 承载滚动(隐藏滚动条)*/
page{height:100vh;overflow:hidden;display:flex;flex-direction:column} page{height:100vh;overflow:hidden;display:flex;flex-direction:column}
+3
View File
@@ -58,6 +58,9 @@ const api = {
// 任务 // 任务
getTasks: (id, date) => request({ url: `/api/pets/${id}/tasks${date ? `?date=${date}` : ''}` }), getTasks: (id, date) => request({ url: `/api/pets/${id}/tasks${date ? `?date=${date}` : ''}` }),
createTask: (id, body) => request({ url: `/api/pets/${id}/tasks`, method: 'POST', data: body }),
updateTask: (taskId, body) => request({ url: `/api/tasks/${taskId}`, method: 'PUT', data: body }),
deleteTask: (taskId) => request({ url: `/api/tasks/${taskId}`, method: 'DELETE' }),
toggleTask: (taskId) => request({ url: `/api/tasks/${taskId}/toggle`, method: 'POST' }), toggleTask: (taskId) => request({ url: `/api/tasks/${taskId}/toggle`, method: 'POST' }),
completeAllTasks: (id) => request({ url: `/api/pets/${id}/tasks/complete-all`, method: 'POST' }), completeAllTasks: (id) => request({ url: `/api/pets/${id}/tasks/complete-all`, method: 'POST' }),
+1 -1
View File
@@ -1,5 +1,5 @@
// 后端地址。本地联调:微信开发者工具需在「详情 → 本地设置」勾选「不校验合法域名」。 // 后端地址。本地联调:微信开发者工具需在「详情 → 本地设置」勾选「不校验合法域名」。
// 上线时改为你的 https 域名,并在微信公众平台配置 request 合法域名。 // 上线时改为你的 https 域名,并在微信公众平台配置 request 合法域名。
const BASE_URL = 'http://192.168.31.4:8080'; const BASE_URL = 'http://192.168.31.3:8080';
module.exports = { BASE_URL }; module.exports = { BASE_URL };