变更:修复求婚同意消息未收到问题,重构求婚流程支持直接选婚礼档位
This commit is contained in:
@@ -111,6 +111,7 @@ class MarriageController extends Controller
|
||||
$data = $request->validate([
|
||||
'target_username' => 'required|string',
|
||||
'ring_purchase_id' => 'required|integer',
|
||||
'wedding_tier_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
$proposer = $request->user();
|
||||
@@ -120,7 +121,7 @@ class MarriageController extends Controller
|
||||
return response()->json(['ok' => false, 'message' => '用户不存在。'], 404);
|
||||
}
|
||||
|
||||
$result = $this->marriage->propose($proposer, $target, $data['ring_purchase_id']);
|
||||
$result = $this->marriage->propose($proposer, $target, $data['ring_purchase_id'], $data['wedding_tier_id'] ?? null);
|
||||
|
||||
if ($result['ok']) {
|
||||
$marriage = Marriage::find($result['marriage_id']);
|
||||
|
||||
@@ -37,10 +37,10 @@ class MarriageService
|
||||
*
|
||||
* @param User $proposer 求婚方
|
||||
* @param User $target 被求婚方
|
||||
* @param int $ringPurchaseId 使用的戒指购买记录 ID
|
||||
* @param int $weddingTierId 可选的婚礼档位 ID
|
||||
* @return array{ok: bool, message: string, marriage_id: int|null}
|
||||
*/
|
||||
public function propose(User $proposer, User $target, int $ringPurchaseId): array
|
||||
public function propose(User $proposer, User $target, int $ringPurchaseId, ?int $weddingTierId = null): array
|
||||
{
|
||||
// 不能向自己求婚
|
||||
if ($proposer->id === $target->id) {
|
||||
@@ -85,7 +85,17 @@ class MarriageService
|
||||
|
||||
$expireHours = $this->config->get('proposal_expire_hours', 48);
|
||||
|
||||
return DB::transaction(function () use ($proposer, $target, $ring, $expireHours) {
|
||||
return DB::transaction(function () use ($proposer, $target, $ring, $expireHours, $weddingTierId) {
|
||||
// 在求婚阶段同时进行婚礼设置(由求婚方一人出全资预扣,属于 "男方付 / scheduled冻结" 模式)
|
||||
if ($weddingTierId) {
|
||||
$tier = \App\Models\WeddingTier::find($weddingTierId);
|
||||
if ($tier && $tier->is_active) {
|
||||
if (($proposer->jjb ?? 0) < $tier->amount) {
|
||||
return ['ok' => false, 'message' => "金币不足,该婚礼档位需要 {$tier->amount} 金币。", 'marriage_id' => null];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 戒指状态改为占用中
|
||||
$ring->update(['status' => 'used_pending']);
|
||||
|
||||
@@ -103,6 +113,15 @@ class MarriageService
|
||||
'hyname1' => $target->username,
|
||||
]);
|
||||
|
||||
if ($weddingTierId) {
|
||||
$weddingService = app(WeddingService::class);
|
||||
// 预扣冻结:payerType=groom, ceremonyType=scheduled
|
||||
$setupRes = $weddingService->setup($marriage, $weddingTierId, 'groom', 'scheduled');
|
||||
if (!$setupRes['ok']) {
|
||||
throw new \Exception($setupRes['message']);
|
||||
}
|
||||
}
|
||||
|
||||
return ['ok' => true, 'message' => '求婚成功,等待对方回应。', 'marriage_id' => $marriage->id];
|
||||
});
|
||||
}
|
||||
@@ -161,6 +180,15 @@ class MarriageService
|
||||
// 初始亲密度
|
||||
$this->intimacy->add($marriage, $initIntimacy, IntimacySource::WEDDING_BONUS, "结婚戒指初始亲密度({$ringName})", true);
|
||||
}
|
||||
|
||||
// 如果有预先设置的婚礼(随求婚一起设定的),则将冻结金币转为正式扣除,并触发红包
|
||||
$ceremony = \App\Models\WeddingCeremony::where('marriage_id', $marriage->id)->where('status', 'pending')->first();
|
||||
if ($ceremony) {
|
||||
$weddingService = app(WeddingService::class);
|
||||
$weddingService->confirmCeremony($ceremony); // 解冻扣除,转为 immediate
|
||||
$weddingService->trigger($ceremony);
|
||||
broadcast(new \App\Events\WeddingCelebration($ceremony, $marriage));
|
||||
}
|
||||
});
|
||||
|
||||
return ['ok' => true, 'message' => '恭喜!你们已正式结婚!'];
|
||||
@@ -184,9 +212,16 @@ class MarriageService
|
||||
return ['ok' => false, 'message' => '无权操作此求婚。'];
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($marriage) {
|
||||
return DB::transaction(function () use ($marriage) {
|
||||
$marriage->update(['status' => 'rejected']);
|
||||
// 戒指消失(lost = 不退还)
|
||||
|
||||
// 检查是否有由于求婚冻结的婚礼金币,若有则退还
|
||||
$ceremony = \App\Models\WeddingCeremony::where('marriage_id', $marriage->id)->where('status', 'pending')->first();
|
||||
if ($ceremony) {
|
||||
app(WeddingService::class)->cancelAndRefund($ceremony);
|
||||
}
|
||||
|
||||
// 戒指拒绝后遗失
|
||||
UserPurchase::where('id', $marriage->ring_purchase_id)->update(['status' => 'lost']);
|
||||
// 记录戒指消失流水(amount=0,仅存档)
|
||||
if ($proposer = $marriage->user) {
|
||||
@@ -343,6 +378,12 @@ class MarriageService
|
||||
if ($proposer = $marriage->user) {
|
||||
$this->currency->change($proposer, 'gold', 0, CurrencySource::RING_LOST, "求婚超时,戒指消失({$marriage->ringItem?->name})");
|
||||
}
|
||||
|
||||
// 退还当时冻结的婚礼金币
|
||||
$ceremony = \App\Models\WeddingCeremony::where('marriage_id', $marriage->id)->where('status', 'pending')->first();
|
||||
if ($ceremony) {
|
||||
app(WeddingService::class)->cancelAndRefund($ceremony);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,66 @@ class WeddingService
|
||||
});
|
||||
}
|
||||
|
||||
// ──────────────────────────── 婚礼生命周期钩子 ───────────────────
|
||||
|
||||
/**
|
||||
* 将预先设置好的定时婚礼(因求婚冻结)转为即刻开始(解冻并记录消费)。
|
||||
*
|
||||
* @param WeddingCeremony $ceremony
|
||||
*/
|
||||
public function confirmCeremony(WeddingCeremony $ceremony): void
|
||||
{
|
||||
DB::transaction(function () use ($ceremony) {
|
||||
$marriage = $ceremony->marriage;
|
||||
$tierName = $ceremony->tier?->name ?? '婚礼';
|
||||
|
||||
if ($ceremony->ceremony_type === 'scheduled') {
|
||||
// 解除冻结,正式扣款记账
|
||||
if ($ceremony->groom_amount > 0) {
|
||||
$groom = clone $marriage->user;
|
||||
$marriage->user->decrement('frozen_jjb', $ceremony->groom_amount);
|
||||
$this->currency->change($groom, 'gold', 0, CurrencySource::WEDDING_ENV_SEND, "求婚成功,正式发红包({$tierName})");
|
||||
}
|
||||
if ($ceremony->partner_amount > 0) {
|
||||
$partner = clone $marriage->partner;
|
||||
$marriage->partner->decrement('frozen_jjb', $ceremony->partner_amount);
|
||||
$this->currency->change($partner, 'gold', 0, CurrencySource::WEDDING_ENV_SEND, "求婚成功,正式发红包({$tierName})");
|
||||
}
|
||||
|
||||
// 将类型转为即时开始
|
||||
$ceremony->update([
|
||||
'ceremony_type' => 'immediate',
|
||||
'ceremony_at' => now(),
|
||||
'expires_at' => now()->addHours($this->config->get('envelope_expire_hours', 24))
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销由于求婚设置的婚礼,并且解冻/退还因为该婚礼冻结的金币。
|
||||
*
|
||||
* @param WeddingCeremony $ceremony
|
||||
*/
|
||||
public function cancelAndRefund(WeddingCeremony $ceremony): void
|
||||
{
|
||||
DB::transaction(function () use ($ceremony) {
|
||||
$ceremony->update(['status' => 'cancelled']);
|
||||
|
||||
if ($ceremony->ceremony_type === 'scheduled') {
|
||||
$marriage = $ceremony->marriage;
|
||||
if ($ceremony->groom_amount > 0) {
|
||||
$marriage->user?->decrement('frozen_jjb', $ceremony->groom_amount);
|
||||
$marriage->user?->increment('jjb', $ceremony->groom_amount);
|
||||
}
|
||||
if ($ceremony->partner_amount > 0) {
|
||||
$marriage->partner?->decrement('frozen_jjb', $ceremony->partner_amount);
|
||||
$marriage->partner?->increment('jjb', $ceremony->partner_amount);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ──────────────────────────── 触发婚礼 ────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,19 +77,19 @@ export function initChat(roomId) {
|
||||
);
|
||||
})
|
||||
// ─── 婚姻系统:全局事件(广播给整个房间) ────────────────
|
||||
.listen("MarriageAccepted", (e) => {
|
||||
.listen(".marriage.accepted", (e) => {
|
||||
console.log("结婚公告:", e);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("chat:marriage-accepted", { detail: e }),
|
||||
);
|
||||
})
|
||||
.listen("MarriageDivorced", (e) => {
|
||||
.listen(".marriage.divorced", (e) => {
|
||||
console.log("离婚公告:", e);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("chat:marriage-divorced", { detail: e }),
|
||||
);
|
||||
})
|
||||
.listen("WeddingCelebration", (e) => {
|
||||
.listen(".wedding.celebration", (e) => {
|
||||
console.log("婚礼庆典:", e);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("chat:wedding-celebration", { detail: e }),
|
||||
|
||||
@@ -114,36 +114,46 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
{{-- ── 婚礼费用提示面板 ── --}}
|
||||
{{-- ── 婚礼档位选择与费用提示面板 ── --}}
|
||||
@php
|
||||
$minWedding = (int) \App\Models\WeddingTier::where('is_active', true)
|
||||
->orderBy('amount')
|
||||
->value('amount');
|
||||
$activeTiers = \App\Models\WeddingTier::where('is_active', true)->orderBy('amount')->get();
|
||||
@endphp
|
||||
@if ($minWedding > 0)
|
||||
<div style="margin-bottom:14px;">
|
||||
@php $canAfford = ($user->jjb >= $minWedding); @endphp
|
||||
<div style="margin-bottom:14px; text-align:left;">
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:8px;">
|
||||
<div
|
||||
style="border-radius:12px; padding:12px 14px; font-size:12px; line-height:1.7;
|
||||
background:{{ $canAfford ? '#f0fdf4' : '#fef2f2' }};
|
||||
border:1.5px solid {{ $canAfford ? '#bbf7d0' : '#fecaca' }};">
|
||||
<div
|
||||
style="font-weight:700; color:{{ $canAfford ? '#15803d' : '#dc2626' }}; margin-bottom:4px;">
|
||||
{{ $canAfford ? '✅ 您的金币可以举办婚礼' : '⚠️ 金币可能不足以举办婚礼' }}
|
||||
</div>
|
||||
<div style="color:#6b7280;">
|
||||
结婚最低花费:<strong style="color:#f43f5e;">🪙 {{ number_format($minWedding) }}</strong>
|
||||
金币
|
||||
</div>
|
||||
<div style="color:#{{ $canAfford ? '15803d' : 'dc2626' }};">
|
||||
当前余额:<strong>🪙 {{ number_format($user->jjb) }}</strong> 金币
|
||||
</div>
|
||||
@if (!$canAfford)
|
||||
<div style="color:#9ca3af; font-size:11px; margin-top:4px;">可先求婚,婚礼设置时再准备金币</div>
|
||||
@endif
|
||||
style="width:3px; height:14px; background:linear-gradient(#f59e0b,#d97706); border-radius:2px;">
|
||||
</div>
|
||||
<span style="font-size:12px; font-weight:700; color:#4b5563;">预设婚礼档位</span>
|
||||
</div>
|
||||
<select x-model="selectedTierId"
|
||||
style="width:100%; padding:8px 10px; border-radius:8px; border:1px solid #d1d5db; background:#fff; font-size:13px; color:#1f2937; margin-bottom:10px;">
|
||||
<option value="">(不举办撒红包婚礼)</option>
|
||||
<template x-for="tier in tiers" :key="tier.id">
|
||||
<option :value="tier.id" x-text="`${tier.icon} ${tier.name} (🪙 ${tier.amount})`">
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<div x-show="selectedTier" x-transition
|
||||
style="border-radius:12px; padding:12px 14px; font-size:12px; line-height:1.7; transition:all .2s;"
|
||||
:style="canAfford ? 'background:#f0fdf4; border:1.5px solid #bbf7d0;' :
|
||||
'background:#fef2f2; border:1.5px solid #fecaca;'">
|
||||
<div style="font-weight:700; margin-bottom:4px;"
|
||||
:style="canAfford ? 'color:#15803d' : 'color:#dc2626'"
|
||||
x-text="canAfford ? '✅ 您的金币足以预定该婚礼' : '⚠️ 金币不足,请降低档位或准备金币'">
|
||||
</div>
|
||||
<div style="color:#6b7280;">
|
||||
婚礼预冻结:<strong style="color:#f43f5e;"
|
||||
x-text="'🪙 ' + (selectedTier ? Number(selectedTier.amount).toLocaleString() : 0)"></strong>
|
||||
金币
|
||||
</div>
|
||||
<div :style="canAfford ? 'color:#15803d' : 'color:#dc2626'">
|
||||
当前余额:<strong>🪙 {{ number_format($user->jjb) }}</strong> 金币
|
||||
</div>
|
||||
<div style="color:#9ca3af; font-size:11px; margin-top:4px;">需男方独自承担预冻结金币,对方同意后即刻举行。被拒则全额退回!
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- 底部按钮:样式修复并参照大卡片弹窗 --}}
|
||||
<div style="display:flex; gap:12px;">
|
||||
@@ -154,9 +164,10 @@
|
||||
onmouseover="this.style.background='#e2e8f0'" onmouseout="this.style.background='#f1f5f9'">
|
||||
✕ 取消
|
||||
</button>
|
||||
<button x-on:click="doPropose()" :disabled="sending || !selectedRing || rings.length === 0"
|
||||
<button x-on:click="doPropose()"
|
||||
:disabled="sending || !selectedRing || rings.length === 0 || !canAfford"
|
||||
style="flex:1; padding:10px 0; border-radius:8px; font-size:13px; font-weight:bold; border:none; transition:all .2s;"
|
||||
:style="(sending || !selectedRing || rings.length === 0) ?
|
||||
:style="(sending || !selectedRing || rings.length === 0 || !canAfford) ?
|
||||
{ background: '#f1f5f9', color: '#94a3b8', cursor: 'not-allowed', boxShadow: 'none' } :
|
||||
{ background: 'linear-gradient(135deg,#be185d,#f43f5e,#ec4899)', color: '#fff',
|
||||
cursor: 'pointer', boxShadow: '0 4px 12px rgba(244,63,94,0.3)' }">
|
||||
@@ -457,10 +468,22 @@
|
||||
marriageId: null, // 当前对方婚姻/求婚记录 ID(accept/reject 用)
|
||||
rings: [],
|
||||
selectedRing: null,
|
||||
tiers: @json(\App\Models\WeddingTier::where('is_active', true)->orderBy('amount')->get()),
|
||||
selectedTierId: '',
|
||||
loading: false,
|
||||
sending: false,
|
||||
error: '',
|
||||
|
||||
get selectedTier() {
|
||||
if (!this.selectedTierId) return null;
|
||||
return this.tiers.find(t => t.id == this.selectedTierId);
|
||||
},
|
||||
|
||||
get canAfford() {
|
||||
const amount = this.selectedTier ? Number(this.selectedTier.amount) : 0;
|
||||
return window.chatContext.userJjb >= amount;
|
||||
},
|
||||
|
||||
async open(username) {
|
||||
this.targetUsername = username;
|
||||
this.selectedRing = null;
|
||||
@@ -520,6 +543,7 @@
|
||||
body: JSON.stringify({
|
||||
target_username: this.targetUsername,
|
||||
ring_purchase_id: this.selectedRing,
|
||||
wedding_tier_id: this.selectedTierId || null,
|
||||
room_id: window.chatContext.roomId
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user