增加婚姻 查看已婚列表

This commit is contained in:
2026-04-12 17:28:42 +08:00
parent 705af810a9
commit 2090250967
5 changed files with 263 additions and 22 deletions
+25 -2
View File
@@ -46,6 +46,29 @@ class MarriageController extends Controller
]);
}
/**
* 获取全站已婚列表(按亲密度或结婚时间排序)。
*/
public function list(Request $request): JsonResponse
{
$marriages = Marriage::query()
->where('status', 'married')
->with(['user:id,username,usersf,sex', 'partner:id,username,usersf,sex', 'ringItem:id,name,icon'])
->orderByDesc('intimacy')
->orderByDesc('married_at')
->paginate(20);
return response()->json([
'status' => 'success',
'data' => $marriages->items(),
'pagination' => [
'current_page' => $marriages->currentPage(),
'last_page' => $marriages->lastPage(),
'total' => $marriages->total(),
],
]);
}
/**
* 获取当前用户的婚姻状态(名片/用户列表用)。
*/
@@ -58,7 +81,7 @@ class MarriageController extends Controller
return response()->json(['married' => false]);
}
$marriage->load(['user:id,username,headface', 'partner:id,username,headface', 'ringItem:id,name,slug,icon']);
$marriage->load(['user:id,username,usersf', 'partner:id,username,usersf', 'ringItem:id,name,slug,icon']);
return response()->json([
'married' => $marriage->status === 'married',
@@ -95,7 +118,7 @@ class MarriageController extends Controller
->where(function ($q) use ($target) {
$q->where('user_id', $target->id)->orWhere('partner_id', $target->id);
})
->with(['user:id,username,headface', 'partner:id,username,headface', 'ringItem:id,name,icon'])
->with(['user:id,username,usersf', 'partner:id,username,usersf', 'ringItem:id,name,icon'])
->first();
if (! $marriage) {
+10
View File
@@ -24,6 +24,16 @@ class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* 追加到 JSON 序列化的属性。
*
* @var array<int, string>
*/
protected $appends = [
'headface',
'headface_url',
];
/**
* The attributes that are mass assignable.
*
+1 -1
View File
@@ -103,7 +103,7 @@ class MarriageService
$marriage = Marriage::create([
'user_id' => $proposer->id,
'partner_id' => $target->id,
'ring_item_id' => $ring->item_id,
'ring_item_id' => $ring->shop_item_id,
'ring_purchase_id' => $ring->id,
'status' => 'pending',
'proposed_at' => now(),
@@ -1829,31 +1829,151 @@ async function generateWechatBindCode() {
</script>
{{-- ═══════════ 婚姻状态弹窗 ═══════════ --}}
<style>
/* 婚姻弹窗选项卡按钮 */
.marriage-tab-btn {
background: rgba(255, 255, 255, .15);
border: none;
color: #fce7f3;
border-radius: 12px;
padding: 3px 12px;
font-size: 11px;
cursor: pointer;
font-weight: bold;
transition: all .2s;
}
.marriage-tab-btn.active {
background: #fff;
color: #be185d;
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
}
/* 已婚列表项样式 */
.married-list-item {
background: #fff;
border: 1px solid #fce7f3;
border-radius: 8px;
padding: 10px;
margin-bottom: 8px;
display: flex;
flex-direction: column;
gap: 8px;
transition: all .2s;
}
.married-list-item:hover {
border-color: #f43f5e;
box-shadow: 0 2px 8px rgba(190, 24, 93, .1);
}
.married-couple-info {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}
.married-user-box {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: 80px;
}
.married-user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border: 2px solid #fff;
box-shadow: 0 0 0 1px #fce7f3;
object-fit: cover;
}
.married-user-name {
font-size: 11px;
font-weight: bold;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.married-heart {
font-size: 20px;
color: #f43f5e;
animation: heartBeat 1.5s ease-in-out infinite;
}
@keyframes heartBeat {
0% { transform: scale(1); }
15% { transform: scale(1.2); }
30% { transform: scale(1); }
45% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.married-meta-info {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 10px;
color: #999;
padding-top: 6px;
border-top: 1px dashed #fce7f3;
}
.married-intimacy {
color: #be185d;
font-weight: bold;
}
</style>
<div id="marriage-status-modal"
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.5);
z-index:9999; justify-content:center; align-items:center;">
<div
style="background:#fff; border-radius:10px; width:360px; max-width:94vw;
style="background:#fff; border-radius:10px; width:380px; max-width:94vw;
box-shadow:0 12px 40px rgba(0,0,0,.3); overflow:hidden;
animation:gdSlideIn .18s ease; display:flex; flex-direction:column;">
animation:gdSlideIn .18s ease; display:flex; flex-direction:column; max-height: 85vh;">
{{-- 标题栏 --}}
<div
style="background:linear-gradient(135deg,#be185d,#f43f5e,#ec4899);
color:#fff; padding:12px 16px;
color:#fff; padding:10px 16px;
display:flex; align-items:center; justify-content:space-between;">
<span style="font-size:14px; font-weight:bold;">💍 我的婚姻</span>
<div style="display:flex; align-items:center; gap:12px;">
<span style="font-size:14px; font-weight:bold;">💍 婚姻系统</span>
<div style="display:flex; gap:6px;">
<button id="marriage-tabbtn-mine" class="marriage-tab-btn active" onclick="switchMarriageTab('mine')">我的婚姻</button>
<button id="marriage-tabbtn-list" class="marriage-tab-btn" onclick="switchMarriageTab('list')">已婚列表</button>
</div>
</div>
<span onclick="closeMarriageStatusModal()"
style="cursor:pointer; font-size:18px; opacity:.8; line-height:1;"></span>
</div>
{{-- 内容区(动态渲染) --}}
<div id="marriage-status-body" style="padding:16px; min-height:120px;">
<div style="text-align:center; color:#aaa; padding:30px 0; font-size:12px;">加载中…</div>
{{-- 我的婚姻 面板 --}}
<div id="marriage-view-mine" style="display:flex; flex-direction:column; flex:1; overflow:hidden;">
<div id="marriage-status-body" style="padding:16px; min-height:120px;">
<div style="text-align:center; color:#aaa; padding:30px 0; font-size:12px;">加载中…</div>
</div>
<div id="marriage-status-footer" style="padding:0 16px 16px; display:flex; gap:8px;"></div>
</div>
{{-- 底部操作区 --}}
<div id="marriage-status-footer" style="padding:0 16px 16px; display:flex; gap:8px;"></div>
{{-- 已婚列表 面板 --}}
<div id="marriage-view-list" style="display:none; flex-direction:column; flex:1; overflow:hidden; background: #fffafb;">
<div id="married-list-container" style="padding:12px; flex:1; overflow-y:auto;">
<div style="text-align:center; color:#aaa; padding:30px 0; font-size:12px;">加载中…</div>
</div>
<div id="married-list-pagination" style="padding:10px 16px; border-top:1px solid #fce7f3; display:flex; justify-content:space-between; align-items:center; font-size:11px;">
<button onclick="fetchMarriedList(marriedListPage - 1)" id="married-prev-btn" style="border:1px solid #fbcfe8; background:#fff; color:#be185d; padding:2px 8px; border-radius:4px; cursor:pointer;">上一页</button>
<span id="married-page-info" style="color:#be185d; font-weight:bold;">1 / 1</span>
<button onclick="fetchMarriedList(marriedListPage + 1)" id="married-next-btn" style="border:1px solid #fbcfe8; background:#fff; color:#be185d; padding:2px 8px; border-radius:4px; cursor:pointer;">下一页</button>
</div>
</div>
</div>
</div>
@@ -1866,27 +1986,113 @@ async function generateWechatBindCode() {
const CSRF = () => document.querySelector('meta[name="csrf-token"]')?.content ?? '';
const $ = id => document.getElementById(id);
window.marriedListPage = 1;
/** 打开弹窗并拉取状态 */
window.openMarriageStatusModal = function() {
$('marriage-status-modal').style.display = 'flex';
switchMarriageTab('mine');
};
/** 切换 Tab */
window.switchMarriageTab = function(tabName) {
$('marriage-tabbtn-mine').classList.toggle('active', tabName === 'mine');
$('marriage-tabbtn-list').classList.toggle('active', tabName === 'list');
$('marriage-view-mine').style.display = tabName === 'mine' ? 'flex' : 'none';
$('marriage-view-list').style.display = tabName === 'list' ? 'flex' : 'none';
if (tabName === 'mine') {
fetchMyMarriageStatus();
} else {
fetchMarriedList(1);
}
};
async function fetchMyMarriageStatus() {
$('marriage-status-body').innerHTML =
'<div style="text-align:center;color:#aaa;padding:30px 0;font-size:12px;">加载中…</div>';
$('marriage-status-footer').innerHTML = '';
fetch('/marriage/status', {
headers: {
Accept: 'application/json'
}
})
.then(r => r.json())
.then(renderMarriageStatus)
.catch(() => {
$('marriage-status-body').innerHTML =
'<div style="text-align:center;color:#e55;padding:30px 0;font-size:12px;">❌ 加载失败,请稍后重试</div>';
try {
const res = await fetch('/marriage/status', {
headers: { Accept: 'application/json' }
});
const data = await res.json();
renderMarriageStatus(data);
} catch (e) {
$('marriage-status-body').innerHTML =
'<div style="text-align:center;color:#e55;padding:30px 0;font-size:12px;">❌ 加载失败,请稍后重试</div>';
}
}
window.fetchMarriedList = async function(page) {
if (page < 1) return;
const container = $('married-list-container');
container.innerHTML = '<div style="text-align:center;color:#aaa;padding:30px 0;font-size:12px;">加载中…</div>';
try {
const res = await fetch(`/marriage/list?page=${page}`, {
headers: { Accept: 'application/json' }
});
const json = await res.json();
if (json.status === 'success') {
marriedListPage = json.pagination.current_page;
renderMarriedList(json.data, json.pagination);
}
} catch (e) {
container.innerHTML = '<div style="text-align:center;color:#e55;padding:30px 0;font-size:12px;">❌ 加载失败</div>';
}
};
function renderMarriedList(data, pagination) {
const container = $('married-list-container');
if (!data || data.length === 0) {
container.innerHTML = '<div style="text-align:center;color:#aaa;padding:40px 0;font-size:12px;">💖 暂无婚姻记录,快去寻找你的另一半吧</div>';
$('married-list-pagination').style.display = 'none';
return;
}
$('married-list-pagination').style.display = 'flex';
$('married-page-info').textContent = `${pagination.current_page} / ${pagination.last_page}`;
$('married-prev-btn').disabled = pagination.current_page <= 1;
$('married-prev-btn').style.opacity = pagination.current_page <= 1 ? 0.5 : 1;
$('married-next-btn').disabled = pagination.current_page >= pagination.last_page;
$('married-next-btn').style.opacity = pagination.current_page >= pagination.last_page ? 0.5 : 1;
container.innerHTML = data.map(m => {
const user = m.user;
const partner = m.partner;
const ring = m.ring_item;
const date = m.married_at ? m.married_at.substring(0, 10) : '—';
const userColor = (user && user.sex == 2) ? 'color:#e91e8c;' : '';
const partnerColor = (partner && partner.sex == 2) ? 'color:#e91e8c;' : '';
return `
<div class="married-list-item">
<div class="married-couple-info">
<div class="married-user-box" style="cursor:pointer;" onclick="openUserCard('${user.username}')">
<img src="${user.headface_url || '/images/headface/1.gif'}" class="married-user-avatar" onerror="this.src='/images/headface/1.gif'">
<span class="married-user-name" style="${userColor}" title="${user.username}">${user.username}</span>
</div>
<div class="married-heart">💖</div>
<div class="married-user-box" style="cursor:pointer;" onclick="openUserCard('${partner.username}')">
<img src="${partner.headface_url || '/images/headface/1.gif'}" class="married-user-avatar" onerror="this.src='/images/headface/1.gif'">
<span class="married-user-name" style="${partnerColor}" title="${partner.username}">${partner.username}</span>
</div>
</div>
<div class="married-meta-info">
<span>💍 ${ring ? ring.name : '无戒指'}</span>
<span>💞 <span class="married-intimacy">${Number(m.intimacy).toLocaleString()}</span></span>
<span>📅 ${date}</span>
</div>
</div>
`;
}).join('');
}
/** 关闭弹窗 */
window.closeMarriageStatusModal = function() {
$('marriage-status-modal').style.display = 'none';
+2
View File
@@ -92,6 +92,8 @@ Route::middleware(['chat.auth'])->group(function () {
// ── 婚姻系统(前台)──────────────────────────────────────────────
Route::prefix('marriage')->name('marriage.')->group(function () {
// 全站已婚列表
Route::get('/list', [\App\Http\Controllers\MarriageController::class, 'list'])->name('list');
// 查询当前用户婚姻状态
Route::get('/status', [\App\Http\Controllers\MarriageController::class, 'status'])->name('status');
// 查询目标用户婚姻信息(名片用)