fix(plans): /v1/plans 500 修复 — plans 表无 name_zh/name_en 列
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:
@@ -63,6 +63,14 @@ class PlansScreen extends ConsumerWidget {
|
|||||||
_ => const [],
|
_ => const [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 套餐名是 UI 文案(后端 plans 表无名称列),按 code 取 l10n。
|
||||||
|
String _name(String code) => switch (code) {
|
||||||
|
'free' => t.freePlan,
|
||||||
|
'pro' => t.proPlan,
|
||||||
|
'team' => t.teamPlan,
|
||||||
|
_ => code,
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final c = context.pangolin;
|
final c = context.pangolin;
|
||||||
@@ -80,7 +88,7 @@ class PlansScreen extends ConsumerWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 14, top: p.code == 'pro' ? 12 : 0),
|
padding: EdgeInsets.only(bottom: 14, top: p.code == 'pro' ? 12 : 0),
|
||||||
child: PlanCard(
|
child: PlanCard(
|
||||||
name: p.localizedName(t.lang),
|
name: _name(p.code),
|
||||||
price: p.priceLabel(),
|
price: p.priceLabel(),
|
||||||
period: t.perMonth,
|
period: t.perMonth,
|
||||||
features: _feats(p.code),
|
features: _feats(p.code),
|
||||||
|
|||||||
@@ -173,8 +173,6 @@ func (a *AccountAPI) weeklyGB(ctx context.Context, uid int64) []float64 {
|
|||||||
|
|
||||||
type planResponse struct {
|
type planResponse struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
NameZH string `json:"name_zh"`
|
|
||||||
NameEN string `json:"name_en"`
|
|
||||||
DailyMinutes *int64 `json:"daily_minutes"` // null = unlimited
|
DailyMinutes *int64 `json:"daily_minutes"` // null = unlimited
|
||||||
AdGate bool `json:"ad_gate"`
|
AdGate bool `json:"ad_gate"`
|
||||||
PriceCents int `json:"price_cents"` // 价格(分);0 = 免费
|
PriceCents int `json:"price_cents"` // 价格(分);0 = 免费
|
||||||
@@ -183,9 +181,10 @@ type planResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListPlans handles GET /v1/plans.
|
// ListPlans handles GET /v1/plans.
|
||||||
|
// 套餐名为 UI 文案(客户端按 code 取 l10n),plans 表不含名称列,故不查 name_*。
|
||||||
func (a *AccountAPI) ListPlans(w http.ResponseWriter, r *http.Request) {
|
func (a *AccountAPI) ListPlans(w http.ResponseWriter, r *http.Request) {
|
||||||
rows, err := a.db.QueryContext(r.Context(),
|
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 {
|
if err != nil {
|
||||||
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
|
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
|
||||||
return
|
return
|
||||||
@@ -196,7 +195,7 @@ func (a *AccountAPI) ListPlans(w http.ResponseWriter, r *http.Request) {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var p planResponse
|
var p planResponse
|
||||||
var dm sql.NullInt64
|
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)
|
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user