feat(publish): 德穆兰选项支持后台开关并默认打开下赛季通行证
This commit is contained in:
@@ -121,7 +121,9 @@ type HomeBannerHotspot struct {
|
|||||||
|
|
||||||
type PublishOptionsDTO struct {
|
type PublishOptionsDTO struct {
|
||||||
// SchemaVersion 发布选项结构版本;缺省 0 代表未版本化的历史配置。
|
// SchemaVersion 发布选项结构版本;缺省 0 代表未版本化的历史配置。
|
||||||
|
// UnlockDemulanEnabled 发布页是否展示「是否解锁德穆兰」选项。
|
||||||
SchemaVersion int `json:"schema_version,omitempty"`
|
SchemaVersion int `json:"schema_version,omitempty"`
|
||||||
|
UnlockDemulanEnabled bool `json:"unlock_demulan_enabled"`
|
||||||
NextSeasonPassEnabled bool `json:"next_season_pass_enabled"`
|
NextSeasonPassEnabled bool `json:"next_season_pass_enabled"`
|
||||||
ServerOptions []string `json:"server_options"`
|
ServerOptions []string `json:"server_options"`
|
||||||
FaceOptions []string `json:"face_options"`
|
FaceOptions []string `json:"face_options"`
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package systemconfig
|
|||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// publishOptionsSchemaVersion 发布选项结构版本;v2 新增「是否解锁德穆兰」开关(默认关闭),
|
||||||
|
// 并把「下赛季是否购买通行证」默认打开。
|
||||||
|
publishOptionsSchemaVersion = 2
|
||||||
publishOptionsConfigKey = "listing.publish_options"
|
publishOptionsConfigKey = "listing.publish_options"
|
||||||
publishOptionsSchemaVersion = 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultPublishOptionsConfigValue() string {
|
func defaultPublishOptionsConfigValue() string {
|
||||||
@@ -18,7 +20,8 @@ func defaultPublishOptionsConfigValue() string {
|
|||||||
func DefaultPublishOptions() PublishOptionsDTO {
|
func DefaultPublishOptions() PublishOptionsDTO {
|
||||||
return PublishOptionsDTO{
|
return PublishOptionsDTO{
|
||||||
SchemaVersion: publishOptionsSchemaVersion,
|
SchemaVersion: publishOptionsSchemaVersion,
|
||||||
NextSeasonPassEnabled: false,
|
UnlockDemulanEnabled: false,
|
||||||
|
NextSeasonPassEnabled: true,
|
||||||
ServerOptions: []string{"QQ", "微信", "Steam"},
|
ServerOptions: []string{"QQ", "微信", "Steam"},
|
||||||
FaceOptions: []string{"是", "否"},
|
FaceOptions: []string{"是", "否"},
|
||||||
RankOptions: []string{"青铜", "白银", "黄金", "铂金", "钻石", "黑鹰", "三角洲巅峰"},
|
RankOptions: []string{"青铜", "白银", "黄金", "铂金", "钻石", "黑鹰", "三角洲巅峰"},
|
||||||
|
|||||||
@@ -106,6 +106,53 @@ func TestNormalizePublishOptionsValuePreservesCurrentSchema(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizePublishOptionsValueMigratesSchemaV1Switches(t *testing.T) {
|
||||||
|
legacy := `{"schema_version":1,"next_season_pass_enabled":false,"server_options":["QQ"]}`
|
||||||
|
|
||||||
|
normalized, err := normalizePublishOptionsValue(legacy)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("规范化失败: %v", err)
|
||||||
|
}
|
||||||
|
var got PublishOptionsDTO
|
||||||
|
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||||
|
t.Fatalf("解析规范化结果失败: %v", err)
|
||||||
|
}
|
||||||
|
if got.SchemaVersion != publishOptionsSchemaVersion {
|
||||||
|
t.Fatalf("schema_version = %d, want %d", got.SchemaVersion, publishOptionsSchemaVersion)
|
||||||
|
}
|
||||||
|
if got.UnlockDemulanEnabled {
|
||||||
|
t.Fatal("v1 升级后「是否解锁德穆兰」应默认关闭")
|
||||||
|
}
|
||||||
|
if !got.NextSeasonPassEnabled {
|
||||||
|
t.Fatal("v1 升级后「下赛季是否购买通行证」应默认打开")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizePublishOptionsValueKeepsSchemaV2Switches(t *testing.T) {
|
||||||
|
options := DefaultPublishOptions()
|
||||||
|
options.UnlockDemulanEnabled = true
|
||||||
|
options.NextSeasonPassEnabled = false
|
||||||
|
raw, err := json.Marshal(options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("构造配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized, err := normalizePublishOptionsValue(string(raw))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("规范化失败: %v", err)
|
||||||
|
}
|
||||||
|
var got PublishOptionsDTO
|
||||||
|
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||||
|
t.Fatalf("解析规范化结果失败: %v", err)
|
||||||
|
}
|
||||||
|
if !got.UnlockDemulanEnabled {
|
||||||
|
t.Fatal("v2 配置中后台手动开启的德穆兰开关不应被覆盖")
|
||||||
|
}
|
||||||
|
if got.NextSeasonPassEnabled {
|
||||||
|
t.Fatal("v2 配置中后台手动关闭的通行证开关不应被覆盖")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizePublishOptionsValueRepairsMojibake(t *testing.T) {
|
func TestNormalizePublishOptionsValueRepairsMojibake(t *testing.T) {
|
||||||
options := DefaultPublishOptions()
|
options := DefaultPublishOptions()
|
||||||
raw, err := json.Marshal(options)
|
raw, err := json.Marshal(options)
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ func normalizePublishOptionsValue(value string) (string, error) {
|
|||||||
if err := json.Unmarshal([]byte(value), &fields); err != nil {
|
if err := json.Unmarshal([]byte(value), &fields); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
originalVersion := options.SchemaVersion
|
||||||
if options.SchemaVersion < publishOptionsSchemaVersion {
|
if options.SchemaVersion < publishOptionsSchemaVersion {
|
||||||
if hasLegacyPublishQuantityItem(options.QuantityItems) {
|
if hasLegacyPublishQuantityItem(options.QuantityItems) {
|
||||||
options = DefaultPublishOptions()
|
options = DefaultPublishOptions()
|
||||||
@@ -342,6 +343,12 @@ func normalizePublishOptionsValue(value string) (string, error) {
|
|||||||
} else {
|
} else {
|
||||||
normalizePublishOptions(&options)
|
normalizePublishOptions(&options)
|
||||||
}
|
}
|
||||||
|
// v2 一次性迁移:新增「是否解锁德穆兰」开关并默认关闭,同时打开「下赛季是否购买通行证」。
|
||||||
|
// 已经是 v2 的配置不再覆盖,保留后台手动调整的结果。
|
||||||
|
if originalVersion < 2 {
|
||||||
|
options.UnlockDemulanEnabled = false
|
||||||
|
options.NextSeasonPassEnabled = true
|
||||||
|
}
|
||||||
options.SchemaVersion = publishOptionsSchemaVersion
|
options.SchemaVersion = publishOptionsSchemaVersion
|
||||||
raw, err := json.Marshal(options)
|
raw, err := json.Marshal(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -294,6 +294,13 @@ async function handleSave() {
|
|||||||
<div class="editor-block-title">
|
<div class="editor-block-title">
|
||||||
<strong>发布规则</strong>
|
<strong>发布规则</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<el-form-item label="是否解锁德穆兰选项">
|
||||||
|
<el-switch
|
||||||
|
v-model="publishOptionsDraft.unlock_demulan_enabled"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="关闭"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="下赛季通行证选项">
|
<el-form-item label="下赛季通行证选项">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="publishOptionsDraft.next_season_pass_enabled"
|
v-model="publishOptionsDraft.next_season_pass_enabled"
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ export interface ListingPublishAgreements {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ListingPublishOptions {
|
export interface ListingPublishOptions {
|
||||||
|
/** 发布页是否展示「是否解锁德穆兰」选项 */
|
||||||
|
unlock_demulan_enabled: boolean
|
||||||
next_season_pass_enabled: boolean
|
next_season_pass_enabled: boolean
|
||||||
server_options: string[]
|
server_options: string[]
|
||||||
face_options: string[]
|
face_options: string[]
|
||||||
@@ -119,6 +121,7 @@ export interface ListingPublishOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const emptyListingPublishOptions: ListingPublishOptions = {
|
export const emptyListingPublishOptions: ListingPublishOptions = {
|
||||||
|
unlock_demulan_enabled: false,
|
||||||
next_season_pass_enabled: false,
|
next_season_pass_enabled: false,
|
||||||
server_options: [],
|
server_options: [],
|
||||||
face_options: [],
|
face_options: [],
|
||||||
@@ -208,6 +211,7 @@ export function mergeListingPublishOptions(
|
|||||||
options?: Partial<ListingPublishOptions>
|
options?: Partial<ListingPublishOptions>
|
||||||
): ListingPublishOptions {
|
): ListingPublishOptions {
|
||||||
return {
|
return {
|
||||||
|
unlock_demulan_enabled: options?.unlock_demulan_enabled === true,
|
||||||
next_season_pass_enabled: options?.next_season_pass_enabled === true,
|
next_season_pass_enabled: options?.next_season_pass_enabled === true,
|
||||||
server_options: normalizeStringList(options?.server_options),
|
server_options: normalizeStringList(options?.server_options),
|
||||||
face_options: normalizeStringList(options?.face_options),
|
face_options: normalizeStringList(options?.face_options),
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
|||||||
const submitButtonText = computed(() => (isEditMode.value ? '提交修改' : '立即发布'))
|
const submitButtonText = computed(() => (isEditMode.value ? '提交修改' : '立即发布'))
|
||||||
const submitLoadingText = computed(() => (isEditMode.value ? '提交中...' : '发布中...'))
|
const submitLoadingText = computed(() => (isEditMode.value ? '提交中...' : '发布中...'))
|
||||||
const showBanRecordRiskHint = false
|
const showBanRecordRiskHint = false
|
||||||
|
const unlockDemulanEnabled = computed(() => publishOptions.value.unlock_demulan_enabled)
|
||||||
const nextSeasonPassEnabled = computed(() => publishOptions.value.next_season_pass_enabled)
|
const nextSeasonPassEnabled = computed(() => publishOptions.value.next_season_pass_enabled)
|
||||||
|
|
||||||
const pricing = usePricingCalculator({
|
const pricing = usePricingCalculator({
|
||||||
@@ -734,7 +735,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
|||||||
if (pricing.banRecordOptions.value.length && !form.ban_record) return '请选择封禁记录'
|
if (pricing.banRecordOptions.value.length && !form.ban_record) return '请选择封禁记录'
|
||||||
if (!form.can_change_game_name) return '请选择是否可以修改游戏名'
|
if (!form.can_change_game_name) return '请选择是否可以修改游戏名'
|
||||||
if (!form.all_hero) return '请选择是否有全英雄'
|
if (!form.all_hero) return '请选择是否有全英雄'
|
||||||
if (!form.unlock_demulan) return '请选择是否解锁德穆兰'
|
if (unlockDemulanEnabled.value && !form.unlock_demulan) {
|
||||||
|
return '请选择是否解锁德穆兰'
|
||||||
|
}
|
||||||
if (nextSeasonPassEnabled.value && !form.next_season_pass) {
|
if (nextSeasonPassEnabled.value && !form.next_season_pass) {
|
||||||
return '请选择下赛季是否购买通行证'
|
return '请选择下赛季是否购买通行证'
|
||||||
}
|
}
|
||||||
@@ -934,6 +937,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
|||||||
passwordAndDeviceConfirmed,
|
passwordAndDeviceConfirmed,
|
||||||
canPublishAfterAgreements,
|
canPublishAfterAgreements,
|
||||||
showBanRecordRiskHint,
|
showBanRecordRiskHint,
|
||||||
|
unlockDemulanEnabled,
|
||||||
nextSeasonPassEnabled,
|
nextSeasonPassEnabled,
|
||||||
isScanOnlyServer,
|
isScanOnlyServer,
|
||||||
effectiveLoginMethodOptions,
|
effectiveLoginMethodOptions,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const {
|
|||||||
passwordAndDeviceConfirmed,
|
passwordAndDeviceConfirmed,
|
||||||
canPublishAfterAgreements,
|
canPublishAfterAgreements,
|
||||||
showBanRecordRiskHint,
|
showBanRecordRiskHint,
|
||||||
|
unlockDemulanEnabled,
|
||||||
nextSeasonPassEnabled,
|
nextSeasonPassEnabled,
|
||||||
fileInput,
|
fileInput,
|
||||||
activeUploadKey,
|
activeUploadKey,
|
||||||
@@ -444,7 +445,12 @@ const screenshotGuides: Record<string, string> = {
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<van-field label="是否解锁德穆兰" required class="publish-field">
|
<van-field
|
||||||
|
v-if="unlockDemulanEnabled"
|
||||||
|
label="是否解锁德穆兰"
|
||||||
|
required
|
||||||
|
class="publish-field"
|
||||||
|
>
|
||||||
<template #input>
|
<template #input>
|
||||||
<div class="radio-group">
|
<div class="radio-group">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const {
|
|||||||
passwordAndDeviceConfirmed,
|
passwordAndDeviceConfirmed,
|
||||||
canPublishAfterAgreements,
|
canPublishAfterAgreements,
|
||||||
showBanRecordRiskHint,
|
showBanRecordRiskHint,
|
||||||
|
unlockDemulanEnabled,
|
||||||
nextSeasonPassEnabled,
|
nextSeasonPassEnabled,
|
||||||
fileInput,
|
fileInput,
|
||||||
activeUploadKey,
|
activeUploadKey,
|
||||||
@@ -360,7 +361,7 @@ function selectDailyLoss(value: string | number) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-row panel-field-row">
|
<div v-if="unlockDemulanEnabled" class="field-row panel-field-row">
|
||||||
<label>是否解锁德穆兰<span>*</span></label>
|
<label>是否解锁德穆兰<span>*</span></label>
|
||||||
<OptionChips
|
<OptionChips
|
||||||
:options="demulanUnlockOptions"
|
:options="demulanUnlockOptions"
|
||||||
|
|||||||
Reference in New Issue
Block a user