后台系统配置列表现在只返回当前真实使用的配置项
This commit is contained in:
@@ -110,7 +110,7 @@ func (j *Job) loadThresholds(ctx context.Context) (thresholds, error) {
|
||||
}
|
||||
for _, row := range rows {
|
||||
value, err := strconv.Atoi(row.Value)
|
||||
if err != nil || value <= 0 {
|
||||
if err != nil || value < 0 {
|
||||
continue
|
||||
}
|
||||
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) {
|
||||
if cfg.PendingPaymentTimeoutMinutes <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var rows []model.RentalOrder
|
||||
err := j.db.WithContext(ctx).
|
||||
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) {
|
||||
if cfg.OwnerSubmitTimeoutMinutes <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var rows []model.RentalOrder
|
||||
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)).
|
||||
@@ -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) {
|
||||
if cfg.RenterConfirmTimeoutMinutes <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var rows []model.RentalOrder
|
||||
err := j.db.WithContext(ctx).
|
||||
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) {
|
||||
if cfg.OwnerReturnConfirmTimeoutMinutes <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var rows []model.RentalOrder
|
||||
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)).
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
db *gorm.DB
|
||||
chatRepo *chat.Repository
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ func (r *Repository) Pay(userID uint64, orderID uint64) error {
|
||||
return ErrOrderCannotPay
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1029,7 +1029,7 @@ func pendingPaymentTimeoutMinutes(tx *gorm.DB) int {
|
||||
return defaultPendingPaymentTimeoutMinutes
|
||||
}
|
||||
value, err := strconv.Atoi(row.Value)
|
||||
if err != nil || value <= 0 {
|
||||
if err != nil || value < 0 {
|
||||
return defaultPendingPaymentTimeoutMinutes
|
||||
}
|
||||
return value
|
||||
|
||||
@@ -33,21 +33,30 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: "handoff.owner_return_confirm_timeout_minutes", Value: "120", Description: "号主待确认结账超时分钟数"},
|
||||
{Key: "order.pending_payment_timeout_minutes", Value: "15", 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: "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.auto_welcome_message", Value: "欢迎加入订单群聊!如有任何问题,请随时沟通。", Description: "建群后自动发送的欢迎话术"},
|
||||
{Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"},
|
||||
{Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"},
|
||||
{Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), 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 {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
@@ -57,7 +66,7 @@ func (r *Repository) List() ([]ConfigDTO, error) {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
items := make([]ConfigDTO, 0, len(rows))
|
||||
|
||||
@@ -93,12 +93,6 @@ WHERE au.username IN ('kf1', 'kf2') AND r.code = 'cs';
|
||||
-- -------------------------------------------
|
||||
|
||||
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', '商品是否需要审核'),
|
||||
('order.pending_payment_timeout_minutes', '15', '待支付超时时间(分钟)'),
|
||||
('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'
|
||||
);
|
||||
@@ -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 { formatDateTime } from '@/utils/time'
|
||||
import { safeParseJSON } from '@/utils/json'
|
||||
import { formatSystemConfigSelectValue } from '@/utils/systemConfigOptions'
|
||||
|
||||
// 导入重构后的 Dialog 子组件
|
||||
import PublishOptionsDialog from './components/PublishOptionsDialog.vue'
|
||||
@@ -154,6 +155,10 @@ function formatConfigValue(row: SystemConfig) {
|
||||
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
||||
return 'JSON 配置'
|
||||
}
|
||||
const selectedLabel = formatSystemConfigSelectValue(row.key, row.value)
|
||||
if (selectedLabel) {
|
||||
return selectedLabel
|
||||
}
|
||||
return row.value
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ElMessage } from 'element-plus'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { updateSystemConfig, type SystemConfig } from '@/api/systemConfigs'
|
||||
import { getSystemConfigSelectOptions, type SystemConfigOption } from '@/utils/systemConfigOptions'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -34,6 +35,15 @@ const isStructuredConfig = computed(() => {
|
||||
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() {
|
||||
submitting.value = true
|
||||
try {
|
||||
@@ -73,6 +83,16 @@ function readError(error: unknown, fallback: string) {
|
||||
<el-form-item v-if="isStructuredConfig" label="配置值 (JSON)" class="full-control">
|
||||
<el-input v-model="value" type="textarea" :rows="12" placeholder="配置值 JSON" />
|
||||
</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-input v-model="value" placeholder="配置值" />
|
||||
</el-form-item>
|
||||
@@ -103,6 +123,10 @@ function readError(error: unknown, fallback: string) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.desc-item {
|
||||
margin-top: 14px;
|
||||
margin-bottom: 0;
|
||||
|
||||
Reference in New Issue
Block a user