优化一键测试权重

This commit is contained in:
2026-03-27 14:14:59 +08:00
parent 0bdab95ab7
commit e2273ef41c
13 changed files with 403 additions and 285 deletions

View File

@@ -57,6 +57,10 @@
"title": "One-Click Weight Test",
"alertTitle": "Bonus pool logic",
"alertBody": "Same as playStart draw: uses name=default safety line and kill switch; when profit is below the line, paid tickets use player tier weights (custom below), free tickets use killScore; when profit reaches the line and kill is on, both use killScore.",
"chainModeHint": "Simulation: set paid spin counts only (CW/CCW). If a paid draw hits “play again” (or T5), the next draw is free with the same ante, lottery type free, paid amount 0. Free-draw tier odds are configured below (including chained free plays).",
"sectionPaid": "Paid draws",
"sectionFreeAfterPlayAgain": "Free draw tier odds (after play-again)",
"tierProbHintFreeChain": "When using custom tier odds: T1T5 below apply when a free draw runs (tier roll; combined with dice_reward row weights).",
"stepPaid": "Paid ticket",
"stepFree": "Free ticket",
"labelLotteryTypePaid": "Test pool type",
@@ -75,6 +79,7 @@
"btnStart": "Start test",
"btnCancel": "Cancel",
"warnAnte": "Ante must be greater than 0",
"warnPaidSpins": "Paid clockwise + counter-clockwise spin counts must be greater than 0",
"warnTotalSpins": "At least one of paid/free direction spin counts must be greater than 0",
"warnPaidTierSumPositive": "When no paid pool is selected, T1T5 odds sum must be greater than 0",
"warnPaidTierSumMax": "Paid T1T5 odds sum cannot exceed 100%",

View File

@@ -8,9 +8,15 @@
"counterclockwiseAbbr": "CCW",
"status": "Status",
"paidDraw": "Paid Draw",
"freeDraw": "Free Draw",
"chainMode": "Chain play-again",
"chainModeYes": "Yes",
"chainModeNo": "No",
"paidPlannedSpins": "Planned paid spins",
"playAgainCount": "Play-again count",
"progressDraws": "{over} done",
"progressFailed": "{over} before fail",
"platformProfit": "Platform Profit",
"totalDrawCount": "Total Draw Count",
"totalDrawCount": "Total draws",
"createdBy": "Created By",
"remark": "Remark",
"createTime": "Create Time",
@@ -37,6 +43,10 @@
"recordId": "Record ID",
"testCount": "Test count",
"testCountSuffix": " runs",
"testCountProgress": "In progress: {over} done",
"testCountFailed": "{over} before failure",
"chainModeLabel": "Chain play-again",
"paidPlannedSpins": "Planned paid spins",
"createTime": "Created at",
"admin": "Operator",
"paidPoolId": "Paid lottery pool config ID",

View File

@@ -57,6 +57,10 @@
"title": "一键测试权重",
"alertTitle": "彩金池逻辑说明",
"alertBody": "与 playStart 抽奖逻辑一致:使用 name=default 的安全线、杀分开关;盈利未达安全线时,付费抽奖券使用玩家自身权重(下方自定义档位),免费抽奖券使用 killScore 配置;盈利达到安全线且杀分开启时,付费/免费均使用 killScore 配置。",
"chainModeHint": "模拟方式:只配置付费抽奖次数(顺/逆时针)。付费抽到「再来一次」或 T5 时,下一局自动为免费抽奖,底注与触发局相同,抽奖类型记为免费、付费金额记为 0。免费抽奖的档位概率由下方「免费抽奖」配置决定含通过再来一次触发的后续免费局。",
"sectionPaid": "付费抽奖",
"sectionFreeAfterPlayAgain": "免费抽奖(再来一次后的档位概率)",
"tierProbHintFreeChain": "当使用自定义档位时:以下为「免费抽奖」时 T1T5 的档位概率(仅在有免费局时参与摇档,与 dice_reward 格子权重共同决定结果)。",
"stepPaid": "付费抽奖券",
"stepFree": "免费抽奖券",
"labelLotteryTypePaid": "测试数据档位类型",
@@ -75,6 +79,7 @@
"btnStart": "开始测试",
"btnCancel": "取消",
"warnAnte": "底注 ante 必须大于 0",
"warnPaidSpins": "付费抽奖顺时针与逆时针次数之和须大于 0",
"warnTotalSpins": "付费或免费至少一种方向次数之和大于 0",
"warnPaidTierSumPositive": "付费未选奖池时T1T5 档位概率之和需大于 0",
"warnPaidTierSumMax": "付费档位概率 T1T5 之和不能超过 100%",

