后台系统配置列表现在只返回当前真实使用的配置项

This commit is contained in:
yml2213
2026-05-27 08:40:03 +08:00
parent 80c7bb18bb
commit 8cbd06dca4
8 changed files with 144 additions and 18 deletions
+13 -1
View File
@@ -110,7 +110,7 @@ func (j *Job) loadThresholds(ctx context.Context) (thresholds, error) {
} }
for _, row := range rows { for _, row := range rows {
value, err := strconv.Atoi(row.Value) value, err := strconv.Atoi(row.Value)
if err != nil || value <= 0 { if err != nil || value < 0 {
continue continue
} }
switch row.Key { switch row.Key {
@@ -130,6 +130,9 @@ func (j *Job) loadThresholds(ctx context.Context) (thresholds, error) {
} }
func (j *Job) handlePendingPaymentTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) { func (j *Job) handlePendingPaymentTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
if cfg.PendingPaymentTimeoutMinutes <= 0 {
return 0, nil
}
var rows []model.RentalOrder var rows []model.RentalOrder
err := j.db.WithContext(ctx). err := j.db.WithContext(ctx).
Where("status = ? AND created_at <= ?", "pending_payment", now.Add(-time.Duration(cfg.PendingPaymentTimeoutMinutes)*time.Minute)). Where("status = ? AND created_at <= ?", "pending_payment", now.Add(-time.Duration(cfg.PendingPaymentTimeoutMinutes)*time.Minute)).
@@ -189,6 +192,9 @@ func (j *Job) handlePendingPaymentTimeout(ctx context.Context, now time.Time, cf
} }
func (j *Job) handleOwnerSubmitTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) { func (j *Job) handleOwnerSubmitTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
if cfg.OwnerSubmitTimeoutMinutes <= 0 {
return 0, nil
}
var rows []model.RentalOrder var rows []model.RentalOrder
err := j.db.WithContext(ctx). err := j.db.WithContext(ctx).
Where("status = ? AND handoff_status = ? AND created_at <= ?", "pending_handoff", "pending_owner", now.Add(-time.Duration(cfg.OwnerSubmitTimeoutMinutes)*time.Minute)). Where("status = ? AND handoff_status = ? AND created_at <= ?", "pending_handoff", "pending_owner", now.Add(-time.Duration(cfg.OwnerSubmitTimeoutMinutes)*time.Minute)).
@@ -237,6 +243,9 @@ func (j *Job) handleOwnerSubmitTimeout(ctx context.Context, now time.Time, cfg t
} }
func (j *Job) handleRenterConfirmTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) { func (j *Job) handleRenterConfirmTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
if cfg.RenterConfirmTimeoutMinutes <= 0 {
return 0, nil
}
var rows []model.RentalOrder var rows []model.RentalOrder
err := j.db.WithContext(ctx). err := j.db.WithContext(ctx).
Where("status = ? AND handoff_status = ?", "pending_handoff", "pending_renter_confirm"). Where("status = ? AND handoff_status = ?", "pending_handoff", "pending_renter_confirm").
@@ -345,6 +354,9 @@ func (j *Job) handleReturnOverdue(ctx context.Context, now time.Time, cfg thresh
} }
func (j *Job) handleOwnerReturnConfirmTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) { func (j *Job) handleOwnerReturnConfirmTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
if cfg.OwnerReturnConfirmTimeoutMinutes <= 0 {
return 0, nil
}
var rows []model.RentalOrder var rows []model.RentalOrder
err := j.db.WithContext(ctx). err := j.db.WithContext(ctx).
Where("status = ? AND handoff_status = ? AND updated_at <= ?", "pending_checkout_confirm", "pending_owner_checkout", now.Add(-time.Duration(cfg.OwnerReturnConfirmTimeoutMinutes)*time.Minute)). Where("status = ? AND handoff_status = ? AND updated_at <= ?", "pending_checkout_confirm", "pending_owner_checkout", now.Add(-time.Duration(cfg.OwnerReturnConfirmTimeoutMinutes)*time.Minute)).
+3 -3
View File
@@ -20,7 +20,7 @@ import (
) )
type Repository struct { type Repository struct {
db *gorm.DB db *gorm.DB
chatRepo *chat.Repository chatRepo *chat.Repository
} }
@@ -195,7 +195,7 @@ func (r *Repository) Pay(userID uint64, orderID uint64) error {
return ErrOrderCannotPay return ErrOrderCannotPay
} }
timeoutMinutes := pendingPaymentTimeoutMinutes(tx) timeoutMinutes := pendingPaymentTimeoutMinutes(tx)
if order.CreatedAt.Before(time.Now().Add(-time.Duration(timeoutMinutes) * time.Minute)) { if timeoutMinutes > 0 && order.CreatedAt.Before(time.Now().Add(-time.Duration(timeoutMinutes)*time.Minute)) {
return ErrOrderCannotPay return ErrOrderCannotPay
} }
@@ -1029,7 +1029,7 @@ func pendingPaymentTimeoutMinutes(tx *gorm.DB) int {
return defaultPendingPaymentTimeoutMinutes return defaultPendingPaymentTimeoutMinutes
} }
value, err := strconv.Atoi(row.Value) value, err := strconv.Atoi(row.Value)
if err != nil || value <= 0 { if err != nil || value < 0 {
return defaultPendingPaymentTimeoutMinutes return defaultPendingPaymentTimeoutMinutes
} }
return value return value
@@ -33,21 +33,30 @@ var defaultConfigs = []defaultConfig{
{Key: "handoff.owner_return_confirm_timeout_minutes", Value: "120", Description: "号主待确认结账超时分钟数"}, {Key: "handoff.owner_return_confirm_timeout_minutes", Value: "120", Description: "号主待确认结账超时分钟数"},
{Key: "order.pending_payment_timeout_minutes", Value: "15", Description: "订单待支付超时取消分钟数"}, {Key: "order.pending_payment_timeout_minutes", Value: "15", Description: "订单待支付超时取消分钟数"},
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"}, {Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"},
{Key: "deposit.min_amount", Value: "50", Description: "发布租号最低押金"},
{Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"}, {Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"},
{Key: "risk.sms_limit_per_phone_hour", Value: "5", Description: "单手机号每小时短信验证码次数"},
{Key: "risk.sms_limit_per_ip_hour", Value: "20", Description: "单 IP 每小时短信验证码次数"},
{Key: "realname.required_for_order", Value: "false", Description: "下单是否必须完成实名认证"},
{Key: "settlement.platform_fee_rate", Value: "0.00", Description: "平台抽成比例"},
{Key: "settlement.owner_cycle_days", Value: "0", Description: "号主结算周期天数"},
{Key: "withdraw.min_amount", Value: "100", Description: "提现最低金额预留"},
{Key: "chat.default_support_admin_id", Value: "1", Description: "订单群聊回退客服 ID(仅当无可用客服时使用)"}, {Key: "chat.default_support_admin_id", Value: "1", Description: "订单群聊回退客服 ID(仅当无可用客服时使用)"},
{Key: "chat.auto_welcome_message", Value: "欢迎加入订单群聊!如有任何问题,请随时沟通。", Description: "建群后自动发送的欢迎话术"},
{Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"}, {Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"},
{Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"}, {Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"},
{Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), Description: "发布页选项配置 JSON"}, {Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), Description: "发布页选项配置 JSON"},
{Key: salePriceConfigKey, Value: defaultSalePriceConfigValue(), Description: "内部出售定价规则 JSON"}, {Key: salePriceConfigKey, Value: defaultSalePriceConfigValue(), Description: "内部出售定价规则 JSON"},
} }
var adminVisibleConfigKeys = []string{
"chat.auto_welcome_message",
"chat.default_support_admin_id",
"handoff.owner_return_confirm_timeout_minutes",
"handoff.owner_submit_timeout_minutes",
"handoff.renter_confirm_timeout_minutes",
"listing.publish_options",
"listing.review_required",
"listing.sale_price_config",
"mobile.home_announcements",
"mobile.home_banners",
"order.pending_payment_timeout_minutes",
"order.return_overdue_grace_minutes",
}
func NewRepository(db *gorm.DB) *Repository { func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db} return &Repository{db: db}
} }
@@ -57,7 +66,7 @@ func (r *Repository) List() ([]ConfigDTO, error) {
return nil, err return nil, err
} }
var rows []model.SystemConfig var rows []model.SystemConfig
if err := r.db.Order("`key` ASC").Find(&rows).Error; err != nil { if err := r.db.Where("`key` IN ?", adminVisibleConfigKeys).Order("`key` ASC").Find(&rows).Error; err != nil {
return nil, err return nil, err
} }
items := make([]ConfigDTO, 0, len(rows)) items := make([]ConfigDTO, 0, len(rows))
-6
View File
@@ -93,12 +93,6 @@ WHERE au.username IN ('kf1', 'kf2') AND r.code = 'cs';
-- ------------------------------------------- -- -------------------------------------------
INSERT INTO system_configs (`key`, `value`, description) VALUES INSERT INTO system_configs (`key`, `value`, description) VALUES
('order.handoff_timeout_minutes', '60', '交接超时时间(分钟)'),
('order.return_timeout_minutes', '1440', '归还超时时间(分钟)'),
('sms.rate_limit_per_minute', '1', '短信限流:每分钟最多发送次数'),
('sms.rate_limit_per_hour', '5', '短信限流:每小时最多发送次数'),
('wallet.min_deposit', '50', '最低押金金额'),
('platform.fee_ratio', '0.1', '平台抽成比例'),
('listing.review_required', 'true', '商品是否需要审核'), ('listing.review_required', 'true', '商品是否需要审核'),
('order.pending_payment_timeout_minutes', '15', '待支付超时时间(分钟)'), ('order.pending_payment_timeout_minutes', '15', '待支付超时时间(分钟)'),
('chat.default_support_admin_id', '1', '订单群聊回退客服 ID(仅当无可用客服时使用)'), ('chat.default_support_admin_id', '1', '订单群聊回退客服 ID(仅当无可用客服时使用)'),
@@ -0,0 +1,17 @@
-- 清理未接入业务逻辑的历史/预留系统配置,避免后台配置列表产生误解。
DELETE FROM system_configs
WHERE `key` IN (
'deposit.min_amount',
'platform.fee_ratio',
'realname.required_for_order',
'risk.sms_limit_per_ip_hour',
'risk.sms_limit_per_phone_hour',
'settlement.owner_cycle_days',
'settlement.platform_fee_rate',
'sms.rate_limit_per_hour',
'sms.rate_limit_per_minute',
'wallet.min_deposit',
'withdraw.min_amount',
'order.handoff_timeout_minutes',
'order.return_timeout_minutes'
);
+65
View File
@@ -0,0 +1,65 @@
export interface SystemConfigOption {
label: string
value: string
}
const shortTimeoutOptions: SystemConfigOption[] = [
{ label: '不限时', value: '0' },
{ label: '5 分钟', value: '5' },
{ label: '10 分钟', value: '10' },
{ label: '15 分钟', value: '15' },
{ label: '30 分钟', value: '30' },
{ label: '45 分钟', value: '45' },
{ label: '60 分钟', value: '60' },
{ label: '90 分钟', value: '90' },
{ label: '120 分钟', value: '120' },
]
const longTimeoutOptions: SystemConfigOption[] = [
{ label: '不限时', value: '0' },
{ label: '30 分钟', value: '30' },
{ label: '60 分钟', value: '60' },
{ label: '120 分钟', value: '120' },
{ label: '180 分钟', value: '180' },
{ label: '240 分钟', value: '240' },
{ label: '360 分钟', value: '360' },
{ label: '12 小时', value: '720' },
{ label: '24 小时', value: '1440' },
{ label: '48 小时', value: '2880' },
]
const booleanOptions: SystemConfigOption[] = [
{ label: '开启', value: 'true' },
{ label: '关闭', value: 'false' },
]
export const systemConfigSelectOptions: Record<string, SystemConfigOption[]> = {
'handoff.owner_submit_timeout_minutes': shortTimeoutOptions,
'handoff.renter_confirm_timeout_minutes': shortTimeoutOptions,
'handoff.owner_return_confirm_timeout_minutes': longTimeoutOptions,
'order.pending_payment_timeout_minutes': shortTimeoutOptions,
'order.return_overdue_grace_minutes': [
{ label: '无宽限', value: '0' },
{ label: '5 分钟', value: '5' },
{ label: '10 分钟', value: '10' },
{ label: '15 分钟', value: '15' },
{ label: '30 分钟', value: '30' },
{ label: '60 分钟', value: '60' },
{ label: '120 分钟', value: '120' },
],
'listing.review_required': booleanOptions,
'chat.default_support_admin_id': [
{ label: '管理员 ID 1', value: '1' },
{ label: '管理员 ID 2', value: '2' },
{ label: '管理员 ID 3', value: '3' },
],
}
export function getSystemConfigSelectOptions(key: string) {
return systemConfigSelectOptions[key] || null
}
export function formatSystemConfigSelectValue(key: string, value: string) {
const option = getSystemConfigSelectOptions(key)?.find((item) => item.value === value)
return option?.label || null
}
@@ -18,6 +18,7 @@ import {
import { fetchSystemConfigs, type SystemConfig } from '@/api/systemConfigs' import { fetchSystemConfigs, type SystemConfig } from '@/api/systemConfigs'
import { formatDateTime } from '@/utils/time' import { formatDateTime } from '@/utils/time'
import { safeParseJSON } from '@/utils/json' import { safeParseJSON } from '@/utils/json'
import { formatSystemConfigSelectValue } from '@/utils/systemConfigOptions'
// 导入重构后的 Dialog 子组件 // 导入重构后的 Dialog 子组件
import PublishOptionsDialog from './components/PublishOptionsDialog.vue' import PublishOptionsDialog from './components/PublishOptionsDialog.vue'
@@ -154,6 +155,10 @@ function formatConfigValue(row: SystemConfig) {
if (trimmed.startsWith('{') || trimmed.startsWith('[')) { if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
return 'JSON 配置' return 'JSON 配置'
} }
const selectedLabel = formatSystemConfigSelectValue(row.key, row.value)
if (selectedLabel) {
return selectedLabel
}
return row.value return row.value
} }
</script> </script>
@@ -3,6 +3,7 @@ import { ElMessage } from 'element-plus'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { updateSystemConfig, type SystemConfig } from '@/api/systemConfigs' import { updateSystemConfig, type SystemConfig } from '@/api/systemConfigs'
import { getSystemConfigSelectOptions, type SystemConfigOption } from '@/utils/systemConfigOptions'
const props = defineProps<{ const props = defineProps<{
modelValue: boolean modelValue: boolean
@@ -34,6 +35,15 @@ const isStructuredConfig = computed(() => {
return trimmed.startsWith('{') || trimmed.startsWith('[') return trimmed.startsWith('{') || trimmed.startsWith('[')
}) })
const selectOptions = computed<SystemConfigOption[] | null>(() => {
const options = getSystemConfigSelectOptions(props.config.key)
if (!options) return null
if (!value.value || options.some((item) => item.value === value.value)) {
return options
}
return [{ label: `当前值:${value.value}`, value: value.value }, ...options]
})
async function handleSave() { async function handleSave() {
submitting.value = true submitting.value = true
try { try {
@@ -73,6 +83,16 @@ function readError(error: unknown, fallback: string) {
<el-form-item v-if="isStructuredConfig" label="配置值 (JSON)" class="full-control"> <el-form-item v-if="isStructuredConfig" label="配置值 (JSON)" class="full-control">
<el-input v-model="value" type="textarea" :rows="12" placeholder="配置值 JSON" /> <el-input v-model="value" type="textarea" :rows="12" placeholder="配置值 JSON" />
</el-form-item> </el-form-item>
<el-form-item v-else-if="selectOptions" label="配置值" class="full-control">
<el-select v-model="value" class="full-select" placeholder="请选择配置值">
<el-option
v-for="option in selectOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item v-else label="配置值" class="full-control"> <el-form-item v-else label="配置值" class="full-control">
<el-input v-model="value" placeholder="配置值" /> <el-input v-model="value" placeholder="配置值" />
</el-form-item> </el-form-item>
@@ -103,6 +123,10 @@ function readError(error: unknown, fallback: string) {
width: 100%; width: 100%;
} }
.full-select {
width: 100%;
}
.desc-item { .desc-item {
margin-top: 14px; margin-top: 14px;
margin-bottom: 0; margin-bottom: 0;