fix(plans): /v1/plans 500 修复 — plans 表无 name_zh/name_en 列
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled

ListPlans 历来 SELECT name_zh/name_en,但 plans 表从未有这两列(各迁移/seed
均无)→ 该端点一直 500(无 live 测试故未暴露)。改为不查名称列(套餐名是 UI
文案,客户端 PlansScreen 按 code 取 l10n:free/pro/team)。价格等真实字段保留。

已在节点实测:/v1/plans 200,返回 free ¥0 / pro ¥25 / team ¥99。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-19 00:32:40 +08:00
parent 6ee7114ff8
commit 93ec421c41
2 changed files with 12 additions and 5 deletions
+9 -1
View File
@@ -63,6 +63,14 @@ class PlansScreen extends ConsumerWidget {
_ => const [],
};
// 套餐名是 UI 文案(后端 plans 表无名称列),按 code 取 l10n。
String _name(String code) => switch (code) {
'free' => t.freePlan,
'pro' => t.proPlan,
'team' => t.teamPlan,
_ => code,
};
@override
Widget build(BuildContext context, WidgetRef ref) {
final c = context.pangolin;
@@ -80,7 +88,7 @@ class PlansScreen extends ConsumerWidget {
Padding(
padding: EdgeInsets.only(bottom: 14, top: p.code == 'pro' ? 12 : 0),
child: PlanCard(
name: p.localizedName(t.lang),
name: _name(p.code),
price: p.priceLabel(),
period: t.perMonth,
features: _feats(p.code),
+3 -4
View File
@@ -173,8 +173,6 @@ func (a *AccountAPI) weeklyGB(ctx context.Context, uid int64) []float64 {
type planResponse struct {
Code string `json:"code"`
NameZH string `json:"name_zh"`
NameEN string `json:"name_en"`
DailyMinutes *int64 `json:"daily_minutes"` // null = unlimited
AdGate bool `json:"ad_gate"`
PriceCents int `json:"price_cents"` // 价格(分);0 = 免费
@@ -183,9 +181,10 @@ type planResponse struct {
}
// ListPlans handles GET /v1/plans.
// 套餐名为 UI 文案(客户端按 code 取 l10n),plans 表不含名称列,故不查 name_*。
func (a *AccountAPI) ListPlans(w http.ResponseWriter, r *http.Request) {
rows, err := a.db.QueryContext(r.Context(),
`SELECT code, name_zh, name_en, daily_minutes, ad_gate, price_cents, currency, period FROM plans ORDER BY id`)
`SELECT code, daily_minutes, ad_gate, price_cents, currency, period FROM plans ORDER BY id`)
if err != nil {
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
return
@@ -196,7 +195,7 @@ func (a *AccountAPI) ListPlans(w http.ResponseWriter, r *http.Request) {
for rows.Next() {
var p planResponse
var dm sql.NullInt64
if err := rows.Scan(&p.Code, &p.NameZH, &p.NameEN, &dm, &p.AdGate, &p.PriceCents, &p.Currency, &p.Period); err != nil {
if err := rows.Scan(&p.Code, &dm, &p.AdGate, &p.PriceCents, &p.Currency, &p.Period); err != nil {
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
return
}