View File

@@ -8,7 +8,13 @@
"counterclockwiseAbbr": "逆",
"status": "状态",
"paidDraw": "付费抽取",
"freeDraw": "免费抽取",
"chainMode": "链式再来一次",
"chainModeYes": "是",
"chainModeNo": "否",
"paidPlannedSpins": "计划付费次数",
"playAgainCount": "再来一次次数",
"progressDraws": "已完成 {over} 次",
"progressFailed": "失败前 {over} 次",
"platformProfit": "平台赚取金额",
"totalDrawCount": "总抽奖次数",
"createdBy": "创建管理员",
@@ -37,6 +43,10 @@
"recordId": "记录ID",
"testCount": "测试次数",
"testCountSuffix": "次",
"testCountProgress": "进行中:已完成 {over} 次",
"testCountFailed": "失败前 {over} 次",
"chainModeLabel": "链式再来一次",
"paidPlannedSpins": "计划付费次数",
"createTime": "创建时间",
"admin": "执行管理员",
"paidPoolId": "付费奖池配置ID",

View File

@@ -57,14 +57,15 @@ export default {
},
/**
* 一键测试权重:创建测试记录并启动后台执行,按付费/免费、顺逆方向交替抽奖
* 可选 lottery_config_id不选则传 paid_tier_weights / free_tier_weightsT1-T5
* 一键测试权重:创建测试记录并启动后台执行
* chain_free_mode=true仅模拟付费次数付费抽到再来一次则插入免费抽奖同底注、付费金额 0
*/
startWeightTest(params: {
ante?: number
lottery_config_id?: number
paid_lottery_config_id?: number
free_lottery_config_id?: number
chain_free_mode?: boolean
s_count?: number
n_count?: number
paid_s_count?: number

View File

@@ -11,155 +11,125 @@
<template #title>{{ $t('page.weightTest.alertTitle') }}</template>
{{ $t('page.weightTest.alertBody') }}
</ElAlert>
<ElForm ref="formRef" :model="form" label-width="140px">
<ElAlert type="warning" :closable="false" show-icon class="weight-test-tip chain-tip">
{{ $t('page.weightTest.chainModeHint') }}
</ElAlert>
<ElForm :model="form" label-width="140px">
<ElFormItem :label="$t('page.weightTest.labelAnte')" prop="ante" required>
<ElInputNumber v-model="form.ante" :min="1" :step="1" style="width: 100%" />
</ElFormItem>
<ElSteps :active="currentStep" finish-status="success" simple class="steps-wrap">
<ElStep :title="$t('page.weightTest.stepPaid')" />
<ElStep :title="$t('page.weightTest.stepFree')" />
</ElSteps>
<!-- 第一页付费抽奖券 -->
<div v-show="currentStep === 0" class="step-panel">
<ElFormItem
:label="$t('page.weightTest.labelLotteryTypePaid')"
prop="paid_lottery_config_id"
<div class="section-title">{{ $t('page.weightTest.sectionPaid') }}</div>
<ElFormItem
:label="$t('page.weightTest.labelLotteryTypePaid')"
prop="paid_lottery_config_id"
>
<ElSelect
v-model="form.paid_lottery_config_id"
:placeholder="$t('page.weightTest.placeholderPaidPool')"
clearable
filterable
style="width: 100%"
>
<ElSelect
v-model="form.paid_lottery_config_id"
:placeholder="$t('page.weightTest.placeholderPaidPool')"
clearable
filterable
style="width: 100%"
>
<ElOption
v-for="item in paidLotteryOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<template v-if="form.paid_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
<ElRow :gutter="12" class="tier-row">
<ElCol v-for="t in tierKeys" :key="'paid-' + t" :span="8">
<div class="tier-field">
<label class="tier-field-label">{{
$t('page.weightTest.tierFieldLabel', { tier: t })
}}</label>
<input
type="number"
:value="getPaidTier(t)"
min="0"
max="100"
placeholder="0"
class="tier-input"
@input="setPaidTier(t, $event)"
/>
</div>
</ElCol>
</ElRow>
<div v-if="paidTierSum > 100" class="tier-error">{{
$t('page.weightTest.tierSumError', { sum: paidTierSum })
}}</div>
</template>
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="paid_s_count" required>
<ElSelect
v-model="form.paid_s_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="paid_n_count" required>
<ElSelect
v-model="form.paid_n_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
</div>
<ElOption
v-for="item in paidLotteryOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<template v-if="form.paid_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
<ElRow :gutter="12" class="tier-row">
<ElCol v-for="t in tierKeys" :key="'paid-' + t" :span="8">
<div class="tier-field">
<label class="tier-field-label">{{
$t('page.weightTest.tierFieldLabel', { tier: t })
}}</label>
<input
type="number"
:value="getPaidTier(t)"
min="0"
max="100"
placeholder="0"
class="tier-input"
@input="setPaidTier(t, $event)"
/>
</div>
</ElCol>
</ElRow>
<div v-if="paidTierSum > 100" class="tier-error">{{
$t('page.weightTest.tierSumError', { sum: paidTierSum })
}}</div>
</template>
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="paid_s_count" required>
<ElSelect
v-model="form.paid_s_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="paid_n_count" required>
<ElSelect
v-model="form.paid_n_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
<!-- 第二页免费抽奖券 -->
<div v-show="currentStep === 1" class="step-panel">
<ElFormItem
:label="$t('page.weightTest.labelLotteryTypeFree')"
prop="free_lottery_config_id"
<div class="section-title">{{ $t('page.weightTest.sectionFreeAfterPlayAgain') }}</div>
<ElFormItem
:label="$t('page.weightTest.labelLotteryTypeFree')"
prop="free_lottery_config_id"
>
<ElSelect
v-model="form.free_lottery_config_id"
:placeholder="$t('page.weightTest.placeholderFreePool')"
clearable
filterable
style="width: 100%"
>
<ElSelect
v-model="form.free_lottery_config_id"
:placeholder="$t('page.weightTest.placeholderFreePool')"
clearable
filterable
style="width: 100%"
>
<ElOption
v-for="item in freeLotteryOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<template v-if="form.free_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
<ElRow :gutter="12" class="tier-row">
<ElCol v-for="t in tierKeys" :key="'free-' + t" :span="8">
<div class="tier-field">
<label class="tier-field-label">{{
$t('page.weightTest.tierFieldLabel', { tier: t })
}}</label>
<input
type="number"
:value="getFreeTier(t)"
min="0"
max="100"
placeholder="0"
class="tier-input"
@input="setFreeTier(t, $event)"
/>
</div>
</ElCol>
</ElRow>
<div v-if="freeTierSum > 100" class="tier-error">{{
$t('page.weightTest.tierSumError', { sum: freeTierSum })
}}</div>
</template>
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="free_s_count" required>
<ElSelect
v-model="form.free_s_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="free_n_count" required>
<ElSelect
v-model="form.free_n_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
</div>
<ElOption
v-for="item in freeLotteryOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<template v-if="form.free_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHintFreeChain') }}</div>
<ElRow :gutter="12" class="tier-row">
<ElCol v-for="t in tierKeys" :key="'free-' + t" :span="8">
<div class="tier-field">
<label class="tier-field-label">{{
$t('page.weightTest.tierFieldLabel', { tier: t })
}}</label>
<input
type="number"
:value="getFreeTier(t)"
min="0"
max="100"
placeholder="0"
class="tier-input"
@input="setFreeTier(t, $event)"
/>
</div>
</ElCol>
</ElRow>
<div v-if="freeTierSum > 100" class="tier-error">{{
$t('page.weightTest.tierSumError', { sum: freeTierSum })
}}</div>
</template>
</ElForm>
<template #footer>
<ElButton v-if="currentStep > 0" :disabled="running" @click="currentStep--">{{
$t('page.weightTest.btnPrev')
}}</ElButton>
<ElButton v-if="currentStep < 1" type="primary" :disabled="running" @click="currentStep++">{{
$t('page.weightTest.btnNext')
}}</ElButton>
<ElButton
v-if="currentStep === 1"
v-permission="'dice:reward:index:startWeightTest'"
type="primary"
:loading="running"
@@ -187,8 +157,6 @@
const visible = defineModel<boolean>({ default: false })
const emit = defineEmits<{ (e: 'success'): void }>()
const formRef = ref()
const currentStep = ref(0)
const form = reactive({
ante: 1,
paid_lottery_config_id: undefined as number | undefined,
@@ -196,9 +164,7 @@
paid_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
paid_s_count: 100,
paid_n_count: 100,
free_s_count: 100,
free_n_count: 100
paid_n_count: 100
})
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
/** 付费抽奖券可选档位name=default */
@@ -214,7 +180,6 @@
function onClose() {
running.value = false
currentStep.value = 0
}
function getPaidTier(t: string): string {
@@ -255,12 +220,10 @@
id: r.id,
name: r.name
}))
// 付费抽奖券默认使用 name=default
const normal = list.find((r: { name?: string }) => r.name === 'default')
if (normal) {
form.paid_lottery_config_id = normal.id
}
// 免费抽奖券默认使用 name=killScore若无则默认选第一项
const kill = list.find((r: { name?: string }) => r.name === 'killScore')
if (kill) {
form.free_lottery_config_id = kill.id
@@ -277,8 +240,9 @@
ante: form.ante,
paid_s_count: form.paid_s_count,
paid_n_count: form.paid_n_count,
free_s_count: form.free_s_count,
free_n_count: form.free_n_count
free_s_count: 0,
free_n_count: 0,
chain_free_mode: true
}
if (form.paid_lottery_config_id != null) {
payload.paid_lottery_config_id = form.paid_lottery_config_id
@@ -298,8 +262,8 @@
ElMessage.warning(t('page.weightTest.warnAnte'))
return false
}
if (form.paid_s_count + form.paid_n_count + form.free_s_count + form.free_n_count <= 0) {
ElMessage.warning(t('page.weightTest.warnTotalSpins'))
if (form.paid_s_count + form.paid_n_count <= 0) {
ElMessage.warning(t('page.weightTest.warnPaidSpins'))
return false
}
const needPaidTier = form.paid_lottery_config_id == null
@@ -351,28 +315,22 @@
onClose()
}
})
// 切换到免费步骤时,若当前选中 id 不在免费档位列表中,则重置为第一个 killScore 的选项,避免显示错误
watch(currentStep, (step) => {
if (step === 1) {
const freeOpts = freeLotteryOptions.value
const id = form.free_lottery_config_id
if (freeOpts.length && (id == null || !freeOpts.some((o) => o.id === id))) {
form.free_lottery_config_id = freeOpts[0].id
}
}
})
</script>
<style lang="scss" scoped>
.weight-test-tip {
margin-bottom: 16px;
}
.steps-wrap {
margin-bottom: 16px;
.chain-tip {
margin-top: -8px;
}
.step-panel {
min-height: 200px;
.section-title {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 8px 0 12px;
padding-bottom: 6px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.tier-label {
font-size: 13px;

View File

@@ -40,19 +40,18 @@
<template #status="{ row }">
<span>{{ formatStatus(row.status) }}</span>
</template>
<!-- 付费抽取顺时针逆时针抽取次数兼容旧数据用 s_count/n_count -->
<!-- 付费抽取顺时针逆时针抽取次数 -->
<template #paid_draw="{ row }">
<span
>{{ $t('page.table.clockwiseAbbr') }} {{ getPaidS(row) }} /
{{ $t('page.table.counterclockwiseAbbr') }} {{ getPaidN(row) }}</span
>
</template>
<!-- 免费抽取顺时针逆时针抽取次数 -->
<template #free_draw="{ row }">
<span
>{{ $t('page.table.clockwiseAbbr') }} {{ row.free_s_count ?? 0 }} /
{{ $t('page.table.counterclockwiseAbbr') }} {{ row.free_n_count ?? 0 }}</span
>
<template #chain_mode="{ row }">
<span>{{ formatChainMode(row) }}</span>
</template>
<template #total_draw="{ row }">
<span>{{ formatTotalDraw(row) }}</span>
</template>
<!-- 平台赚取金额 -->
<template #platform_profit="{ row }">
@@ -136,16 +135,12 @@
return t('page.detail.dash')
}
// 付费抽取次数(兼容旧数据:无 paid_s_count 时用 s_count
// 付费抽取次数
function getPaidS(row: Record<string, any>): number {
const v = row.paid_s_count
if (v !== undefined && v !== null && (Number(v) || 0) > 0) return Number(v)
return Number(row.s_count ?? 0)
return Number(row.paid_s_count ?? 0)
}
function getPaidN(row: Record<string, any>): number {
const v = row.paid_n_count
if (v !== undefined && v !== null && (Number(v) || 0) > 0) return Number(v)
return Number(row.n_count ?? 0)
return Number(row.paid_n_count ?? 0)
}
// 平台赚取金额展示(未完成或空显示 —)
@@ -157,6 +152,31 @@
return String(n)
}
/** 链式再来一次1=是新库字段JSON 旧数据用 tier_weights_snapshot.chain_free_mode */
function formatChainMode(row: Record<string, any>): string {
const v = row.chain_free_mode
if (v === 1 || v === '1' || v === true) return t('page.table.chainModeYes')
const snap = row.tier_weights_snapshot
if (snap && typeof snap === 'object' && (snap as { chain_free_mode?: boolean }).chain_free_mode) {
return t('page.table.chainModeYes')
}
return t('page.table.chainModeNo')
}
/** 总抽奖次数:仅完成态写最终值;测试中显示已完成次数 */
function formatTotalDraw(row: Record<string, any>): string {
const status = Number(row.status)
const done = Number(row.total_play_count ?? 0)
const over = Number(row.over_play_count ?? 0)
if (status === 1) {
return String(done)
}
if (status === -1) {
return over > 0 ? t('page.table.progressFailed', { over }) : t('page.detail.dash')
}
return t('page.table.progressDraws', { over })
}
// 表格配置
const {
columns,
@@ -193,12 +213,24 @@
useSlot: true
},
{
prop: 'free_draw',
label: 'page.table.freeDraw',
width: 160,
prop: 'chain_mode',
label: 'page.table.chainMode',
width: 110,
align: 'center',
useSlot: true
},
{
prop: 'paid_planned_spins',
label: 'page.table.paidPlannedSpins',
width: 120,
align: 'center'
},
{
prop: 'play_again_count',
label: 'page.table.playAgainCount',
width: 120,
align: 'center'
},
{
prop: 'platform_profit',
label: 'page.table.platformProfit',
@@ -206,7 +238,13 @@
align: 'center',
useSlot: true
},
{ prop: 'total_play_count', label: 'page.table.totalDrawCount', width: 110, align: 'center' },
{
prop: 'total_draw',
label: 'page.table.totalDrawCount',
width: 140,
align: 'center',
useSlot: true
},
{
prop: 'admin_name',
label: 'page.table.createdBy',

View File

@@ -14,9 +14,15 @@
<el-descriptions-item :label="$t('page.detail.recordId')">
{{ record.id }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.testCount')"
>{{ record.test_count }}{{ $t('page.detail.testCountSuffix') }}</el-descriptions-item
>
<el-descriptions-item :label="$t('page.detail.chainModeLabel')">
{{ formatChainModeDetail(record) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.paidPlannedSpins')">
{{ record.paid_planned_spins ?? $t('page.detail.dash') }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.testCount')">
{{ formatTestCountDisplay(record) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.createTime')">
{{ record.create_time || $t('page.detail.dash') }}
</el-descriptions-item>
@@ -231,6 +237,11 @@
interface RecordRow {
id?: number
test_count?: number
total_play_count?: number
status?: number
over_play_count?: number
chain_free_mode?: number | boolean
paid_planned_spins?: number
create_time?: string
admin_id?: number | null
admin_name?: string
@@ -238,7 +249,6 @@
paid_lottery_config_id?: number | null
free_lottery_config_id?: number | null
bigwin_weight?: Record<string, number> | Array<[number, number]> | null
// 新结构:{ paid: {T1..T5}, free: {T1..T5} },兼容旧结构直接是 {T1..T5}
tier_weights_snapshot?:
| {
paid?: Record<string, number>
@@ -257,6 +267,32 @@
result_counts?: Record<string, number>
}
function formatChainModeDetail(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
const v = record.chain_free_mode
if (v === 1 || v === '1' || v === true) return t('page.table.chainModeYes')
const snap = record.tier_weights_snapshot
if (snap && typeof snap === 'object' && (snap as { chain_free_mode?: boolean }).chain_free_mode) {
return t('page.table.chainModeYes')
}
return t('page.table.chainModeNo')
}
function formatTestCountDisplay(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
const status = Number(record.status)
if (status === 1) {
const n = record.test_count ?? record.total_play_count
return `${n ?? 0}${t('page.detail.testCountSuffix')}`
}
if (status === -1) {
const over = Number(record.over_play_count ?? 0)
return over > 0 ? t('page.detail.testCountFailed', { over }) : t('page.detail.dash')
}
const over = Number(record.over_play_count ?? 0)
return t('page.detail.testCountProgress', { over })
}
interface Props {
modelValue: boolean
record: RecordRow | null