diff --git a/src/views/finance/FundFreezeConfig.vue b/src/views/finance/FundFreezeConfig.vue index f0bd629..05ebc87 100644 --- a/src/views/finance/FundFreezeConfig.vue +++ b/src/views/finance/FundFreezeConfig.vue @@ -9,7 +9,7 @@ type="warning" :closable="false" show-icon - title="默认关闭。未开启时结算全额进可提现,与现网一致。仅对当前顶部所选俱乐部生效。" + title="打开「启用冻结」并保存后,该角色新入账立即按规则冻结;旧单仍按当时快照解冻。关闭则新入账全额可提现。定时任务约每 5 分钟扫描到期/条件解冻。" style="margin-bottom: 16px" />
当前俱乐部:{{ clubId }}
@@ -24,16 +24,17 @@
- + + 开关与冻结额 - - 关闭=该角色入账不冻结 + + 保存后立即对新入账生效 按比例 固定额 - 比例+上限 + 比例 + 单笔上限 @@ -45,7 +46,7 @@ - 0=不限 + 0 = 不限 @@ -57,50 +58,173 @@ 考核代收费 + + +
小程序冻结旁「?」点开后展示这段文字
+
+ + 解冻触发 - - + + - - - + + + + + + 一次全解 / 仅条件模式:未满此天数不解 + 一次全解 分期解冻 - - - 一次全解时生效;分期以各步 after_days 为准 - - - - - - - - - + + + + 达标条件(选项) + +
+
+ 最低完成单量 + + 窗口内完成单 ≥ 此值 +
+
+ 最高罚款率 + + 0.05 = 罚款率不超过 5% + 窗口 + + +
+
+ 最高投诉率 + + 窗口 + + +
+
+ 最低有效邀请 + + 管事/组长更适用 +
+
+ 可提现 {{ preview.available }} / 冻结 {{ preview.frozen }} 重算 - 保存该角色规则 + + {{ form.enabled ? '保存并启用' : '保存(保持关闭)' }} +
@@ -121,10 +245,66 @@ const clubId = ref('') const roles = ref([]) const activeRole = ref('dashou') const form = ref(null) -const installmentText = ref('') -const conditionText = ref('') +const steps = ref([]) +const cond = reactive({ + enable_min_completed_orders: false, + min_completed_orders: 30, + enable_max_fine_rate: false, + max_fine_rate: 0.05, + fine_rate_window_days: 30, + enable_max_complaint_rate: false, + max_complaint_rate: 0.05, + complaint_rate_window_days: 30, + enable_min_valid_invites: false, + min_valid_invites: 0, +}) const preview = reactive({ available: '100', frozen: '0' }) +function defaultStep() { + return { after_days: 7, type: 'ratio', value: 0.3, need_condition: false } +} + +function syncCondFromJson(cj) { + const c = cj || {} + const minOrders = Number(c.min_completed_orders || 0) + const maxFine = Number(c.max_fine_rate ?? 1) + const maxComp = Number(c.max_complaint_rate ?? 1) + const minInv = Number(c.min_valid_invites || 0) + cond.enable_min_completed_orders = c.enable_min_completed_orders != null + ? !!c.enable_min_completed_orders + : minOrders > 0 + cond.min_completed_orders = minOrders || 30 + cond.enable_max_fine_rate = c.enable_max_fine_rate != null + ? !!c.enable_max_fine_rate + : maxFine >= 0 && maxFine < 1 + cond.max_fine_rate = maxFine < 1 ? maxFine : 0.05 + cond.fine_rate_window_days = Number(c.fine_rate_window_days || 30) + cond.enable_max_complaint_rate = c.enable_max_complaint_rate != null + ? !!c.enable_max_complaint_rate + : maxComp >= 0 && maxComp < 1 + cond.max_complaint_rate = maxComp < 1 ? maxComp : 0.05 + cond.complaint_rate_window_days = Number(c.complaint_rate_window_days || 30) + cond.enable_min_valid_invites = c.enable_min_valid_invites != null + ? !!c.enable_min_valid_invites + : minInv > 0 + cond.min_valid_invites = minInv +} + +function buildConditionJson() { + return { + enable_min_completed_orders: !!cond.enable_min_completed_orders, + min_completed_orders: Number(cond.min_completed_orders || 0), + enable_max_fine_rate: !!cond.enable_max_fine_rate, + max_fine_rate: Number(cond.max_fine_rate || 0), + fine_rate_window_days: Number(cond.fine_rate_window_days || 30), + enable_max_complaint_rate: !!cond.enable_max_complaint_rate, + max_complaint_rate: Number(cond.max_complaint_rate || 0), + complaint_rate_window_days: Number(cond.complaint_rate_window_days || 30), + enable_min_valid_invites: !!cond.enable_min_valid_invites, + min_valid_invites: Number(cond.min_valid_invites || 0), + } +} + function syncFormFromRole(role) { const item = roles.value.find((r) => r.role === role) if (!item) { @@ -139,9 +319,18 @@ function syncFormFromRole(role) { min_credit_to_freeze: Number(item.min_credit_to_freeze || 0), freeze_min_days: Number(item.freeze_min_days || 0), source_scope: Array.isArray(item.source_scope) ? [...item.source_scope] : ['order_settle'], + display_reason: item.display_reason || item.remark || '', } - installmentText.value = JSON.stringify(item.installment_json || {}, null, 2) - conditionText.value = JSON.stringify(item.condition_json || {}, null, 2) + const rawSteps = item.installment_json?.steps + steps.value = Array.isArray(rawSteps) && rawSteps.length + ? rawSteps.map((s) => ({ + after_days: Number(s.after_days || 0), + type: s.type || 'ratio', + value: Number(s.value ?? (s.type === 'remain' ? 1 : 0.3)), + need_condition: !!s.need_condition, + })) + : [defaultStep(), { after_days: 14, type: 'ratio', value: 0.3, need_condition: true }, { after_days: 30, type: 'remain', value: 1, need_condition: true }] + syncCondFromJson(item.condition_json) preview.available = item.preview_100?.available || '100' preview.frozen = item.preview_100?.frozen || '0' } @@ -150,6 +339,15 @@ function onTabChange(name) { syncFormFromRole(name) } +function addStep() { + steps.value.push(defaultStep()) +} + +function removeStep(index) { + if (steps.value.length <= 1) return + steps.value.splice(index, 1) +} + async function loadConfig() { loading.value = true try { @@ -204,20 +402,14 @@ watch( async function save() { if (!form.value) return - let installment_json = {} - let condition_json = {} - try { - installment_json = installmentText.value ? JSON.parse(installmentText.value) : {} - } catch { - ElMessage.error('分期 JSON 格式错误') - return - } - try { - condition_json = conditionText.value ? JSON.parse(conditionText.value) : {} - } catch { - ElMessage.error('条件 JSON 格式错误') + if (!form.value.source_scope?.length) { + ElMessage.error('请至少选择一个入账来源') return } + const installment_json = form.value.schedule_mode === 'installment' + ? { steps: steps.value.map((s, i) => ({ ...s, seq: i + 1 })) } + : { steps: [{ seq: 1, after_days: form.value.freeze_min_days || 0, type: 'remain', value: 1, need_condition: false }] } + saving.value = true try { const res = await request.post(JITUAN_API.fundFreezeConfig, { @@ -235,11 +427,14 @@ async function save() { schedule_mode: form.value.schedule_mode, freeze_min_days: form.value.freeze_min_days, installment_json, - condition_json, - remark: form.value.remark, + condition_json: buildConditionJson(), + display_reason: form.value.display_reason, + remark: form.value.display_reason, }) if (res.code === 0) { - ElMessage.success('已保存') + ElMessage.success(form.value.enabled + ? '已保存并启用:新入账将按规则冻结,解冻由定时任务自动处理' + : '已保存(未启用):新入账仍全额可提现') await loadConfig() } else { ElMessage.error(res.msg || '保存失败') @@ -279,4 +474,27 @@ onMounted(loadConfig) color: #888; font-size: 12px; } +.tip.block { + display: block; + margin: 6px 0 0; +} +.steps-wrap { + width: 100%; +} +.steps-toolbar { + display: flex; + align-items: center; + margin-bottom: 8px; +} +.cond-list { + display: flex; + flex-direction: column; + gap: 12px; +} +.cond-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +}