增加婚姻 查看已婚列表
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
// 查询目标用户婚姻信息(名片用)
|
||||
|
||||
Reference in New Issue
Block a user