增加婚姻 查看已婚列表
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]);
|
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([
|
return response()->json([
|
||||||
'married' => $marriage->status === 'married',
|
'married' => $marriage->status === 'married',
|
||||||
@@ -95,7 +118,7 @@ class MarriageController extends Controller
|
|||||||
->where(function ($q) use ($target) {
|
->where(function ($q) use ($target) {
|
||||||
$q->where('user_id', $target->id)->orWhere('partner_id', $target->id);
|
$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();
|
->first();
|
||||||
|
|
||||||
if (! $marriage) {
|
if (! $marriage) {
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
use HasFactory, Notifiable;
|
use HasFactory, Notifiable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加到 JSON 序列化的属性。
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $appends = [
|
||||||
|
'headface',
|
||||||
|
'headface_url',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class MarriageService
|
|||||||
$marriage = Marriage::create([
|
$marriage = Marriage::create([
|
||||||
'user_id' => $proposer->id,
|
'user_id' => $proposer->id,
|
||||||
'partner_id' => $target->id,
|
'partner_id' => $target->id,
|
||||||
'ring_item_id' => $ring->item_id,
|
'ring_item_id' => $ring->shop_item_id,
|
||||||
'ring_purchase_id' => $ring->id,
|
'ring_purchase_id' => $ring->id,
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
'proposed_at' => now(),
|
'proposed_at' => now(),
|
||||||
|
|||||||
@@ -1829,32 +1829,152 @@ async function generateWechatBindCode() {
|
|||||||
</script>
|
</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"
|
<div id="marriage-status-modal"
|
||||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.5);
|
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.5);
|
||||||
z-index:9999; justify-content:center; align-items:center;">
|
z-index:9999; justify-content:center; align-items:center;">
|
||||||
<div
|
<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;
|
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
|
<div
|
||||||
style="background:linear-gradient(135deg,#be185d,#f43f5e,#ec4899);
|
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;">
|
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()"
|
<span onclick="closeMarriageStatusModal()"
|
||||||
style="cursor:pointer; font-size:18px; opacity:.8; line-height:1;">✕</span>
|
style="cursor:pointer; font-size:18px; opacity:.8; line-height:1;">✕</span>
|
||||||
</div>
|
</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 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 style="text-align:center; color:#aaa; padding:30px 0; font-size:12px;">加载中…</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- 底部操作区 --}}
|
|
||||||
<div id="marriage-status-footer" style="padding:0 16px 16px; display:flex; gap:8px;"></div>
|
<div id="marriage-status-footer" style="padding:0 16px 16px; display:flex; gap:8px;"></div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -1867,26 +1987,112 @@ async function generateWechatBindCode() {
|
|||||||
const CSRF = () => document.querySelector('meta[name="csrf-token"]')?.content ?? '';
|
const CSRF = () => document.querySelector('meta[name="csrf-token"]')?.content ?? '';
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
|
|
||||||
|
window.marriedListPage = 1;
|
||||||
|
|
||||||
/** 打开弹窗并拉取状态 */
|
/** 打开弹窗并拉取状态 */
|
||||||
window.openMarriageStatusModal = function() {
|
window.openMarriageStatusModal = function() {
|
||||||
$('marriage-status-modal').style.display = 'flex';
|
$('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 =
|
$('marriage-status-body').innerHTML =
|
||||||
'<div style="text-align:center;color:#aaa;padding:30px 0;font-size:12px;">加载中…</div>';
|
'<div style="text-align:center;color:#aaa;padding:30px 0;font-size:12px;">加载中…</div>';
|
||||||
$('marriage-status-footer').innerHTML = '';
|
$('marriage-status-footer').innerHTML = '';
|
||||||
|
|
||||||
fetch('/marriage/status', {
|
try {
|
||||||
headers: {
|
const res = await fetch('/marriage/status', {
|
||||||
Accept: 'application/json'
|
headers: { Accept: 'application/json' }
|
||||||
}
|
});
|
||||||
})
|
const data = await res.json();
|
||||||
.then(r => r.json())
|
renderMarriageStatus(data);
|
||||||
.then(renderMarriageStatus)
|
} catch (e) {
|
||||||
.catch(() => {
|
|
||||||
$('marriage-status-body').innerHTML =
|
$('marriage-status-body').innerHTML =
|
||||||
'<div style="text-align:center;color:#e55;padding:30px 0;font-size:12px;">❌ 加载失败,请稍后重试</div>';
|
'<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() {
|
window.closeMarriageStatusModal = function() {
|
||||||
$('marriage-status-modal').style.display = 'none';
|
$('marriage-status-modal').style.display = 'none';
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ Route::middleware(['chat.auth'])->group(function () {
|
|||||||
|
|
||||||
// ── 婚姻系统(前台)──────────────────────────────────────────────
|
// ── 婚姻系统(前台)──────────────────────────────────────────────
|
||||||
Route::prefix('marriage')->name('marriage.')->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');
|
Route::get('/status', [\App\Http\Controllers\MarriageController::class, 'status'])->name('status');
|
||||||
// 查询目标用户婚姻信息(名片用)
|
// 查询目标用户婚姻信息(名片用)
|
||||||
|
|||||||
Reference in New Issue
Block a user