增加账号凌晨响应标记
This commit is contained in:
@@ -275,9 +275,11 @@ func externalAccountToCreateRequest(
|
|||||||
"resources": resources,
|
"resources": resources,
|
||||||
"skin_groups": externalSkinGroups(skins),
|
"skin_groups": externalSkinGroups(skins),
|
||||||
"online_time_text": onlineTimeText,
|
"online_time_text": onlineTimeText,
|
||||||
"ban_record": normalizeBanRecord(item.BanRecord),
|
// 外部导入没有该字段时按不可凌晨响应处理,避免旧数据误入夜间专区。
|
||||||
"common_regions": commonRegions(item.CommonRegion),
|
"early_morning_response": "否",
|
||||||
"remark": remark,
|
"ban_record": normalizeBanRecord(item.BanRecord),
|
||||||
|
"common_regions": commonRegions(item.CommonRegion),
|
||||||
|
"remark": remark,
|
||||||
"import_meta": map[string]any{
|
"import_meta": map[string]any{
|
||||||
"uploader_name": uploaderName,
|
"uploader_name": uploaderName,
|
||||||
"client_upload_time": uploadTime,
|
"client_upload_time": uploadTime,
|
||||||
|
|||||||
@@ -403,53 +403,13 @@ func isNightAvailableSummary(summary map[string]any) bool {
|
|||||||
if summary == nil {
|
if summary == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
onlineTime, ok := summary["online_time"].(map[string]any)
|
// 夜间专区只认号主明确确认的凌晨响应能力,旧数据缺失时默认不进入专区。
|
||||||
if !ok {
|
switch value := summary["early_morning_response"].(type) {
|
||||||
|
case string:
|
||||||
|
return strings.TrimSpace(value) == "是"
|
||||||
|
case bool:
|
||||||
|
return value
|
||||||
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
start, okStart := parseTimeMinuteValue(onlineTime["start"])
|
|
||||||
end, okEnd := parseTimeMinuteValue(onlineTime["end"])
|
|
||||||
if !okStart || !okEnd {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return timeRangeOverlapsMinutes(start, end, 0, 8*60)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTimeMinuteValue(value any) (int, bool) {
|
|
||||||
text, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
parts := strings.Split(text, ":")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
hour, err := strconv.Atoi(parts[0])
|
|
||||||
if err != nil || hour < 0 || hour > 23 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
minute, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil || minute < 0 || minute > 59 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return hour*60 + minute, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeRangeOverlapsMinutes(start int, end int, targetStart int, targetEnd int) bool {
|
|
||||||
if start == end {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, segment := range splitMinuteRange(start, end) {
|
|
||||||
if segment[0] <= targetEnd && segment[1] >= targetStart {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitMinuteRange(start int, end int) [][2]int {
|
|
||||||
if start < end {
|
|
||||||
return [][2]int{{start, end}}
|
|
||||||
}
|
|
||||||
return [][2]int{{start, 23*60 + 59}, {0, end}}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,31 +178,27 @@ func TestSortPublicListingsDefaultUsesShuffleKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsNightAvailableSummaryUsesMidnightToEight(t *testing.T) {
|
func TestIsNightAvailableSummaryUsesEarlyMorningResponse(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
start string
|
response any
|
||||||
end string
|
want bool
|
||||||
want bool
|
|
||||||
}{
|
}{
|
||||||
{name: "全天可上号", start: "00:00", end: "23:59", want: true},
|
{name: "明确响应", response: "是", want: true},
|
||||||
{name: "覆盖夜间新区间", start: "00:00", end: "08:00", want: true},
|
{name: "明确不响应", response: "否", want: false},
|
||||||
{name: "刚好八点开始", start: "08:00", end: "12:00", want: true},
|
{name: "兼容布尔值", response: true, want: true},
|
||||||
{name: "八点半开始不算夜间", start: "08:30", end: "12:00", want: false},
|
{name: "缺失字段", response: nil, want: false},
|
||||||
{name: "跨零点覆盖夜间", start: "23:00", end: "02:00", want: true},
|
|
||||||
{name: "旧夜间晚间时段不再命中", start: "22:00", end: "23:00", want: false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := isNightAvailableSummary(map[string]any{
|
summary := map[string]any{}
|
||||||
"online_time": map[string]any{
|
if tt.response != nil {
|
||||||
"start": tt.start,
|
summary["early_morning_response"] = tt.response
|
||||||
"end": tt.end,
|
}
|
||||||
},
|
got := isNightAvailableSummary(summary)
|
||||||
})
|
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Fatalf("isNightAvailableSummary(%s-%s) = %v, want %v", tt.start, tt.end, got, tt.want)
|
t.Fatalf("isNightAvailableSummary(%#v) = %v, want %v", tt.response, got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -530,6 +530,9 @@ func TestExternalAccountToCreateRequestMapsUploadFields(t *testing.T) {
|
|||||||
if req.AssetSummary["online_time_text"] != "08:00 至 01:00" {
|
if req.AssetSummary["online_time_text"] != "08:00 至 01:00" {
|
||||||
t.Fatalf("online_time_text = %#v", req.AssetSummary["online_time_text"])
|
t.Fatalf("online_time_text = %#v", req.AssetSummary["online_time_text"])
|
||||||
}
|
}
|
||||||
|
if req.AssetSummary["early_morning_response"] != "否" {
|
||||||
|
t.Fatalf("early_morning_response = %#v, want 否", req.AssetSummary["early_morning_response"])
|
||||||
|
}
|
||||||
onlineTime, ok := req.AssetSummary["online_time"].(map[string]any)
|
onlineTime, ok := req.AssetSummary["online_time"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("online_time missing: %#v", req.AssetSummary["online_time"])
|
t.Fatalf("online_time missing: %#v", req.AssetSummary["online_time"])
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ const zoneOptions = computed(() => [
|
|||||||
{
|
{
|
||||||
key: 'night',
|
key: 'night',
|
||||||
label: '夜间专区',
|
label: '夜间专区',
|
||||||
hint: '0-8点可上号',
|
hint: '0-2点可响应上号',
|
||||||
count: zoneCount('night'),
|
count: zoneCount('night'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const zoneOptions = [
|
|||||||
{ key: 'all', label: '全部', hint: '当前可租' },
|
{ key: 'all', label: '全部', hint: '当前可租' },
|
||||||
{ key: 'sale', label: '特惠', hint: '价格更划算' },
|
{ key: 'sale', label: '特惠', hint: '价格更划算' },
|
||||||
{ key: 'gift', label: '赠送', hint: '含赠送物品' },
|
{ key: 'gift', label: '赠送', hint: '含赠送物品' },
|
||||||
{ key: 'night', label: '夜间', hint: '0-8点可上号' },
|
{ key: 'night', label: '夜间', hint: '0-2点可响应上号' },
|
||||||
{ key: 'password', label: '账密', hint: '交接更快' },
|
{ key: 'password', label: '账密', hint: '交接更快' },
|
||||||
{ key: 'highCoin', label: '高币', hint: '100M以上' },
|
{ key: 'highCoin', label: '高币', hint: '100M以上' },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export function defaultPublishForm(): PublishForm {
|
|||||||
login_method: '',
|
login_method: '',
|
||||||
online_start: '',
|
online_start: '',
|
||||||
online_end: '',
|
online_end: '',
|
||||||
|
early_morning_response: '否',
|
||||||
can_change_game_name: '',
|
can_change_game_name: '',
|
||||||
all_hero: '',
|
all_hero: '',
|
||||||
ban_record: '',
|
ban_record: '',
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
|||||||
login_method: listing.login_platform || '',
|
login_method: listing.login_platform || '',
|
||||||
online_start: stringValue(onlineTime.start),
|
online_start: stringValue(onlineTime.start),
|
||||||
online_end: stringValue(onlineTime.end),
|
online_end: stringValue(onlineTime.end),
|
||||||
|
early_morning_response: stringValue(summary.early_morning_response) || '否',
|
||||||
can_change_game_name: stringValue(summary.can_change_game_name),
|
can_change_game_name: stringValue(summary.can_change_game_name),
|
||||||
all_hero: stringValue(summary.all_hero),
|
all_hero: stringValue(summary.all_hero),
|
||||||
ban_record: stringValue(summary.ban_record),
|
ban_record: stringValue(summary.ban_record),
|
||||||
@@ -700,6 +701,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
|||||||
if (!dailyLossOptions.includes(pricing.dailyLossMAmount.value)) return '请选择每日损耗'
|
if (!dailyLossOptions.includes(pricing.dailyLossMAmount.value)) return '请选择每日损耗'
|
||||||
const onlineTimeError = validateOnlineTime()
|
const onlineTimeError = validateOnlineTime()
|
||||||
if (onlineTimeError) return onlineTimeError
|
if (onlineTimeError) return onlineTimeError
|
||||||
|
if (form.early_morning_response !== '是' && form.early_morning_response !== '否') {
|
||||||
|
return '请选择凌晨0-2点是否可响应上号'
|
||||||
|
}
|
||||||
if (pricing.hasAcceleratedSaleRatioInput()) {
|
if (pricing.hasAcceleratedSaleRatioInput()) {
|
||||||
const ratio = Number(form.accelerated_sale_ratio)
|
const ratio = Number(form.accelerated_sale_ratio)
|
||||||
if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确'
|
if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确'
|
||||||
@@ -818,6 +822,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
|||||||
start: form.online_start,
|
start: form.online_start,
|
||||||
end: form.online_end,
|
end: form.online_end,
|
||||||
},
|
},
|
||||||
|
early_morning_response: form.early_morning_response,
|
||||||
can_change_game_name: form.can_change_game_name,
|
can_change_game_name: form.can_change_game_name,
|
||||||
all_hero: form.all_hero,
|
all_hero: form.all_hero,
|
||||||
ban_record: form.ban_record,
|
ban_record: form.ban_record,
|
||||||
|
|||||||
@@ -88,6 +88,10 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.early-morning-response-field :deep(.van-field__label) {
|
||||||
|
width: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
.hint-label,
|
.hint-label,
|
||||||
.backend-hint-target.has-hint,
|
.backend-hint-target.has-hint,
|
||||||
.upload-title.has-hint {
|
.upload-title.has-hint {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
|
|||||||
|
|
||||||
const canChangeGameNameOptions = ['是', '否']
|
const canChangeGameNameOptions = ['是', '否']
|
||||||
const allHeroOptions = ['是', '否']
|
const allHeroOptions = ['是', '否']
|
||||||
|
const earlyMorningResponseOptions = ['是', '否']
|
||||||
|
|
||||||
// 在线时间:通栏自定义块 + 底部时间选择,避免 van-field 窄列截断原生 time
|
// 在线时间:通栏自定义块 + 底部时间选择,避免 van-field 窄列截断原生 time
|
||||||
const onlineTimePickerVisible = ref(false)
|
const onlineTimePickerVisible = ref(false)
|
||||||
@@ -481,6 +482,27 @@ const screenshotGuides: Record<string, string> = {
|
|||||||
请填写能百分百联系上您的时间。该时段联系不上可能影响上架或扣款,请预留扫码与冻结人脸时间。
|
请填写能百分百联系上您的时间。该时段联系不上可能影响上架或扣款,请预留扫码与冻结人脸时间。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
label="凌晨0-2点可响应上号"
|
||||||
|
required
|
||||||
|
class="publish-field early-morning-response-field"
|
||||||
|
>
|
||||||
|
<template #input>
|
||||||
|
<div class="radio-group">
|
||||||
|
<button
|
||||||
|
v-for="opt in earlyMorningResponseOptions"
|
||||||
|
:key="opt"
|
||||||
|
type="button"
|
||||||
|
class="radio-btn"
|
||||||
|
:class="{ active: form.early_morning_response === opt }"
|
||||||
|
@click="selectRadio(opt, value => (form.early_morning_response = value))"
|
||||||
|
>
|
||||||
|
{{ opt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
<van-popup v-model:show="onlineTimePickerVisible" position="bottom" round teleport="body">
|
<van-popup v-model:show="onlineTimePickerVisible" position="bottom" round teleport="body">
|
||||||
<van-time-picker
|
<van-time-picker
|
||||||
v-model="onlineTimePickerValue"
|
v-model="onlineTimePickerValue"
|
||||||
|
|||||||
@@ -376,6 +376,10 @@
|
|||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.early-morning-response-row {
|
||||||
|
grid-template-columns: 150px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.online-time-body {
|
.online-time-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ const activeAgreementContent = computed(() => activeAgreement.value?.content ||
|
|||||||
|
|
||||||
const canChangeGameNameOptions = ['是', '否']
|
const canChangeGameNameOptions = ['是', '否']
|
||||||
const allHeroOptions = ['是', '否']
|
const allHeroOptions = ['是', '否']
|
||||||
|
const earlyMorningResponseOptions = ['是', '否']
|
||||||
|
|
||||||
// 截图槽位的指导示例图(仅展示、点击放大,不占用上传数量)
|
// 截图槽位的指导示例图(仅展示、点击放大,不占用上传数量)
|
||||||
const screenshotGuides: Record<string, string> = {
|
const screenshotGuides: Record<string, string> = {
|
||||||
@@ -396,6 +397,15 @@ function selectDailyLoss(value: string | number) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field-row panel-field-row early-morning-response-row">
|
||||||
|
<label>凌晨0-2点可响应上号<span>*</span></label>
|
||||||
|
<OptionChips
|
||||||
|
:options="earlyMorningResponseOptions"
|
||||||
|
:model-value="form.early_morning_response"
|
||||||
|
@select="form.early_morning_response = String($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="banRecordOptions.length" class="field-row panel-field-row">
|
<div v-if="banRecordOptions.length" class="field-row panel-field-row">
|
||||||
<label>封禁记录<span>*</span></label>
|
<label>封禁记录<span>*</span></label>
|
||||||
<OptionChips
|
<OptionChips
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export type PublishForm = {
|
|||||||
login_method: string
|
login_method: string
|
||||||
online_start: string
|
online_start: string
|
||||||
online_end: string
|
online_end: string
|
||||||
|
/** 是否可在凌晨 0-2 点响应上号(是/否) */
|
||||||
|
early_morning_response: string
|
||||||
/** 是否可以修改游戏名(是/否) */
|
/** 是否可以修改游戏名(是/否) */
|
||||||
can_change_game_name: string
|
can_change_game_name: string
|
||||||
/** 是否有全英雄(是/否) */
|
/** 是否有全英雄(是/否) */
|
||||||
|
|||||||
Reference in New Issue
Block a user