fix mobile task rules and feature availability
This commit is contained in:
@@ -803,7 +803,10 @@ func (s *Service) ensureWithdrawalEnabled(ctx context.Context, tenantID int64) e
|
||||
return err
|
||||
}
|
||||
var config map[string]any
|
||||
if json.Unmarshal(raw, &config) != nil || !findBool(config, "withdrawal_enabled", "withdrawalEnabled", "WITHDRAWAL") {
|
||||
if json.Unmarshal(raw, &config) != nil {
|
||||
config = map[string]any{}
|
||||
}
|
||||
if enabled, found := findBoolValue(config, "withdrawal_enabled", "withdrawalEnabled", "WITHDRAWAL"); found && !enabled {
|
||||
return store.ErrWithdrawalDisabled
|
||||
}
|
||||
return nil
|
||||
@@ -1142,25 +1145,28 @@ func allDigits(value string) bool {
|
||||
return value != ""
|
||||
}
|
||||
|
||||
func findBool(config map[string]any, keys ...string) bool {
|
||||
func findBoolValue(config map[string]any, keys ...string) (bool, bool) {
|
||||
for _, key := range keys {
|
||||
for candidate, value := range config {
|
||||
if strings.EqualFold(candidate, key) {
|
||||
switch typed := value.(type) {
|
||||
case bool:
|
||||
return typed
|
||||
return typed, true
|
||||
case float64:
|
||||
return typed == 1
|
||||
return typed == 1, true
|
||||
case string:
|
||||
return typed == "1" || strings.EqualFold(typed, "true") || strings.EqualFold(typed, "on")
|
||||
return typed == "1" || strings.EqualFold(typed, "true") || strings.EqualFold(typed, "on"), true
|
||||
}
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, value := range config {
|
||||
if nested, ok := value.(map[string]any); ok && findBool(nested, keys...) {
|
||||
return true
|
||||
if nested, ok := value.(map[string]any); ok {
|
||||
if result, found := findBoolValue(nested, keys...); found {
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false, false
|
||||
}
|
||||
|
||||
@@ -118,11 +118,18 @@ func TestQuoteCalculatesRateFeeAndLimits(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteFailsClosed(t *testing.T) {
|
||||
func TestQuoteDefaultsToEnabledWithoutWithdrawalSwitch(t *testing.T) {
|
||||
service := &Service{repository: &fakeRepository{config: json.RawMessage(`{}`)}, ready: true, now: time.Now}
|
||||
if err := service.ensureWithdrawalEnabled(context.Background(), 0); err != nil {
|
||||
t.Fatalf("missing switch should default to enabled, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteHonorsExplicitDisabledWithdrawalSwitch(t *testing.T) {
|
||||
service := &Service{repository: &fakeRepository{config: json.RawMessage(`{"features":{"withdrawal_enabled":false}}`)}, ready: true, now: time.Now}
|
||||
_, err := service.Quote(context.Background(), 0, 1, QuoteInput{PaymentAccountID: 1, Amount: 100, Currency: "CNY"})
|
||||
if !errors.Is(err, store.ErrWithdrawalDisabled) {
|
||||
t.Fatalf("expected disabled error, got %v", err)
|
||||
t.Fatalf("expected explicitly disabled withdrawal, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-15
@@ -328,30 +328,40 @@ func maskedUsername(value string) string {
|
||||
func parseFeatures(raw json.RawMessage) Features {
|
||||
var config map[string]any
|
||||
if json.Unmarshal(raw, &config) != nil {
|
||||
return Features{}
|
||||
config = map[string]any{}
|
||||
}
|
||||
return Features{
|
||||
TaskEnabled: findBool(config, "task_enabled", "taskEnabled", "TASK"),
|
||||
RechargeEnabled: findBool(config, "recharge_enabled", "rechargeEnabled", "RECHARGE"),
|
||||
WithdrawalEnabled: findBool(config, "withdrawal_enabled", "withdrawalEnabled", "WITHDRAWAL"),
|
||||
ManualMallEnabled: findBool(config, "manual_mall_enabled", "manualMallEnabled", "SHOP_STATUS"),
|
||||
TaskEnabled: findBoolDefault(config, true, "task_enabled", "taskEnabled", "TASK"),
|
||||
RechargeEnabled: findBoolDefault(config, true, "recharge_enabled", "rechargeEnabled", "RECHARGE"),
|
||||
WithdrawalEnabled: findBoolDefault(config, true, "withdrawal_enabled", "withdrawalEnabled", "WITHDRAWAL"),
|
||||
ManualMallEnabled: findBoolDefault(config, true, "manual_mall_enabled", "manualMallEnabled", "SHOP_STATUS"),
|
||||
}
|
||||
}
|
||||
|
||||
func findBool(config map[string]any, keys ...string) bool {
|
||||
for _, key := range keys {
|
||||
if value, ok := config[key]; ok {
|
||||
return boolValue(value)
|
||||
}
|
||||
func findBoolDefault(config map[string]any, defaultValue bool, keys ...string) bool {
|
||||
value, found := findBoolValue(config, keys...)
|
||||
if !found {
|
||||
return defaultValue
|
||||
}
|
||||
if nested, ok := config["features"].(map[string]any); ok {
|
||||
for _, key := range keys {
|
||||
if value, found := nested[key]; found {
|
||||
return boolValue(value)
|
||||
return value
|
||||
}
|
||||
|
||||
func findBoolValue(config map[string]any, keys ...string) (bool, bool) {
|
||||
for _, key := range keys {
|
||||
for candidate, value := range config {
|
||||
if strings.EqualFold(candidate, key) {
|
||||
return boolValue(value), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
for _, value := range config {
|
||||
if nested, ok := value.(map[string]any); ok {
|
||||
if result, found := findBoolValue(nested, keys...); found {
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
func boolValue(value any) bool {
|
||||
|
||||
@@ -77,6 +77,18 @@ func TestParseFeaturesSupportsNestedAndLegacyKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFeaturesDefaultsMissingSwitchesToEnabled(t *testing.T) {
|
||||
features := parseFeatures(json.RawMessage(`{}`))
|
||||
if !features.TaskEnabled || !features.RechargeEnabled || !features.WithdrawalEnabled || !features.ManualMallEnabled {
|
||||
t.Fatalf("missing switches should default to enabled: %+v", features)
|
||||
}
|
||||
|
||||
features = parseFeatures(json.RawMessage(`{"features":{"task_enabled":false,"withdrawal_enabled":false}}`))
|
||||
if features.TaskEnabled || features.WithdrawalEnabled || !features.RechargeEnabled || !features.ManualMallEnabled {
|
||||
t.Fatalf("explicitly disabled switches must remain disabled: %+v", features)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBannersSanitizeTargets(t *testing.T) {
|
||||
service := New(fakeRepository{banners: []store.Banner{
|
||||
{ID: 1, TargetType: 2, TargetValue: "/receive"},
|
||||
|
||||
@@ -205,7 +205,7 @@ func (s *Store) ClaimTask(ctx context.Context, input ClaimTaskInput) (*TaskOrder
|
||||
if wallet.Status != 1 {
|
||||
return nil, ErrAccountRestricted
|
||||
}
|
||||
if member.AutoDispatch == 1 && (wallet.AvailableAmount <= 0 || wallet.AvailableAmount < input.MinimumAmount) {
|
||||
if wallet.AvailableAmount < input.MinimumAmount {
|
||||
return nil, ErrInsufficientBalance
|
||||
}
|
||||
var pending int64
|
||||
|
||||
+11
-49
@@ -18,10 +18,11 @@ const (
|
||||
defaultPageSize = 20
|
||||
maxPageSize = 100
|
||||
defaultDeadlineMinute = 15
|
||||
minimumClaimAmount = 29900
|
||||
businessDisplayStart = "09:00:00+08:00"
|
||||
businessDisplayEnd = "21:00:00+08:00"
|
||||
)
|
||||
|
||||
var shanghai = time.FixedZone("Asia/Shanghai", 8*60*60)
|
||||
|
||||
type Repository interface {
|
||||
CurrentSiteConfig(context.Context, int64) (json.RawMessage, error)
|
||||
TaskHalls(context.Context, int64, int64) ([]store.TaskHall, error)
|
||||
@@ -158,8 +159,6 @@ type taskPolicy struct {
|
||||
AutoDelay int
|
||||
AutoMinAmount int64
|
||||
AutoMaxAmount int64
|
||||
Start string
|
||||
End string
|
||||
Deadline time.Duration
|
||||
}
|
||||
|
||||
@@ -194,20 +193,18 @@ func (s *Service) Eligibility(ctx context.Context, tenantID, memberID, hallID in
|
||||
}
|
||||
result := &Eligibility{Eligible: true, AvailableAmount: row.AvailableAmount, MinimumAmount: policy.MinimumAmount,
|
||||
TodayCount: row.TodayCount, DailyLimit: row.DailyLimit, PendingOrderID: row.PendingOrderID,
|
||||
BusinessStartAt: policy.Start + "+08:00", BusinessEndAt: policy.End + "+08:00"}
|
||||
BusinessStartAt: businessDisplayStart, BusinessEndAt: businessDisplayEnd}
|
||||
switch {
|
||||
case !policy.Enabled:
|
||||
result.reject("TASK_DISABLED", "任务功能未开启")
|
||||
case !withinBusinessHours(s.now().In(shanghai), policy.Start, policy.End):
|
||||
result.reject("OUTSIDE_BUSINESS_HOURS", "当前不在营业时间")
|
||||
case row.MemberStatus != 1:
|
||||
result.reject("ACCOUNT_RESTRICTED", "账户状态受限")
|
||||
case !row.HallEligible:
|
||||
result.reject("LEVEL_NOT_ELIGIBLE", "当前等级不能进入该任务大厅")
|
||||
case row.WalletStatus != 1:
|
||||
result.reject("ACCOUNT_RESTRICTED", "账户状态受限")
|
||||
case row.AutoDispatch == 1 && (row.AvailableAmount <= 0 || row.AvailableAmount < policy.MinimumAmount):
|
||||
result.reject("INSUFFICIENT_BALANCE", "可用余额不足")
|
||||
case row.AvailableAmount < policy.MinimumAmount:
|
||||
result.reject("INSUFFICIENT_BALANCE", "可用余额不足299元")
|
||||
case row.DailyLimit > 0 && row.TodayCount >= row.DailyLimit:
|
||||
result.reject("DAILY_LIMIT_REACHED", "已达当日领取上限")
|
||||
case row.PendingOrderID > 0:
|
||||
@@ -233,9 +230,6 @@ func (s *Service) Claim(ctx context.Context, tenantID, memberID, hallID int64) (
|
||||
if !policy.Enabled {
|
||||
return nil, ErrTaskDisabled
|
||||
}
|
||||
if !withinBusinessHours(s.now().In(shanghai), policy.Start, policy.End) {
|
||||
return nil, ErrOutsideBusinessHour
|
||||
}
|
||||
now := s.now().UTC()
|
||||
row, err := s.repository.ClaimTask(ctx, store.ClaimTaskInput{TenantID: tenantID, MemberID: memberID,
|
||||
HallID: hallID, MinimumAmount: policy.MinimumAmount, AutoDelay: policy.AutoDelay,
|
||||
@@ -379,14 +373,14 @@ func (s *Service) policy(ctx context.Context, tenantID int64) (taskPolicy, error
|
||||
if minutes <= 0 || minutes > 24*60 {
|
||||
minutes = defaultDeadlineMinute
|
||||
}
|
||||
start := clockValue(findValue(config, "task_business_start_at", "business_start_at", "taskBusinessStartAt"), "00:00:00")
|
||||
end := clockValue(findValue(config, "task_business_end_at", "business_end_at", "taskBusinessEndAt"), "23:59:59")
|
||||
return taskPolicy{Enabled: boolValue(findValue(config, "task_enabled", "taskEnabled", "TASK")),
|
||||
MinimumAmount: max64(0, int64Value(findValue(config, "task_minimum_amount", "minimum_balance_amount", "taskMinimumAmount"))),
|
||||
enabledValue := findValue(config, "task_enabled", "taskEnabled", "TASK")
|
||||
enabled := enabledValue == nil || boolValue(enabledValue)
|
||||
return taskPolicy{Enabled: enabled,
|
||||
MinimumAmount: minimumClaimAmount,
|
||||
AutoDelay: max(0, intValue(findValue(config, "auto_order_delay_seconds", "autoOrderDelaySeconds"))),
|
||||
AutoMinAmount: max64(0, int64Value(findValue(config, "auto_order_min_amount", "autoOrderMinAmount"))),
|
||||
AutoMaxAmount: max64(0, int64Value(findValue(config, "auto_order_max_amount", "autoOrderMaxAmount"))),
|
||||
Start: start, End: end, Deadline: time.Duration(minutes) * time.Minute}, nil
|
||||
Deadline: time.Duration(minutes) * time.Minute}, nil
|
||||
}
|
||||
|
||||
func orderFromStore(row store.TaskOrder, detail bool) Order {
|
||||
@@ -506,38 +500,6 @@ func submissionStatusText(status int) string {
|
||||
return names[status]
|
||||
}
|
||||
|
||||
func withinBusinessHours(now time.Time, start, end string) bool {
|
||||
current := now.Hour()*3600 + now.Minute()*60 + now.Second()
|
||||
startSecond := parseClock(start)
|
||||
endSecond := parseClock(end)
|
||||
if startSecond <= endSecond {
|
||||
return current >= startSecond && current <= endSecond
|
||||
}
|
||||
return current >= startSecond || current <= endSecond
|
||||
}
|
||||
|
||||
func parseClock(value string) int {
|
||||
parsed, err := time.Parse("15:04:05", value)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed.Hour()*3600 + parsed.Minute()*60 + parsed.Second()
|
||||
}
|
||||
|
||||
func clockValue(value any, fallback string) string {
|
||||
raw, ok := value.(string)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
raw = strings.TrimSpace(raw)
|
||||
for _, layout := range []string{"15:04:05", "15:04"} {
|
||||
if parsed, err := time.Parse(layout, raw); err == nil {
|
||||
return parsed.Format("15:04:05")
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func findValue(config map[string]any, keys ...string) any {
|
||||
for _, key := range keys {
|
||||
for candidate, value := range config {
|
||||
|
||||
@@ -62,58 +62,66 @@ func (f *fakeRepository) OrderLedgers(context.Context, int64, int64, []string) (
|
||||
return []map[string]any{}, nil
|
||||
}
|
||||
|
||||
func TestEligibilityAppliesPublishedPolicy(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true},"task_minimum_amount":50000,"task_business_start_at":"09:00","task_business_end_at":"22:00"}`),
|
||||
eligibility: &store.TaskEligibility{MemberStatus: 1, AutoDispatch: 1, HallEligible: true, AvailableAmount: 49999, WalletStatus: 1, DailyLimit: 5, TodayCount: 2}}
|
||||
func TestEligibilityRequiresFixedMinimumBalance(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true},"task_minimum_amount":0}`),
|
||||
eligibility: &store.TaskEligibility{MemberStatus: 1, AutoDispatch: 1, HallEligible: true, AvailableAmount: 29899, WalletStatus: 1, DailyLimit: 5, TodayCount: 2}}
|
||||
service := New(repository)
|
||||
service.now = func() time.Time { return time.Date(2026, 10, 9, 10, 0, 0, 0, shanghai) }
|
||||
|
||||
result, err := service.Eligibility(context.Background(), 0, 10, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Eligible || result.Reason != "INSUFFICIENT_BALANCE" || result.MinimumAmount != 50000 {
|
||||
if result.Eligible || result.Reason != "INSUFFICIENT_BALANCE" || result.MinimumAmount != 29900 {
|
||||
t.Fatalf("unexpected eligibility: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEligibilityAllowsManualDispatchBelowMinimumBalance(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true},"task_minimum_amount":50000,"task_business_start_at":"09:00","task_business_end_at":"22:00"}`),
|
||||
eligibility: &store.TaskEligibility{MemberStatus: 1, AutoDispatch: 2, HallEligible: true, AvailableAmount: 0, WalletStatus: 1, DailyLimit: 5}}
|
||||
func TestEligibilityAlsoRequiresMinimumForManualDispatch(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true}}`),
|
||||
eligibility: &store.TaskEligibility{MemberStatus: 1, AutoDispatch: 2, HallEligible: true, AvailableAmount: 29899, WalletStatus: 1, DailyLimit: 5}}
|
||||
service := New(repository)
|
||||
service.now = func() time.Time { return time.Date(2026, 10, 9, 10, 0, 0, 0, shanghai) }
|
||||
|
||||
result, err := service.Eligibility(context.Background(), 0, 10, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !result.Eligible {
|
||||
t.Fatalf("manual dispatch should ignore the automatic minimum balance gate: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEligibilityRejectsAutoDispatchWithZeroBalanceWhenMinimumIsZero(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true},"task_minimum_amount":0,"task_business_start_at":"09:00","task_business_end_at":"22:00"}`),
|
||||
eligibility: &store.TaskEligibility{MemberStatus: 1, AutoDispatch: 1, HallEligible: true, AvailableAmount: 0, WalletStatus: 1, DailyLimit: 5}}
|
||||
service := New(repository)
|
||||
service.now = func() time.Time { return time.Date(2026, 10, 9, 10, 0, 0, 0, shanghai) }
|
||||
|
||||
result, err := service.Eligibility(context.Background(), 0, 10, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Eligible || result.Reason != "INSUFFICIENT_BALANCE" {
|
||||
t.Fatalf("zero balance must not allow automatic claiming: %#v", result)
|
||||
t.Fatalf("manual dispatch must use the same minimum balance gate: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaimFailsClosedWithoutTaskSwitch(t *testing.T) {
|
||||
func TestEligibilityDoesNotEnforceDisplayedBusinessHours(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true},"task_business_start_at":"09:00","task_business_end_at":"21:00"}`),
|
||||
eligibility: &store.TaskEligibility{MemberStatus: 1, AutoDispatch: 1, HallEligible: true, AvailableAmount: 29900, WalletStatus: 1, DailyLimit: 5}}
|
||||
service := New(repository)
|
||||
service.now = func() time.Time { return time.Date(2026, 10, 9, 23, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60)) }
|
||||
|
||||
result, err := service.Eligibility(context.Background(), 0, 10, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !result.Eligible || result.BusinessStartAt != "09:00:00+08:00" || result.BusinessEndAt != "21:00:00+08:00" {
|
||||
t.Fatalf("business hours are display-only: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaimDefaultsToEnabledWithoutTaskSwitch(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{}`)}
|
||||
service := New(repository)
|
||||
service.now = func() time.Time { return time.Date(2026, 10, 9, 2, 0, 0, 0, time.UTC) }
|
||||
|
||||
if _, err := service.Claim(context.Background(), 0, 10, 1); err != nil {
|
||||
t.Fatalf("missing switch should default to enabled, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaimHonorsExplicitDisabledTaskSwitch(t *testing.T) {
|
||||
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":false}}`)}
|
||||
service := New(repository)
|
||||
|
||||
_, err := service.Claim(context.Background(), 0, 10, 1)
|
||||
if !errors.Is(err, ErrTaskDisabled) {
|
||||
t.Fatalf("expected task disabled, got %v", err)
|
||||
t.Fatalf("expected explicitly disabled task, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +136,7 @@ func TestClaimBuildsMatchingOrder(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Status != "matching" || repository.claimed.HallID != 2 ||
|
||||
!repository.claimed.DeadlineAt.Equal(now.Add(20*time.Minute)) {
|
||||
repository.claimed.MinimumAmount != 29900 || !repository.claimed.DeadlineAt.Equal(now.Add(20*time.Minute)) {
|
||||
t.Fatalf("unexpected claim: result=%#v input=%#v", result, repository.claimed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const reasonMessages: Record<string, string> = {
|
||||
RATE_LIMITED: '操作过于频繁,请稍后再试',
|
||||
TASK_DISABLED: '领取功能暂未开放',
|
||||
OUTSIDE_BUSINESS_HOURS: '当前不在营业时间内',
|
||||
INSUFFICIENT_BALANCE: '可用余额不足',
|
||||
INSUFFICIENT_BALANCE: '可用余额不足299元,暂时无法领取',
|
||||
DAILY_LIMIT_REACHED: '今日领取次数已用完',
|
||||
PENDING_ORDER_EXISTS: '存在未完成的任务,请先处理',
|
||||
NO_TASK_AVAILABLE: '当前暂无可领取任务',
|
||||
|
||||
@@ -3760,10 +3760,18 @@ body {
|
||||
.m-rank-winner > strong,
|
||||
.m-rank-winner > em {
|
||||
max-width: 100%;
|
||||
font-style: normal;
|
||||
}
|
||||
.m-rank-winner > strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-style: normal;
|
||||
}
|
||||
.m-rank-winner > em {
|
||||
overflow: visible;
|
||||
font-size: clamp(11px, 3vw, 14px);
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.m-rank-winner > small {
|
||||
margin-top: 4px;
|
||||
@@ -3781,7 +3789,7 @@ body {
|
||||
.m-rank-table header,
|
||||
.m-rank-row {
|
||||
display: grid;
|
||||
grid-template-columns: 48px minmax(0, 1.5fr) minmax(82px, 1fr) 70px;
|
||||
grid-template-columns: 48px minmax(0, 1fr) max-content 70px;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
@@ -3823,6 +3831,7 @@ body {
|
||||
font-style: normal;
|
||||
}
|
||||
.m-rank-row strong {
|
||||
white-space: nowrap;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -3849,7 +3858,7 @@ body {
|
||||
.m-home-rank-list a {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 30px 34px minmax(0, 1fr) 72px 82px;
|
||||
grid-template-columns: 30px 34px minmax(0, 1fr) max-content 82px;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: var(--m-ink);
|
||||
@@ -3895,14 +3904,16 @@ body {
|
||||
.m-home-rank-list strong,
|
||||
.m-home-rank-list em {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.m-home-rank-list strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
}
|
||||
.m-home-rank-list em {
|
||||
overflow: visible;
|
||||
font-size: clamp(12px, 3.2vw, 15px);
|
||||
font-style: normal;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
@@ -4039,14 +4050,14 @@ body {
|
||||
}
|
||||
.m-rank-table header,
|
||||
.m-rank-row {
|
||||
grid-template-columns: 38px minmax(0, 1.4fr) 72px 58px;
|
||||
grid-template-columns: 38px minmax(0, 1fr) max-content 58px;
|
||||
gap: 5px;
|
||||
}
|
||||
.m-rank-table {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
.m-home-rank-list a {
|
||||
grid-template-columns: 26px 32px minmax(0, 1fr) 54px 72px;
|
||||
grid-template-columns: 26px 32px minmax(0, 1fr) max-content 68px;
|
||||
gap: 5px;
|
||||
}
|
||||
.m-home-rank-list small {
|
||||
|
||||
@@ -22,21 +22,22 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-if="halls.length && !eligibleHalls.length && eligibility?.eligible === false" class="m-claim2-warning">
|
||||
<div v-if="balanceInsufficient" class="m-claim2-warning">
|
||||
<n-icon :component="AlertCircleOutline" />
|
||||
<span>{{ eligibility.message || '暂不满足领取条件' }}</span>
|
||||
<button v-if="eligibility.pending_order_id" type="button" @click="openPendingOrder">查看任务</button>
|
||||
<span>可用余额不足299元</span>
|
||||
</div>
|
||||
|
||||
<section class="m-claim2-rules">
|
||||
<h2><span></span>活动规则<span></span></h2>
|
||||
<ol>
|
||||
<li>平台运营时间每天 {{ businessHours }}。</li>
|
||||
<li>每一次储入请先咨询客服获取商家卡号,如有疑问请联系在线客服。</li>
|
||||
<li>领取后系统将自动分配任务,请按订单要求完成任务内容。</li>
|
||||
<li>领取时间根据会员等级不同,系统随机匹配订单。</li>
|
||||
<li>账户可用余额需达到 {{ money(eligibility?.minimum_amount) }} 才可领取。</li>
|
||||
<li>订单需要在规定时间内完成,请勿超时提交。</li>
|
||||
<li>平台运营时间每天上午9:00-晚上21:00点!</li>
|
||||
<li>每一次储入必须咨询客户获取商家卡号!如有不理解可联系在线客服!</li>
|
||||
<li>领取后系统将自动分配任务,请按订单要求完成任务内容!</li>
|
||||
<li>领取订单的时间是根据会员等级的不同!系统随机匹配订单!</li>
|
||||
<li>会员账户必须要拥有299启动资金才可进行领取!</li>
|
||||
<li>订单需要在规定时间内完成!请勿超时完成商品!</li>
|
||||
<li>平台硬性条件提现必须信用分值满足100分,请广大会员珍惜账户信用分值!信用分下降必须联系客服处理恢复100分才可正常提现,信用分提升金额具体根据会员等级规定,详细咨询平台客服。</li>
|
||||
<li>非本人充值资金需要提供支付人身份证信息验证充值资金,避免出现非必要的资金纠纷。</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
@@ -132,11 +133,12 @@
|
||||
const eligibleHalls = computed(() =>
|
||||
halls.value.filter((hall) => eligibilityByHall.value[hall.id]?.eligible)
|
||||
);
|
||||
const businessHours = computed(() => {
|
||||
const start = String(eligibility.value?.business_start_at || '09:00').slice(0, 5);
|
||||
const end = String(eligibility.value?.business_end_at || '21:00').slice(0, 5);
|
||||
return `${start}-${end}`;
|
||||
});
|
||||
const balanceInsufficient = computed(() =>
|
||||
Object.values(eligibilityByHall.value).some((item: any) =>
|
||||
Number(item?.minimum_amount || 0) > 0 &&
|
||||
Number(item?.available_amount || 0) < Number(item.minimum_amount)
|
||||
)
|
||||
);
|
||||
const isAutoMatching = computed(() => order.value?.dispatch_mode !== 'manual');
|
||||
const matchingHint = computed(() => {
|
||||
if (order.value?.dispatch_mode === 'manual') return '订单已领取,正在等待后台匹配...';
|
||||
@@ -348,10 +350,6 @@
|
||||
closeResult();
|
||||
router.push('/app/orders');
|
||||
}
|
||||
function openPendingOrder() {
|
||||
const orderID = Number(eligibility.value?.pending_order_id || 0);
|
||||
router.push(orderID > 0 ? `/app/orders/${orderID}` : '/app/orders');
|
||||
}
|
||||
function retryClaim() {
|
||||
closeResult();
|
||||
void claim();
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
enabled: home.value?.features?.withdrawal_enabled === true,
|
||||
},
|
||||
{
|
||||
label: '商城',
|
||||
label: '手工',
|
||||
path: '/app/mall',
|
||||
icon: mobileAssets.shortcutMall,
|
||||
enabled: home.value?.features?.manual_mall_enabled !== false,
|
||||
|
||||
Reference in New Issue
Block a user