后台系统配置列表现在只返回当前真实使用的配置项
This commit is contained in:
@@ -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)).
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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'
|
||||||
|
);
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user