经过这两天的测试结果,1.6.0来了!
一、优化说明:
- 因为存在积分明细和积分排行不一致的会员,同时经过与dabo大佬关于抽奖系统的确认,修改了积分的显示来源,使插件显示积分满足抽奖机制的判断机制。
- 新增快捷签到,可以不需要到签到页面完成签到,同时显示当日签到积分、连续签到天数和历史签到天数。
二、功能
1.未签到状态:
2.签到完成状态:
3.签到完成后签到页面:
4.签到完成后个人总结页面:
Tips:因为论坛的底层对积分不是实时更新,所以个人总结页面的积分实际在刚签到完后是少于签到页面的。静待积分同步即可。
三、插件代码:
// ==UserScript==
// @name MJJBOX 等级进度查看器(积分优化+签到版)
// @namespace http://tampermonkey.net/
// @version 1.6.0
// @description 依据官方默认等级规则显示进度,积分获取来源于抽奖机制同步,增加快捷签到。
// @author Aimei
// @match https://mjjbox.com/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(() => {
'use strict';
if (window !== window.top) return;
/* ========== 等级名称(与官方同步) ========== */
const levelNames = {
0: '青铜会员',
1: '白银会员',
2: '黄金会员',
3: '钻石会员',
4: '星曜会员'
};
/* ========== 官方默认晋级条件(完全同步) ========== */
const levelRequirements = {
1: {
topics_entered: 20,
posts_read: 30,
time_read: 60 * 60
},
2: {
days_visited: 15,
topics_entered: 20,
posts_read: 100,
time_read: 60 * 60,
posts_created: 1,
likes_received: 15,
likes_given: 20,
has_avatar: true,
has_bio: true
},
3: {
days_visited_in_100: 50,
topics_entered: 500,
posts_read: 9000,
posts_created_in_100: 10,
likes_received: 50,
likes_given: 100,
flagged_posts_ratio: 0.05
},
4: {
manual_promotion: true
}
};
/* ========== CSS(黑字 + 绿/红状态色) ========== */
const styles = `
.mjjbox-level-badge {
position: fixed; top: 20px; right: 20px;
width: 56px; height: 56px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 12px; color: #fff;
cursor: pointer; z-index: 9999;
box-shadow: 0 4px 20px rgba(0,0,0,.25);
transition: transform .3s,box-shadow .3s;
border: 2px solid #fff;
}
.mjjbox-level-badge:hover { transform: scale(1.1); box-shadow: 0 6px 25px rgba(0,0,0,.3); }
.mjjbox-level-badge.level-0 { background: linear-gradient(135deg,#9ca3af 0%,#6b7280 100%); }
.mjjbox-level-badge.level-1 { background: linear-gradient(135deg,#10b981 0%,#059669 100%); }
.mjjbox-level-badge.level-2 { background: linear-gradient(135deg,#3b82f6 0%,#1d4ed8 100%); }
.mjjbox-level-badge.level-3 { background: linear-gradient(135deg,#8b5cf6 0%,#7c3aed 100%); }
.mjjbox-level-badge.level-4 { background: linear-gradient(135deg,#f59e0b 0%,#d97706 100%); }
.mjjbox-level-modal {
position: fixed; inset: 0;
background: rgba(0,0,0,.42); z-index: 10000;
opacity: 0; visibility: hidden;
transition: opacity .35s,visibility .35s;
backdrop-filter: blur(6px);
}
.mjjbox-level-modal.show { opacity: 1; visibility: visible; }
.mjjbox-level-modal-content {
position: absolute;
background: #fff; border-radius: 12px;
width: 400px; max-width: 90vw; max-height: 80vh;
padding: 16px 16px 24px;
box-shadow: 0 16px 40px rgba(0,0,0,.3);
overflow-y: auto;
transform: scale(.9) translateY(-20px);
transition: transform .35s;
/* 滚动条样式 (Firefox) */
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f3f3f3;
}
/* WebKit 滚动条样式 (Chrome, Edge, Safari) */
.mjjbox-level-modal-content::-webkit-scrollbar {
width: 6px;
}
.mjjbox-level-modal-content::-webkit-scrollbar-track {
background: #f3f3f3;
border-radius: 3px;
}
.mjjbox-level-modal-content::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.mjjbox-level-modal-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 小屏适配:在更小的屏幕上进一步缩小 */
@media (max-width: 480px) {
.mjjbox-level-modal-content {
width: 320px;
max-width: 92vw;
padding: 14px 14px 22px;
border-radius: 10px;
}
.mjjbox-level-title { font-size: 20px; }
.mjjbox-level-subtitle, .mjjbox-level-score { font-size: 14px; }
.mjjbox-progress-label { font-size: 14px; }
.mjjbox-progress-required, .mjjbox-progress-tooltip { font-size: 12px; }
.mjjbox-close-btn { font-size: 24px; top: 8px; right: 12px; }
}
.mjjbox-close-btn {
position: absolute; top: 10px; right: 14px;
background: none; border: none;
font-size: 26px; cursor: pointer; color: #000;
}
.mjjbox-level-header { text-align: center; }
.mjjbox-level-title { margin: 0; font-size: 20px; font-weight: 700; color: #000; }
.mjjbox-level-subtitle, .mjjbox-level-score { margin: 4px 0 0; font-size: 14px; color: #000; }
.mjjbox-progress-section h3 { margin: 0 0: 14px; font-size: 16px; color: #000; }
.mjjbox-progress-item { margin-bottom: 10px; }
.mjjbox-progress-label { display: block; font-weight: 600; margin-bottom: 4px; color: #000; font-size: 14px; }
.mjjbox-progress-bar-container { display: flex; align-items: center; gap: 6px; }
.mjjbox-progress-bar { flex: 1; height: 6px; background: #f3f4f6; border-radius: 3px; overflow: hidden; }
.mjjbox-progress-fill { height: 100%; background: linear-gradient(90deg,#10b981 0%,#34d399 100%); transition: width .4s; }
.mjjbox-progress-fill.incomplete { background: linear-gradient(90deg,#f87171 0%,#fca5a5 100%); }
.mjjbox-progress-required, .mjjbox-progress-tooltip { font-size: 12px; color: #000; }
.mjjbox-progress-undone { color: #ef4444; }
.mjjbox-progress-done { color: #10b981; }
/* 签到区域样式 */
.mjjbox-checkin-section {
margin: 20px 0 10px;
padding: 15px;
background: #f8fafc;
border-radius: 8px;
text-align: center;
}
.mjjbox-checkin-status {
font-size: 14px;
color: #000;
margin-bottom: 12px;
}
.mjjbox-checkin-button {
background: linear-gradient(135deg,#3b82f6 0%,#1d4ed8 100%);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: opacity 0.2s;
}
.mjjbox-checkin-button:hover {
opacity: 0.9;
}
.mjjbox-checkin-button:disabled {
background: #9ca3af;
cursor: not-allowed;
}
`;
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
/* ========== 通用小工具 ========== */
const getCurrentUsername = () => {
try {
if (typeof Discourse !== 'undefined' && Discourse.User && Discourse.User.current()) {
return Discourse.User.current()?.username || null;
}
} catch (e) {
console.error('获取用户名失败:', e);
}
return null;
};
const showNotification = (msg, type = 'info', dur = 3000) => {
const n = document.createElement('div');
n.style.cssText = `
position: fixed; top: 90px; right: 24px;
padding: 12px 18px; border-radius: 8px;
color: #fff; font-weight: 600; z-index: 10001;
opacity: 0; transform: translateX(120%);
transition: all .3s;
background: ${type === 'error' ? '#ef4444' : '#10b981'};
`;
n.textContent = msg;
document.body.appendChild(n);
setTimeout(() => { n.style.opacity = '1'; n.style.transform = 'translateX(0)'; }, 100);
setTimeout(() => { n.style.opacity = '0'; n.style.transform = 'translateX(120%)'; setTimeout(() => n.remove(), 300); }, dur);
};
/* ========== 签到功能 ========== */
const checkCheckinStatus = (callback) => {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://mjjbox.com/checkin.json',
onload: (response) => {
if (response.status === 200) {
try {
const data = JSON.parse(response.responseText);
console.log('签到状态响应:', data);
callback(data);
} catch (e) {
console.error('解析签到状态失败:', e);
callback(null);
}
} else {
console.warn('获取签到状态失败:', response.status);
callback(null);
}
},
onerror: (error) => {
console.error('获取签到状态错误:', error);
callback(null);
}
});
};
const performCheckin = (callback) => {
// 获取CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
if (!csrfToken) {
console.error('无法获取CSRF token');
callback(null, '无法获取安全令牌,请刷新页面');
return;
}
console.log('发送签到请求,CSRF Token:', csrfToken.substring(0, 10) + '...');
// 使用正确的请求格式
GM_xmlhttpRequest({
method: 'POST',
url: 'https://mjjbox.com/checkin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRF-Token': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
data: `authenticity_token=${encodeURIComponent(csrfToken)}`,
onload: (response) => {
console.log('签到响应状态:', response.status);
if (response.status === 200) {
try {
const data = JSON.parse(response.responseText);
console.log('签到响应数据:', data);
// 根据实际响应格式处理
if (data.success) {
callback(data, null);
} else {
let errorMsg = data.errors?.[0] || data.error || '签到失败';
if (errorMsg.includes('already checked in')) {
errorMsg = '您今天已经签到了';
}
callback(null, errorMsg);
}
} catch (e) {
console.error('解析JSON响应失败:', e, '响应内容:', response.responseText.substring(0, 200));
callback(null, '签到响应格式异常');
}
} else if (response.status === 403) {
callback(null, '权限不足,请重新登录');
} else if (response.status === 422) {
callback(null, '安全验证失败,请刷新页面');
} else {
callback(null, `签到失败,状态码: ${response.status}`);
}
},
onerror: (error) => {
console.error('签到请求错误:', error);
callback(null, '网络错误,请稍后重试');
}
});
};
/* ========== 等级徽章 ========== */
const createLevelBadge = () => {
const badge = document.createElement('div');
badge.className = 'mjjbox-level-badge';
badge.textContent = 'LV ?';
badge.title = '点击查看等级进度和签到';
badge.addEventListener('click', fetchUserLevel);
document.body.appendChild(badge);
return badge;
};
const updateLevelBadge = (level, username) => {
const badge = document.querySelector('.mjjbox-level-badge');
if (!badge) return;
badge.textContent = `LV ${level}`;
badge.className = `mjjbox-level-badge level-${level}`;
badge.title = `${username} - ${levelNames[level] || '未知等级'}(点击查看详情和签到)`;
};
/* ========== 拉取数据 ========== */
const fetchUserLevel = () => {
const username = getCurrentUsername();
if (!username) return showNotification('❌ 无法获取当前用户名', 'error');
let summaryData = null;
let userData = null;
let checkinData = null;
let done = 0;
const checkDone = () => { done++; if (done === 3) processUserData(summaryData, userData, checkinData, username); };
GM_xmlhttpRequest({
method: 'GET',
url: `https://mjjbox.com/u/${username}/summary.json`,
timeout: 15000,
headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
onload: resp => { if (resp.status === 200) { try { summaryData = JSON.parse(resp.responseText); } catch {} } checkDone(); },
onerror: checkDone
});
GM_xmlhttpRequest({
method: 'GET',
url: `https://mjjbox.com/u/${username}.json`,
timeout: 15000,
headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
onload: resp => { if (resp.status === 200) { try { userData = JSON.parse(resp.responseText); } catch {} } checkDone(); },
onerror: checkDone
});
// 获取签到状态
checkCheckinStatus((data) => {
checkinData = data;
checkDone();
});
};
/* ========== 处理数据 ========== */
const processUserData = (summaryData, userData, checkinData, username) => {
if (!summaryData || !userData) return showNotification('❌ 获取用户数据失败', 'error');
const user = userData.user || summaryData.users?.[0];
const userSummary = summaryData.user_summary;
if (user && typeof user.trust_level === 'number') {
const level = user.trust_level;
updateLevelBadge(level, username);
showNotification(`✅ 等级信息获取成功:LV${level} ${levelNames[level]}`, 'success', 2000);
const modal = createLevelModal({
level,
username,
userData: { user, userSummary, gamification_score: user.gamification_score || 0 },
checkinData
});
document.body.appendChild(modal);
setTimeout(() => modal.classList.add('show'), 10);
} else {
showNotification('❌ 无法解析用户等级信息', 'error');
}
};
/* ========== 计算进度(完全按官方字段) ========== */
const calculateLevelProgress = (currentLevel, userData) => {
if (!userData?.userSummary) return { items: [], achievedCount: 0, totalCount: 0 };
const us = userData.userSummary;
const u = userData.user;
const next = currentLevel + 1;
const req = levelRequirements[next];
if (!req) return { items: [{ label: '升级方式', current: '联系管理员', required: '手动提升', isMet: false }], achievedCount: 0, totalCount: 1 };
const items = [];
let achieved = 0;
const add = (label, current, required, isTime = false) => {
const met = current >= required;
items.push({ label, current, required, isMet: met, percentage: Math.min((current / required) * 100, 100), isTime });
if (met) achieved++;
};
/* 新增:100 天内访问天数(官方字段目前需自行计算,这里先用 days_visited 近似) */
const daysVisited100 = us.days_visited || 0;
if (req.topics_entered !== undefined) add('阅读主题数', us.topics_entered || 0, req.topics_entered);
if (req.posts_read !== undefined) add('阅读帖子数', us.posts_read_count || 0, req.posts_read);
if (req.time_read !== undefined) add('总阅读时间(分钟)', Math.floor((us.time_read || 0) / 60), Math.floor(req.time_read / 60), true);
if (req.days_visited !== undefined) add('累计访问天数', us.days_visited || 0, req.days_visited);
if (req.days_visited_in_100 !== undefined) add('过去100天内访问天数', daysVisited100, req.days_visited_in_100);
if (req.posts_created !== undefined) add('累计发帖数', us.topic_count || 0, req.posts_created);
if (req.posts_created_in_100 !== undefined) add('过去100天内发帖/回复数', (us.topic_count || 0) + (us.post_count || 0), req.posts_created_in_100);
if (req.likes_received !== undefined) add('收到赞数', us.likes_received || 0, req.likes_received);
if (req.likes_given !== undefined) add('送出赞数', us.likes_given || 0, req.likes_given);
/* 头像 & 个人简介 */
if (req.has_avatar !== undefined) {
const has = !!(u.avatar_template && !u.avatar_template.includes('letter_avatar') && !u.avatar_template.includes('system_avatar'));
items.push({ label: '已上传头像', current: has ? '已上传' : '未上传', required: '已上传', isMet: has, isBoolean: true });
if (has) achieved++;
}
if (req.has_bio !== undefined) {
const has = !!(u.bio_raw && u.bio_raw.trim());
items.push({ label: '已填写基本资料', current: has ? '已填写' : '未填写', required: '已填写', isMet: has, isBoolean: true });
if (has) achieved++;
}
/* 被隐藏/举报比例(官方无直接字段,这里留 0 占位) */
if (req.flagged_posts_ratio !== undefined) {
const flaggedRatio = 0; // 暂无 API,直接给 0
items.push({ label: '被举报/隐藏帖子比例', current: `${(flaggedRatio * 100).toFixed(1)}%`, required: `${(req.flagged_posts_ratio * 100).toFixed(0)}% 以内`, isMet: flaggedRatio <= req.flagged_posts_ratio });
if (flaggedRatio <= req.flagged_posts_ratio) achieved++;
}
items.sort((a, b) => (a.isMet ? 1 : -1));
return { items, achievedCount: achieved, totalCount: items.length };
};
/* ========== 弹窗(位置固定在徽章下方) ========== */
const createLevelModal = ({ level, username, userData, checkinData }) => {
const modal = document.createElement('div');
modal.className = 'mjjbox-level-modal';
const progress = calculateLevelProgress(level, userData);
const currentName = levelNames[level] || '未知等级';
const content = document.createElement('div');
content.className = 'mjjbox-level-modal-content';
// 计算位置:徽章下方
const badgeRect = document.querySelector('.mjjbox-level-badge').getBoundingClientRect();
let top = badgeRect.bottom + 18;
let right = window.innerWidth - badgeRect.right;
if (top + 500 > window.innerHeight) top = badgeRect.top - 500 - 18;
if (right - 420 < 0) right = 10;
content.style.top = `${top}px`;
content.style.right = `${right}px`;
// 处理签到状态
let checkinStatusText = '未知';
let isCheckedIn = false;
let pointsEarned = 0;
let consecutiveDays = 0;
let totalCheckins = 0;
// 始终使用 userData 中的 gamification_score
let currentPoints = userData.gamification_score || 0;
if (checkinData) {
isCheckedIn = checkinData.today_checked_in || false;
pointsEarned = checkinData.checkin_history && checkinData.checkin_history[0] ? checkinData.checkin_history[0].points_earned : 0;
consecutiveDays = checkinData.consecutive_days || 0;
totalCheckins = checkinData.user_checkin_count || 0;
checkinStatusText = isCheckedIn ?
`今日已签到,获得 ${pointsEarned} 积分` :
'今日未签到';
}
content.innerHTML = `
<button class="mjjbox-close-btn">×</button>
<div class="mjjbox-level-header">
<h2 class="mjjbox-level-title">${username}</h2>
<p class="mjjbox-level-subtitle">当前等级:LV${level} ${currentName}</p>
<p class="mjjbox-level-score">当前积分:${currentPoints}</p>
</div>
<div class="mjjbox-checkin-section">
<div class="mjjbox-checkin-status">${checkinStatusText}</div>
<div>已连续签到 ${consecutiveDays} 天,总共签到 ${totalCheckins} 次</div>
<button class="mjjbox-checkin-button" ${isCheckedIn ? 'disabled' : ''}>
${isCheckedIn ? '今日已签到' : '立即签到'}
</button>
</div>
<div class="mjjbox-progress-section">
<h3>${level >= 4 ? '已达到最高等级' : `晋级到 LV${level + 1} ${levelNames[level + 1]} 的进度(${progress.achievedCount}/${progress.totalCount})`}</h3>
${progress.items.map(item => {
const cur = item.isTime ? `${item.current} 分钟` : item.current;
const need = item.isTime ? `${item.required} 分钟` : item.required;
const met = item.isMet;
const icon = met ? '✅' : '❌';
return `
<div class="mjjbox-progress-item">
<span class="mjjbox-progress-label">${item.label}</span>
<div class="mjjbox-progress-bar-container">
<div class="mjjbox-progress-bar">
<div class="mjjbox-progress-fill ${met ? '' : 'incomplete'}" style="width: ${item.percentage || 0}%"></div>
</div>
<span class="mjjbox-progress-required ${met ? '' : 'mjjbox-progress-undone'}">
需要:${item.isBoolean ? item.required : need} ${icon}
</span>
</div>
<div class="mjjbox-progress-tooltip">
当前:<span class="${met ? 'mjjbox-progress-done' : 'mjjbox-progress-undone'}">
${item.isBoolean ? item.current : cur} ${icon}
</span>
</div>
</div>`;
}).join('')}
${progress.items.length === 0 ? '<div style="text-align:center;padding:20px;color:#000;">🎉 恭喜!您已达到最高等级!</div>' : ''}
</div>
`;
modal.appendChild(content);
// 添加签到按钮事件
const checkinButton = content.querySelector('.mjjbox-checkin-button');
if (checkinButton && !isCheckedIn) {
checkinButton.addEventListener('click', () => {
checkinButton.disabled = true;
checkinButton.textContent = '签到中...';
// 先检查登录状态
const currentUser = getCurrentUsername();
if (!currentUser) {
showNotification('❌ 请先登录后再签到', 'error');
checkinButton.disabled = false;
checkinButton.textContent = '立即签到';
return;
}
performCheckin((result, error) => {
if (result && result.success) {
const points = result.points_earned || 10;
const newConsecutiveDays = result.consecutive_days || consecutiveDays + 1;
const newTotalCheckins = totalCheckins + 1;
// 不再使用签到返回的积分,而是重新获取用户数据
showNotification(`✅ ${result.message || `签到成功!获得 ${points} 积分`}`, 'success');
// 立即更新UI状态
const statusElement = content.querySelector('.mjjbox-checkin-status');
statusElement.textContent = `今日已签到,获得 ${points} 积分`;
// 更新连续签到和总签到次数
const consecutiveElement = content.querySelector('.mjjbox-checkin-section > div:nth-child(2)');
if (consecutiveElement) {
consecutiveElement.textContent = `已连续签到 ${newConsecutiveDays} 天,总共签到 ${newTotalCheckins} 次`;
}
checkinButton.textContent = '今日已签到';
checkinButton.disabled = true;
// 重新获取用户数据以更新积分显示
setTimeout(() => {
const username = getCurrentUsername();
if (username) {
GM_xmlhttpRequest({
method: 'GET',
url: `https://mjjbox.com/u/${username}.json`,
onload: (resp) => {
if (resp.status === 200) {
try {
const newUserData = JSON.parse(resp.responseText);
const newPoints = newUserData.user?.gamification_score || currentPoints;
// 更新积分显示为最新的 gamification_score
const scoreElement = content.querySelector('.mjjbox-level-score');
scoreElement.textContent = `当前积分:${newPoints}`;
showNotification('✅ 积分已更新', 'success', 2000);
} catch (e) {
console.error('解析用户数据失败:', e);
}
}
}
});
}
}, 500);
} else {
let errorMsg = error || '签到失败,请稍后重试';
if (error && (error.includes('already checked in') || error.includes('已经签到了'))) {
errorMsg = '您今天已经签到了';
checkinButton.textContent = '今日已签到';
checkinButton.disabled = true;
const statusElement = content.querySelector('.mjjbox-checkin-status');
statusElement.textContent = `今日已签到`;
} else if (error && error.includes('请先登录')) {
errorMsg = '请先登录后再签到';
checkinButton.disabled = false;
checkinButton.textContent = '立即签到';
} else {
checkinButton.disabled = false;
checkinButton.textContent = '立即签到';
}
showNotification(`❌ ${errorMsg}`, 'error');
}
});
});
}
content.querySelector('.mjjbox-close-btn').addEventListener('click', () => {
modal.classList.remove('show');
setTimeout(() => modal.remove(), 300);
});
modal.addEventListener('click', e => {
if (e.target === modal) {
modal.classList.remove('show');
setTimeout(() => modal.remove(), 300);
}
});
document.addEventListener('keydown', function esc(e) {
if (e.key === 'Escape') {
modal.classList.remove('show');
setTimeout(() => modal.remove(), 300);
document.removeEventListener('keydown', esc);
}
});
return modal;
};
/* ========== 启动 ========== */
const init = () => {
if (document.readyState === 'loading') return document.addEventListener('DOMContentLoaded', init);
createLevelBadge();
};
init();
})();
四、如何安装?
1.安装油猴
- Chrome浏览器: 点击安装 Tampermonkey
- Firefox浏览器: 点击安装 Tampermonkey
- Edge浏览器: 点击安装 Tampermonkey
2.赋予开发者权限
在管理拓展页面找到开发模式,各个浏览器叫法不同:
![]()
- Chrome浏览器: 开发者模式
- Edge浏览器: 开发人员模式
3.安装脚本
- 复制
插件代码文件的全部内容 - 点击浏览器右上角的 Tampermonkey 图标
- 选择"添加新脚本"
- 删除默认内容,粘贴复制脚本代码
- 选择
文件下的保存按钮即可保存。 - 返回MJJBOX论坛,右上角将会出现一个圆形徽章,点击即可使用相关功能。
五、其他说明
由于权限等问题,所有进度插件都卡在升钻石的过去100天访问这个数据上,由于暂时拿不到这个真实数据,所以如果你发现你条件都满足,但无法升级钻石,可能就是这个数据的异常,只要正常访问,基本后面都能升级。



