fix(systemconfig): 首页入口展示开关与发布选项编码修复
This commit is contained in:
@@ -35,6 +35,8 @@ type HomeConfigDTO struct {
|
||||
PublishOptions PublishOptionsDTO `json:"publish_options"`
|
||||
AWRecycle AWRecycleConfig `json:"aw_recycle"`
|
||||
CooperationFeedback CooperationFeedbackConfig `json:"cooperation_feedback"`
|
||||
// HomeEntriesEnabled 首页业务入口总开关:关闭后 PC/移动端同时隐藏回收与合作入口。
|
||||
HomeEntriesEnabled bool `json:"home_entries_enabled"`
|
||||
}
|
||||
|
||||
// AWRecycleConfig 首页「AW炫彩回收」入口:点击展示可配置图片。
|
||||
@@ -118,6 +120,8 @@ type HomeBannerHotspot struct {
|
||||
}
|
||||
|
||||
type PublishOptionsDTO struct {
|
||||
// SchemaVersion 发布选项结构版本;缺省 0 代表未版本化的历史配置。
|
||||
SchemaVersion int `json:"schema_version,omitempty"`
|
||||
NextSeasonPassEnabled bool `json:"next_season_pass_enabled"`
|
||||
ServerOptions []string `json:"server_options"`
|
||||
FaceOptions []string `json:"face_options"`
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package systemconfig
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseHomeEntriesEnabled(t *testing.T) {
|
||||
cases := []struct {
|
||||
value string
|
||||
want bool
|
||||
}{
|
||||
{value: "true", want: true},
|
||||
{value: " TRUE ", want: true},
|
||||
{value: "1", want: true},
|
||||
{value: "false", want: false},
|
||||
{value: "0", want: false},
|
||||
{value: "", want: false},
|
||||
{value: "unknown", want: false},
|
||||
}
|
||||
|
||||
for _, item := range cases {
|
||||
if got := parseHomeEntriesEnabled(item.value); got != item.want {
|
||||
t.Fatalf("parseHomeEntriesEnabled(%q) = %v, want %v", item.value, got, item.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package systemconfig
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const publishOptionsConfigKey = "listing.publish_options"
|
||||
const (
|
||||
publishOptionsConfigKey = "listing.publish_options"
|
||||
publishOptionsSchemaVersion = 1
|
||||
)
|
||||
|
||||
func defaultPublishOptionsConfigValue() string {
|
||||
raw, err := json.Marshal(DefaultPublishOptions())
|
||||
@@ -14,6 +17,7 @@ func defaultPublishOptionsConfigValue() string {
|
||||
|
||||
func DefaultPublishOptions() PublishOptionsDTO {
|
||||
return PublishOptionsDTO{
|
||||
SchemaVersion: publishOptionsSchemaVersion,
|
||||
NextSeasonPassEnabled: false,
|
||||
ServerOptions: []string{"QQ", "微信", "Steam"},
|
||||
FaceOptions: []string{"是", "否"},
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package systemconfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeCP1252Mojibake(t *testing.T) {
|
||||
original := `{"label":"6级子弹","price":"0.26元/发","punct":"(:,)"}`
|
||||
mojibake := encodeCP1252Mojibake(original)
|
||||
|
||||
decoded, ok := decodeCP1252Mojibake(mojibake)
|
||||
if !ok {
|
||||
t.Fatal("期望识别出 CP1252 乱码")
|
||||
}
|
||||
if decoded != original {
|
||||
t.Fatalf("还原结果 = %q, want %q", decoded, original)
|
||||
}
|
||||
if _, ok := decodeCP1252Mojibake(original); ok {
|
||||
t.Fatal("正常中文不应被误判为乱码")
|
||||
}
|
||||
if _, ok := decodeCP1252Mojibake("plain ascii"); ok {
|
||||
t.Fatal("纯 ASCII 不应被误判为乱码")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepairMojibakeJSONRepairsIndividualFields(t *testing.T) {
|
||||
raw := `{"normal":"正常字段","broken":"` + encodeCP1252Mojibake("6级子弹") + `"}`
|
||||
repaired := repairMojibakeJSON(raw)
|
||||
|
||||
var payload map[string]string
|
||||
if err := json.Unmarshal([]byte(repaired), &payload); err != nil {
|
||||
t.Fatalf("还原后的 JSON 无法解析: %v", err)
|
||||
}
|
||||
if payload["normal"] != "正常字段" {
|
||||
t.Fatalf("正常字段被破坏: %q", payload["normal"])
|
||||
}
|
||||
if payload["broken"] != "6级子弹" {
|
||||
t.Fatalf("乱码字段未还原: %q", payload["broken"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueMigratesLegacyQuantityItems(t *testing.T) {
|
||||
legacy := `{
|
||||
"server_options": ["QQ"],
|
||||
"quantity_items": [
|
||||
{"key": "kit5", "label": "5级全装套数量", "price": "5元/个"},
|
||||
{"key": "level6Ammo", "label": "6级子弹", "price": "0.26元/发"}
|
||||
]
|
||||
}`
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(legacy)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var options PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &options); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if options.SchemaVersion != publishOptionsSchemaVersion {
|
||||
t.Fatalf("schema_version = %d, want %d", options.SchemaVersion, publishOptionsSchemaVersion)
|
||||
}
|
||||
for _, item := range options.QuantityItems {
|
||||
if item.Key == "kit5" {
|
||||
t.Fatal("旧版 kit5 选项应被迁移掉")
|
||||
}
|
||||
}
|
||||
if !hasQuantityItemKey(options.QuantityItems, "level6Ammo") {
|
||||
t.Fatal("迁移后的默认选项缺少 level6Ammo")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValuePreservesCurrentSchema(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
for index := range options.ScreenshotSlots {
|
||||
if options.ScreenshotSlots[index].Key == "tencentSecurity" {
|
||||
options.ScreenshotSlots[index].Required = true
|
||||
}
|
||||
}
|
||||
options.QuantityItems = append(options.QuantityItems, PublishQuantityItem{
|
||||
Key: "kit5",
|
||||
Label: "自定义旧选项",
|
||||
Price: "9元/个",
|
||||
})
|
||||
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 !hasQuantityItemKey(got.QuantityItems, "kit5") {
|
||||
t.Fatal("当前版本配置中的自定义项不应被旧版迁移清掉")
|
||||
}
|
||||
for _, slot := range got.ScreenshotSlots {
|
||||
if slot.Key == "tencentSecurity" && !slot.Required {
|
||||
t.Fatal("当前版本配置中 tencentSecurity.required=true 不应被覆盖")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueRepairsMojibake(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
t.Fatalf("构造配置失败: %v", err)
|
||||
}
|
||||
mojibake := encodeCP1252Mojibake(string(raw))
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(mojibake)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化乱码配置失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if !containsString(got.ServerOptions, "微信") {
|
||||
t.Fatalf("乱码未恢复,server_options = %#v", got.ServerOptions)
|
||||
}
|
||||
if got.SchemaVersion != publishOptionsSchemaVersion {
|
||||
t.Fatalf("schema_version = %d, want %d", got.SchemaVersion, publishOptionsSchemaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeCP1252Mojibake(value string) string {
|
||||
var builder strings.Builder
|
||||
for _, item := range []byte(value) {
|
||||
if item >= 0x80 && item <= 0x9F {
|
||||
builder.WriteRune(cp1252HighRunes[item-0x80])
|
||||
continue
|
||||
}
|
||||
builder.WriteRune(rune(item))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func hasQuantityItemKey(items []PublishQuantityItem, key string) bool {
|
||||
for _, item := range items {
|
||||
if item.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsString(items []string, target string) bool {
|
||||
for _, item := range items {
|
||||
if item == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"hfb_sys/backend/internal/auditlog"
|
||||
"hfb_sys/backend/internal/model"
|
||||
@@ -54,6 +55,7 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"},
|
||||
{Key: awRecycleConfigKey, Value: defaultAWRecycleConfigValue(), Description: "首页 AW炫彩回收入口配置 JSON(标题/副标题/展示图片)"},
|
||||
{Key: cooperationFeedbackConfigKey, Value: defaultCooperationFeedbackConfigValue(), Description: "首页 合作与反馈入口配置 JSON(标题/副标题/展示图片)"},
|
||||
{Key: homeEntriesEnabledConfigKey, Value: defaultHomeEntriesEnabledConfigValue(), Description: "首页 AW炫彩回收/合作与反馈入口总开关(关闭后 PC/移动端均隐藏)"},
|
||||
{Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), Description: "发布页选项配置 JSON"},
|
||||
{Key: salePriceConfigKey, Value: defaultSalePriceConfigValue(), Description: "内部出售定价规则 JSON"},
|
||||
{Key: DistributionSettlementHoldDaysKey, Value: defaultDistributionSettlementHoldDaysValue(), Description: "新建分销站点默认结算冷却天数(0=立即释放,最大 365)"},
|
||||
@@ -84,6 +86,7 @@ var adminVisibleConfigKeys = []string{
|
||||
"mobile.home_banners",
|
||||
"mobile.aw_recycle",
|
||||
"mobile.cooperation_feedback",
|
||||
"mobile.home_entries_enabled",
|
||||
"order.agreements",
|
||||
"order.pending_payment_timeout_minutes",
|
||||
"order.return_overdue_grace_minutes",
|
||||
@@ -288,30 +291,14 @@ func updateLegacyPublishOptions(tx *gorm.DB, item defaultConfig) error {
|
||||
if err := tx.Where("`key` = ?", item.Key).First(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if isLegacyPublishOptionsValue(row.Value) {
|
||||
row.Value = item.Value
|
||||
row.Description = item.Description
|
||||
return tx.Save(&row).Error
|
||||
}
|
||||
var options PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(row.Value), &options); err != nil {
|
||||
row.Value = item.Value
|
||||
row.Description = item.Description
|
||||
return tx.Save(&row).Error
|
||||
}
|
||||
before := row.Value
|
||||
isOldPublishOptionsSchema := !strings.Contains(before, `"ban_record_options"`)
|
||||
normalizePublishOptions(&options)
|
||||
if isOldPublishOptionsSchema {
|
||||
applyConditionalBanEvidenceDefault(&options)
|
||||
}
|
||||
raw, err := json.Marshal(options)
|
||||
normalized, err := normalizePublishOptionsValue(row.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
normalized = item.Value
|
||||
}
|
||||
row.Value = string(raw)
|
||||
changed := row.Value != normalized || row.Description != item.Description
|
||||
row.Value = normalized
|
||||
row.Description = item.Description
|
||||
if row.Value == before {
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
return tx.Save(&row).Error
|
||||
@@ -333,41 +320,164 @@ func applyConditionalBanEvidenceDefault(options *PublishOptionsDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
func isLegacyPublishOptionsValue(value string) bool {
|
||||
legacyMarkers := []string{
|
||||
`"kit5"`,
|
||||
"5级全装套",
|
||||
"6级子弹",
|
||||
"6头数量",
|
||||
"6甲数量",
|
||||
"0.26元/发",
|
||||
"0.8元/发",
|
||||
"çº",
|
||||
"å¼",
|
||||
"å…",
|
||||
"å¤",
|
||||
"ä½",
|
||||
"é«",
|
||||
"ï¼",
|
||||
func normalizePublishOptionsValue(value string) (string, error) {
|
||||
value = repairMojibakeJSON(value)
|
||||
var options PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(value), &options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, marker := range legacyMarkers {
|
||||
if strings.Contains(value, marker) {
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal([]byte(value), &fields); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if options.SchemaVersion < publishOptionsSchemaVersion {
|
||||
if hasLegacyPublishQuantityItem(options.QuantityItems) {
|
||||
options = DefaultPublishOptions()
|
||||
} else {
|
||||
normalizePublishOptions(&options)
|
||||
if _, ok := fields["ban_record_options"]; !ok {
|
||||
applyConditionalBanEvidenceDefault(&options)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
normalizePublishOptions(&options)
|
||||
}
|
||||
options.SchemaVersion = publishOptionsSchemaVersion
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
func hasLegacyPublishQuantityItem(items []PublishQuantityItem) bool {
|
||||
for _, item := range items {
|
||||
if item.Key == "kit5" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isMojibakeText(value string) bool {
|
||||
mojibakeMarkers := []string{"å", "æ", "ç", "è", "é", "ä", "ï¼", "ã€", "â"}
|
||||
for _, marker := range mojibakeMarkers {
|
||||
if strings.Contains(value, marker) {
|
||||
// repairMojibakeJSON 先按整段 JSON 还原;整段不是纯乱码时,再逐个字符串字段尝试还原。
|
||||
func repairMojibakeJSON(value string) string {
|
||||
if decoded, ok := decodeCP1252Mojibake(value); ok {
|
||||
return decoded
|
||||
}
|
||||
var root any
|
||||
if err := json.Unmarshal([]byte(value), &root); err != nil {
|
||||
return value
|
||||
}
|
||||
repaired, changed := repairMojibakeValue(root)
|
||||
if !changed {
|
||||
return value
|
||||
}
|
||||
raw, err := json.Marshal(repaired)
|
||||
if err != nil {
|
||||
return value
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
func repairMojibakeValue(value any) (any, bool) {
|
||||
switch current := value.(type) {
|
||||
case string:
|
||||
if decoded, ok := decodeCP1252Mojibake(current); ok {
|
||||
return decoded, true
|
||||
}
|
||||
return current, false
|
||||
case []any:
|
||||
changed := false
|
||||
for index := range current {
|
||||
next, updated := repairMojibakeValue(current[index])
|
||||
if updated {
|
||||
current[index] = next
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return current, changed
|
||||
case map[string]any:
|
||||
changed := false
|
||||
for key, item := range current {
|
||||
next, updated := repairMojibakeValue(item)
|
||||
if updated {
|
||||
current[key] = next
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return current, changed
|
||||
default:
|
||||
return value, false
|
||||
}
|
||||
}
|
||||
|
||||
// decodeCP1252Mojibake 把「UTF-8 字节被当作 CP1252/Latin-1 字符」的文本还原为原始 UTF-8。
|
||||
// 普通中文包含 CP1252 无法表示的字符,会直接判定失败;纯 ASCII 也不会误判。
|
||||
func decodeCP1252Mojibake(value string) (string, bool) {
|
||||
if value == "" {
|
||||
return "", false
|
||||
}
|
||||
raw := make([]byte, 0, len(value))
|
||||
sawNonASCII := false
|
||||
for _, char := range value {
|
||||
next, ok := cp1252Byte(char)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if next >= 0x80 {
|
||||
sawNonASCII = true
|
||||
}
|
||||
raw = append(raw, next)
|
||||
}
|
||||
if !sawNonASCII || !utf8.Valid(raw) {
|
||||
return "", false
|
||||
}
|
||||
decoded := string(raw)
|
||||
if decoded == value {
|
||||
return "", false
|
||||
}
|
||||
// 只接受能还原出汉字的候选,避免把正常的西文重音字符误判为乱码。
|
||||
if !containsHan(decoded) {
|
||||
return "", false
|
||||
}
|
||||
return decoded, true
|
||||
}
|
||||
|
||||
func containsHan(value string) bool {
|
||||
for _, char := range value {
|
||||
if char >= 0x4E00 && char <= 0x9FFF {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// cp1252HighRunes 是 CP1252 中 0x80-0x9F 对应的 Unicode 字符;
|
||||
// 其余可映射字符与 Latin-1 一致。
|
||||
var cp1252HighRunes = [32]rune{
|
||||
0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
|
||||
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
|
||||
0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
|
||||
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
|
||||
}
|
||||
|
||||
func cp1252Byte(char rune) (byte, bool) {
|
||||
if char <= 0xFF {
|
||||
return byte(char), true
|
||||
}
|
||||
for offset, candidate := range cp1252HighRunes {
|
||||
if char == candidate {
|
||||
return byte(0x80 + offset), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func isMojibakeText(value string) bool {
|
||||
_, ok := decodeCP1252Mojibake(value)
|
||||
return ok
|
||||
}
|
||||
|
||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||
return auditlog.Append(tx, auditlog.Entry{
|
||||
ActorType: "admin",
|
||||
|
||||
@@ -3,11 +3,18 @@ package systemconfig
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
)
|
||||
|
||||
const homeEntriesEnabledConfigKey = "mobile.home_entries_enabled"
|
||||
|
||||
func defaultHomeEntriesEnabledConfigValue() string {
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (s *Service) HomeAnnouncements(ctx context.Context) (*HomeAnnouncementsDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
@@ -43,12 +50,17 @@ func (s *Service) HomeConfig(ctx context.Context, siteCode string) (*HomeConfigD
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
homeEntriesEnabled, err := s.homeEntriesEnabled(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &HomeConfigDTO{
|
||||
Announcements: announcements,
|
||||
Banners: banners,
|
||||
PublishOptions: publishOptions,
|
||||
AWRecycle: awRecycle,
|
||||
CooperationFeedback: cooperationFeedback,
|
||||
HomeEntriesEnabled: homeEntriesEnabled,
|
||||
}
|
||||
s.applySiteHomeOverrides(ctx, siteCode, config)
|
||||
return config, nil
|
||||
@@ -147,3 +159,16 @@ func (s *Service) cooperationFeedback(ctx context.Context) (CooperationFeedbackC
|
||||
}
|
||||
return normalizeCooperationFeedback(item), nil
|
||||
}
|
||||
|
||||
func (s *Service) homeEntriesEnabled(ctx context.Context) (bool, error) {
|
||||
value, err := s.repo.FindValue(ctx, homeEntriesEnabledConfigKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return parseHomeEntriesEnabled(value), nil
|
||||
}
|
||||
|
||||
func parseHomeEntriesEnabled(value string) bool {
|
||||
enabled, err := strconv.ParseBool(strings.TrimSpace(value))
|
||||
return err == nil && enabled
|
||||
}
|
||||
|
||||
@@ -26,6 +26,13 @@ func (s *Service) Update(ctx context.Context, actorID uint64, key string, req Up
|
||||
return nil, ErrInvalidConfig
|
||||
}
|
||||
}
|
||||
if key == publishOptionsConfigKey {
|
||||
value, err := normalizePublishOptionsValue(req.Value)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidConfig
|
||||
}
|
||||
req.Value = value
|
||||
}
|
||||
if key == rentergrowth.ConfigKey {
|
||||
cfg, err := rentergrowth.ParseConfigValue(req.Value)
|
||||
if err != nil {
|
||||
|
||||
@@ -38,6 +38,33 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.home-entry-switch-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #e5edf8;
|
||||
border-radius: 8px;
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.home-entry-switch-row > div {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.home-entry-switch-row strong {
|
||||
color: #111827;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home-entry-switch-row span {
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.welcome-config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
@@ -17,15 +17,21 @@ import {
|
||||
type HomeBannerSlide,
|
||||
} from '@/features/listings/api/homeConfig'
|
||||
import type { OrderAgreements } from '@/features/orders/api/orders'
|
||||
import { fetchSystemConfigs, type SystemConfig } from '@/features/admin/api/systemConfigs'
|
||||
import {
|
||||
fetchSystemConfigs,
|
||||
updateSystemConfig,
|
||||
type SystemConfig,
|
||||
} from '@/features/admin/api/systemConfigs'
|
||||
import { parseRenterGrowthConfig } from '@/features/admin/api/renterGrowth'
|
||||
import {
|
||||
parseSmsBusinessTemplatesConfig,
|
||||
smsNodeMetas,
|
||||
} from '@/features/admin/api/smsNodeTemplates'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
import { safeParseJSON } from '@/shared/utils/json'
|
||||
import { formatSystemConfigSelectValue } from '@/shared/utils/systemConfigOptions'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// 导入重构后的 Dialog 子组件
|
||||
import PublishOptionsDialog from '../components/PublishOptionsDialog.vue'
|
||||
@@ -120,6 +126,14 @@ const awRecycleConfig = computed(
|
||||
const cooperationFeedbackConfig = computed(
|
||||
() => configs.value.find(item => item.key === 'mobile.cooperation_feedback') || null
|
||||
)
|
||||
const homeEntriesEnabledConfig = computed(
|
||||
() => configs.value.find(item => item.key === 'mobile.home_entries_enabled') || null
|
||||
)
|
||||
const homeEntriesDisplayEnabled = computed(() => {
|
||||
const raw = homeEntriesEnabledConfig.value?.value.trim().toLowerCase() || ''
|
||||
return raw === 'true' || raw === '1'
|
||||
})
|
||||
const homeEntriesSwitchSaving = ref(false)
|
||||
const listingPublishAgreementsConfig = computed(
|
||||
() => configs.value.find(item => item.key === 'listing.publish_agreements') || null
|
||||
)
|
||||
@@ -167,6 +181,7 @@ const regularConfigs = computed(() =>
|
||||
item.key !== 'mobile.home_banners' &&
|
||||
item.key !== 'mobile.aw_recycle' &&
|
||||
item.key !== 'mobile.cooperation_feedback' &&
|
||||
item.key !== 'mobile.home_entries_enabled' &&
|
||||
item.key !== 'listing.publish_agreements' &&
|
||||
item.key !== 'order.agreements' &&
|
||||
item.key !== 'profile.post_rental_notice' &&
|
||||
@@ -216,6 +231,7 @@ const homeStats = computed(() => {
|
||||
|
||||
const awRecycleStats = computed(() => {
|
||||
const item = parseAWRecycle(awRecycleConfig.value?.value || '')
|
||||
if (!homeEntriesDisplayEnabled.value) return { enabledLabel: '已隐藏' }
|
||||
return {
|
||||
enabledLabel: item.enabled ? (item.image_url ? '已配置图' : '已开启') : '已隐藏',
|
||||
}
|
||||
@@ -223,6 +239,7 @@ const awRecycleStats = computed(() => {
|
||||
|
||||
const cooperationFeedbackStats = computed(() => {
|
||||
const item = parseCooperationFeedback(cooperationFeedbackConfig.value?.value || '')
|
||||
if (!homeEntriesDisplayEnabled.value) return { enabledLabel: '已隐藏' }
|
||||
return {
|
||||
enabledLabel: item.enabled ? (item.image_url ? '已配置图' : '已开启') : '已隐藏',
|
||||
}
|
||||
@@ -288,6 +305,25 @@ async function loadConfigs() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleHomeEntriesDisplayChange(value: string | number | boolean) {
|
||||
const row = homeEntriesEnabledConfig.value
|
||||
if (!row || homeEntriesSwitchSaving.value) return
|
||||
const next = value === true || value === 1 || value === 'true'
|
||||
homeEntriesSwitchSaving.value = true
|
||||
try {
|
||||
await updateSystemConfig(row.key, {
|
||||
value: next ? 'true' : 'false',
|
||||
description: row.description,
|
||||
})
|
||||
await loadConfigs()
|
||||
ElMessage.success(next ? '首页业务入口已恢复展示' : '首页业务入口已隐藏')
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '保存失败'))
|
||||
} finally {
|
||||
homeEntriesSwitchSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function systemConfigByKey(key: string, fallbackValue: string, description: string): SystemConfig {
|
||||
const existing = configs.value.find(item => item.key === key)
|
||||
if (existing) return existing
|
||||
@@ -654,7 +690,11 @@ function formatConfigValue(row: SystemConfig) {
|
||||
|
||||
<section
|
||||
v-if="
|
||||
homeAnnouncementsConfig || homeBannersConfig || awRecycleConfig || cooperationFeedbackConfig
|
||||
homeAnnouncementsConfig ||
|
||||
homeBannersConfig ||
|
||||
awRecycleConfig ||
|
||||
cooperationFeedbackConfig ||
|
||||
homeEntriesEnabledConfig
|
||||
"
|
||||
class="publish-config-panel"
|
||||
>
|
||||
@@ -685,6 +725,23 @@ function formatConfigValue(row: SystemConfig) {
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-entry-switch-row">
|
||||
<div>
|
||||
<strong>首页业务入口展示</strong>
|
||||
<span
|
||||
>总开关关闭后,PC
|
||||
与移动端同时隐藏「AW炫彩回收」「合作与反馈」;开启后按各入口自己的启用状态展示。</span
|
||||
>
|
||||
</div>
|
||||
<el-switch
|
||||
:model-value="homeEntriesDisplayEnabled"
|
||||
:loading="homeEntriesSwitchSaving"
|
||||
:disabled="!homeEntriesEnabledConfig || homeEntriesSwitchSaving"
|
||||
active-text="展示"
|
||||
inactive-text="隐藏"
|
||||
@change="handleHomeEntriesDisplayChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="publish-stat-grid home-stat-grid">
|
||||
<div class="publish-stat">
|
||||
<strong>{{ homeStats.announcementCount }}</strong>
|
||||
|
||||
@@ -129,6 +129,12 @@ describe('mergeHomeConfig', () => {
|
||||
expect(config.banners.length).toBeGreaterThan(0)
|
||||
expect(config.banners[0]?.pc.image_url).toBe('')
|
||||
})
|
||||
|
||||
it('首页业务入口总开关缺省关闭,显式开启时透传', () => {
|
||||
expect(mergeHomeConfig({}).home_entries_enabled).toBe(false)
|
||||
expect(mergeHomeConfig({ home_entries_enabled: true }).home_entries_enabled).toBe(true)
|
||||
expect(mergeHomeConfig({ home_entries_enabled: false }).home_entries_enabled).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createBannerHotspot', () => {
|
||||
|
||||
@@ -209,8 +209,13 @@ export interface MobileHomeConfig {
|
||||
publish_options: ListingPublishOptions
|
||||
aw_recycle: AWRecycleConfig
|
||||
cooperation_feedback: CooperationFeedbackConfig
|
||||
/** 首页业务入口总开关:关闭后 PC/移动端同时隐藏回收与合作入口。 */
|
||||
home_entries_enabled: boolean
|
||||
}
|
||||
|
||||
/** 总开关默认关闭,避免新环境误展示尚未准备好的业务入口。 */
|
||||
export const defaultHomeEntriesEnabled = false
|
||||
|
||||
export const defaultAWRecycleConfig: AWRecycleConfig = {
|
||||
title: 'AW炫彩回收',
|
||||
subtitle: '点击查看回收说明',
|
||||
@@ -320,6 +325,10 @@ export function mergeHomeConfig(config?: Partial<MobileHomeConfig>): MobileHomeC
|
||||
publish_options: mergeListingPublishOptions(config?.publish_options),
|
||||
aw_recycle: awRecycle,
|
||||
cooperation_feedback: cooperationFeedback,
|
||||
home_entries_enabled:
|
||||
typeof config?.home_entries_enabled === 'boolean'
|
||||
? config.home_entries_enabled
|
||||
: defaultHomeEntriesEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
defaultCooperationFeedbackConfig,
|
||||
defaultHomeAnnouncements,
|
||||
defaultHomeBanners,
|
||||
defaultHomeEntriesEnabled,
|
||||
fetchMobileHomeConfig,
|
||||
normalizeHomeBanner,
|
||||
type AWRecycleConfig,
|
||||
@@ -71,6 +72,20 @@ const awRecycle = ref<AWRecycleConfig>({ ...defaultAWRecycleConfig })
|
||||
const awRecycleDialogVisible = ref(false)
|
||||
const cooperationFeedback = ref<CooperationFeedbackConfig>({ ...defaultCooperationFeedbackConfig })
|
||||
const cooperationFeedbackDialogVisible = ref(false)
|
||||
const homeEntriesEnabled = ref(defaultHomeEntriesEnabled)
|
||||
const showAWRecycle = computed(() => homeEntriesEnabled.value && awRecycle.value.enabled)
|
||||
const showCooperationFeedback = computed(
|
||||
() => homeEntriesEnabled.value && cooperationFeedback.value.enabled
|
||||
)
|
||||
const showBizEntryRail = computed(
|
||||
() => !siteStore.isDistribution || showAWRecycle.value || showCooperationFeedback.value
|
||||
)
|
||||
const bizEntryCount = computed(
|
||||
() =>
|
||||
(siteStore.isDistribution ? 0 : 1) +
|
||||
(showAWRecycle.value ? 1 : 0) +
|
||||
(showCooperationFeedback.value ? 1 : 0)
|
||||
)
|
||||
const publishOptions = ref<ListingPublishOptions>(emptyListingPublishOptions)
|
||||
interface SortButtonItem {
|
||||
key: string
|
||||
@@ -251,12 +266,14 @@ async function loadHome() {
|
||||
banners.value = config.banners
|
||||
awRecycle.value = config.aw_recycle
|
||||
cooperationFeedback.value = config.cooperation_feedback
|
||||
homeEntriesEnabled.value = config.home_entries_enabled
|
||||
publishOptions.value = config.publish_options
|
||||
} catch {
|
||||
announcements.value = defaultHomeAnnouncements
|
||||
banners.value = defaultHomeBanners
|
||||
awRecycle.value = { ...defaultAWRecycleConfig }
|
||||
cooperationFeedback.value = { ...defaultCooperationFeedbackConfig }
|
||||
homeEntriesEnabled.value = defaultHomeEntriesEnabled
|
||||
publishOptions.value = emptyListingPublishOptions
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -404,6 +421,7 @@ function restoreFromCache(): boolean {
|
||||
banners.value = pcHomeCache.banners.map(slide => normalizeHomeBanner(slide))
|
||||
awRecycle.value = { ...pcHomeCache.awRecycle }
|
||||
cooperationFeedback.value = { ...pcHomeCache.cooperationFeedback }
|
||||
homeEntriesEnabled.value = pcHomeCache.homeEntriesEnabled
|
||||
publishOptions.value = pcHomeCache.publishOptions
|
||||
}
|
||||
return true
|
||||
@@ -425,6 +443,7 @@ function saveToCache() {
|
||||
banners: banners.value,
|
||||
awRecycle: awRecycle.value,
|
||||
cooperationFeedback: cooperationFeedback.value,
|
||||
homeEntriesEnabled: homeEntriesEnabled.value,
|
||||
publishOptions: publishOptions.value,
|
||||
})
|
||||
pcHomeCache.saveScrollTop(window.scrollY)
|
||||
@@ -539,11 +558,16 @@ if (!restored) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-section">
|
||||
<div class="hero-section" :class="{ 'without-entry-rail': !showBizEntryRail }">
|
||||
<HomeBanner :banners="displayBanners" :stats="statCards" />
|
||||
|
||||
<!-- 业务入口:贴在轮播右侧竖排;原「租号大厅」只是跳回首页,已移除 -->
|
||||
<div class="biz-entry-rail" aria-label="业务入口">
|
||||
<div
|
||||
v-if="showBizEntryRail"
|
||||
class="biz-entry-rail"
|
||||
:class="{ 'single-entry': bizEntryCount === 1 }"
|
||||
aria-label="业务入口"
|
||||
>
|
||||
<RouterLink v-if="!siteStore.isDistribution" class="biz-entry crash" to="/crash">
|
||||
<div class="biz-title-row">
|
||||
<strong>撞车 <em>NEW</em></strong>
|
||||
@@ -552,7 +576,7 @@ if (!restored) {
|
||||
<van-icon name="arrow" :size="14" class="biz-arrow" />
|
||||
</RouterLink>
|
||||
<button
|
||||
v-if="awRecycle.enabled"
|
||||
v-if="showAWRecycle"
|
||||
type="button"
|
||||
class="biz-entry aw-recycle"
|
||||
@click="openAWRecycle"
|
||||
@@ -564,7 +588,7 @@ if (!restored) {
|
||||
<van-icon name="arrow" :size="14" class="biz-arrow" />
|
||||
</button>
|
||||
<button
|
||||
v-if="cooperationFeedback.enabled"
|
||||
v-if="showCooperationFeedback"
|
||||
type="button"
|
||||
class="biz-entry cooperation-feedback"
|
||||
@click="openCooperationFeedback"
|
||||
@@ -699,6 +723,10 @@ if (!restored) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hero-section.without-entry-rail {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.biz-entry-rail {
|
||||
display: grid;
|
||||
grid-auto-rows: minmax(0, 1fr);
|
||||
@@ -706,6 +734,11 @@ if (!restored) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.biz-entry-rail.single-entry {
|
||||
grid-auto-rows: auto;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.biz-entry {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -843,6 +876,14 @@ if (!restored) {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.biz-entry-rail.single-entry {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.biz-entry-rail.two-entries {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.biz-entry {
|
||||
min-height: 76px;
|
||||
padding: 14px 40px 14px 16px;
|
||||
|
||||
@@ -493,6 +493,14 @@
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.biz-entry-grid.single-entry {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.biz-entry-grid.two-entries {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.biz-entry {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
defaultCooperationFeedbackConfig,
|
||||
defaultHomeAnnouncements,
|
||||
defaultHomeBanners,
|
||||
defaultHomeEntriesEnabled,
|
||||
fetchMobileHomeConfig,
|
||||
hasBannerImage,
|
||||
normalizeHomeBanner,
|
||||
@@ -126,6 +127,21 @@ const awRecycle = ref<AWRecycleConfig>({ ...defaultAWRecycleConfig })
|
||||
const awRecyclePreviewVisible = ref(false)
|
||||
const cooperationFeedback = ref<CooperationFeedbackConfig>({ ...defaultCooperationFeedbackConfig })
|
||||
const cooperationFeedbackPreviewVisible = ref(false)
|
||||
const homeEntriesEnabled = ref(defaultHomeEntriesEnabled)
|
||||
const showAWRecycle = computed(() => homeEntriesEnabled.value && awRecycle.value.enabled)
|
||||
const showCooperationFeedback = computed(
|
||||
() => homeEntriesEnabled.value && cooperationFeedback.value.enabled
|
||||
)
|
||||
const showCrashEntry = computed(() => !siteStore.isDistribution)
|
||||
const showBizEntryGrid = computed(
|
||||
() => showCrashEntry.value || showAWRecycle.value || showCooperationFeedback.value
|
||||
)
|
||||
const bizEntryCount = computed(
|
||||
() =>
|
||||
(showCrashEntry.value ? 1 : 0) +
|
||||
(showAWRecycle.value ? 1 : 0) +
|
||||
(showCooperationFeedback.value ? 1 : 0)
|
||||
)
|
||||
const supportLoading = ref(false)
|
||||
const showBackTop = ref(false)
|
||||
const mobilePageSize = 10
|
||||
@@ -361,6 +377,9 @@ function restoreHomeConfigFromCache() {
|
||||
if (!homeCache.homeConfigLoaded) return false
|
||||
announcements.value = homeCache.announcements.slice()
|
||||
bannerSlides.value = homeCache.bannerSlides.map(slide => normalizeHomeBanner(slide))
|
||||
awRecycle.value = { ...homeCache.awRecycle }
|
||||
cooperationFeedback.value = { ...homeCache.cooperationFeedback }
|
||||
homeEntriesEnabled.value = homeCache.homeEntriesEnabled
|
||||
publishOptions.value = homeCache.publishOptions
|
||||
return true
|
||||
}
|
||||
@@ -479,10 +498,14 @@ async function loadHomeConfig() {
|
||||
bannerSlides.value = config.banners
|
||||
awRecycle.value = config.aw_recycle
|
||||
cooperationFeedback.value = config.cooperation_feedback
|
||||
homeEntriesEnabled.value = config.home_entries_enabled
|
||||
publishOptions.value = config.publish_options
|
||||
homeCache.saveHomeConfig({
|
||||
announcements: config.announcements,
|
||||
bannerSlides: config.banners,
|
||||
awRecycle: config.aw_recycle,
|
||||
cooperationFeedback: config.cooperation_feedback,
|
||||
homeEntriesEnabled: config.home_entries_enabled,
|
||||
publishOptions: config.publish_options,
|
||||
})
|
||||
} catch {
|
||||
@@ -490,6 +513,7 @@ async function loadHomeConfig() {
|
||||
bannerSlides.value = defaultHomeBanners
|
||||
awRecycle.value = { ...defaultAWRecycleConfig }
|
||||
cooperationFeedback.value = { ...defaultCooperationFeedbackConfig }
|
||||
homeEntriesEnabled.value = defaultHomeEntriesEnabled
|
||||
publishOptions.value = emptyListingPublishOptions
|
||||
}
|
||||
}
|
||||
@@ -556,10 +580,14 @@ async function onRefresh() {
|
||||
bannerSlides.value = nextHomeConfig.banners
|
||||
awRecycle.value = nextHomeConfig.aw_recycle
|
||||
cooperationFeedback.value = nextHomeConfig.cooperation_feedback
|
||||
homeEntriesEnabled.value = nextHomeConfig.home_entries_enabled
|
||||
publishOptions.value = nextHomeConfig.publish_options
|
||||
homeCache.saveHomeConfig({
|
||||
announcements: nextHomeConfig.announcements,
|
||||
bannerSlides: nextHomeConfig.banners,
|
||||
awRecycle: nextHomeConfig.aw_recycle,
|
||||
cooperationFeedback: nextHomeConfig.cooperation_feedback,
|
||||
homeEntriesEnabled: nextHomeConfig.home_entries_enabled,
|
||||
publishOptions: nextHomeConfig.publish_options,
|
||||
})
|
||||
showToast({ message: '刷新成功', icon: 'passed' })
|
||||
@@ -944,14 +972,22 @@ if (!restoreHomeConfigFromCache()) {
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
|
||||
<div class="biz-entry-grid" aria-label="业务入口">
|
||||
<RouterLink v-if="!siteStore.isDistribution" class="biz-entry" to="/crash">
|
||||
<div
|
||||
v-if="showBizEntryGrid"
|
||||
class="biz-entry-grid"
|
||||
:class="{
|
||||
'single-entry': bizEntryCount === 1,
|
||||
'two-entries': bizEntryCount === 2,
|
||||
}"
|
||||
aria-label="业务入口"
|
||||
>
|
||||
<RouterLink v-if="showCrashEntry" class="biz-entry" to="/crash">
|
||||
<strong>撞车</strong>
|
||||
<small>选购商品 · 一键找客服</small>
|
||||
<em class="biz-entry-badge">NEW</em>
|
||||
</RouterLink>
|
||||
<button
|
||||
v-if="awRecycle.enabled"
|
||||
v-if="showAWRecycle"
|
||||
type="button"
|
||||
class="biz-entry aw-recycle"
|
||||
@click="openAWRecycle"
|
||||
@@ -960,7 +996,7 @@ if (!restoreHomeConfigFromCache()) {
|
||||
<small>{{ awRecycle.subtitle }}</small>
|
||||
</button>
|
||||
<button
|
||||
v-if="cooperationFeedback.enabled"
|
||||
v-if="showCooperationFeedback"
|
||||
type="button"
|
||||
class="biz-entry cooperation-feedback"
|
||||
@click="openCooperationFeedback"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import {
|
||||
defaultAWRecycleConfig,
|
||||
defaultCooperationFeedbackConfig,
|
||||
defaultHomeAnnouncements,
|
||||
defaultHomeBanners,
|
||||
defaultHomeEntriesEnabled,
|
||||
type AWRecycleConfig,
|
||||
type CooperationFeedbackConfig,
|
||||
type HomeBannerSlide,
|
||||
} from '@/features/listings/api/homeConfig'
|
||||
import {
|
||||
@@ -44,6 +49,9 @@ export const useMobileHomeCacheStore = defineStore('mobileHomeCache', {
|
||||
scrollTop: 0,
|
||||
announcements: [...defaultHomeAnnouncements] as string[],
|
||||
bannerSlides: defaultHomeBanners.map(slide => ({ ...slide })) as HomeBannerSlide[],
|
||||
awRecycle: { ...defaultAWRecycleConfig } as AWRecycleConfig,
|
||||
cooperationFeedback: { ...defaultCooperationFeedbackConfig } as CooperationFeedbackConfig,
|
||||
homeEntriesEnabled: defaultHomeEntriesEnabled,
|
||||
publishOptions: { ...emptyListingPublishOptions } as ListingPublishOptions,
|
||||
homeConfigLoaded: false,
|
||||
}),
|
||||
@@ -77,10 +85,16 @@ export const useMobileHomeCacheStore = defineStore('mobileHomeCache', {
|
||||
saveHomeConfig(payload: {
|
||||
announcements: string[]
|
||||
bannerSlides: HomeBannerSlide[]
|
||||
awRecycle: AWRecycleConfig
|
||||
cooperationFeedback: CooperationFeedbackConfig
|
||||
homeEntriesEnabled: boolean
|
||||
publishOptions: ListingPublishOptions
|
||||
}) {
|
||||
this.announcements = payload.announcements
|
||||
this.bannerSlides = payload.bannerSlides
|
||||
this.awRecycle = payload.awRecycle
|
||||
this.cooperationFeedback = payload.cooperationFeedback
|
||||
this.homeEntriesEnabled = payload.homeEntriesEnabled
|
||||
this.publishOptions = payload.publishOptions
|
||||
this.homeConfigLoaded = true
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
defaultCooperationFeedbackConfig,
|
||||
defaultHomeAnnouncements,
|
||||
defaultHomeBanners,
|
||||
defaultHomeEntriesEnabled,
|
||||
type AWRecycleConfig,
|
||||
type CooperationFeedbackConfig,
|
||||
type HomeBannerSlide,
|
||||
@@ -30,6 +31,7 @@ export interface PcHomeConfigSnapshot {
|
||||
awRecycle: AWRecycleConfig
|
||||
cooperationFeedback: CooperationFeedbackConfig
|
||||
publishOptions: ListingPublishOptions
|
||||
homeEntriesEnabled: boolean
|
||||
}
|
||||
|
||||
/** PC端首页列表/配置/滚动缓存:与页面实例解耦,从详情页返回时可无缝秒级恢复,避免回顶闪烁 */
|
||||
@@ -47,6 +49,7 @@ export const usePcHomeCacheStore = defineStore('pcHomeCache', {
|
||||
awRecycle: { ...defaultAWRecycleConfig } as AWRecycleConfig,
|
||||
cooperationFeedback: { ...defaultCooperationFeedbackConfig } as CooperationFeedbackConfig,
|
||||
publishOptions: { ...emptyListingPublishOptions } as ListingPublishOptions,
|
||||
homeEntriesEnabled: defaultHomeEntriesEnabled,
|
||||
homeConfigLoaded: false,
|
||||
}),
|
||||
getters: {
|
||||
@@ -77,6 +80,7 @@ export const usePcHomeCacheStore = defineStore('pcHomeCache', {
|
||||
this.awRecycle = payload.awRecycle
|
||||
this.cooperationFeedback = payload.cooperationFeedback
|
||||
this.publishOptions = payload.publishOptions
|
||||
this.homeEntriesEnabled = payload.homeEntriesEnabled
|
||||
this.homeConfigLoaded = true
|
||||
},
|
||||
clearList() {
|
||||
|
||||
Reference in New Issue
Block a user