mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 20:01:04 +00:00
feat(mbti): 替换 mbti 简单实现 sbti acgti 功能
This commit is contained in:
@@ -621,6 +621,34 @@ class ConfigManager:
|
||||
self._ensure_group("basic")["enable_analysis_reply"] = enabled
|
||||
self.config.save_config()
|
||||
|
||||
def get_profile_display_mode(self) -> str:
|
||||
"""获取人格标签展示模式。"""
|
||||
mode = str(self._get_group("basic").get("profile_display_mode", "mbti")).lower()
|
||||
if mode not in {"mbti", "sbti", "acgti"}:
|
||||
return "mbti"
|
||||
return mode
|
||||
|
||||
def get_profile_image_opacity(self) -> float:
|
||||
"""获取人格背景图透明度。"""
|
||||
value = self._get_group("basic").get("profile_image_opacity", 0.12)
|
||||
try:
|
||||
return max(0.0, min(1.0, float(value)))
|
||||
except (TypeError, ValueError):
|
||||
return 0.12
|
||||
|
||||
def get_profile_image_size_mode(self) -> str:
|
||||
"""获取人格背景图尺寸模式。"""
|
||||
mode = str(
|
||||
self._get_group("basic").get("profile_image_size_mode", "contain")
|
||||
).lower()
|
||||
if mode not in {"contain", "cover"}:
|
||||
return "contain"
|
||||
return mode
|
||||
|
||||
def get_profile_mapping_config(self) -> str:
|
||||
"""获取人格映射配置(JSON 文本)。"""
|
||||
return str(self._get_group("basic").get("profile_mapping_config", "")).strip()
|
||||
|
||||
# ========== 群文件/群相册上传配置 ==========
|
||||
|
||||
def get_enable_group_file_upload(self) -> bool:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from dataclasses import asdict, is_dataclass
|
||||
@@ -28,6 +29,63 @@ from .templates import HTMLTemplates
|
||||
MAX_CONCURRENT_DOWNLOADS = 10
|
||||
AVATAR_CACHE_EXPIRE_TIME = 259200
|
||||
|
||||
DEFAULT_PROFILE_MAPPING = {
|
||||
"mbti": {
|
||||
"INTJ": {"code": "INTJ", "name_zh": "建筑师"},
|
||||
"INTP": {"code": "INTP", "name_zh": "逻辑学家"},
|
||||
"ENTJ": {"code": "ENTJ", "name_zh": "指挥官"},
|
||||
"ENTP": {"code": "ENTP", "name_zh": "辩论家"},
|
||||
"INFJ": {"code": "INFJ", "name_zh": "提倡者"},
|
||||
"INFP": {"code": "INFP", "name_zh": "调停者"},
|
||||
"ENFJ": {"code": "ENFJ", "name_zh": "主人公"},
|
||||
"ENFP": {"code": "ENFP", "name_zh": "竞选者"},
|
||||
"ISTJ": {"code": "ISTJ", "name_zh": "物流师"},
|
||||
"ISFJ": {"code": "ISFJ", "name_zh": "守卫者"},
|
||||
"ESTJ": {"code": "ESTJ", "name_zh": "总经理"},
|
||||
"ESTP": {"code": "ESTP", "name_zh": "企业家"},
|
||||
"ISTP": {"code": "ISTP", "name_zh": "鉴赏家"},
|
||||
"ISFP": {"code": "ISFP", "name_zh": "探险家"},
|
||||
"ESFJ": {"code": "ESFJ", "name_zh": "执政官"},
|
||||
"ESFP": {"code": "ESFP", "name_zh": "表演者"},
|
||||
},
|
||||
"sbti": {
|
||||
"INTJ": {"code": "CTRL", "name_zh": "拿捏者", "asset_code": "CTRL"},
|
||||
"INTP": {"code": "THIN-K", "name_zh": "思考者", "asset_code": "THIN-K"},
|
||||
"ENTJ": {"code": "BOSS", "name_zh": "领导者", "asset_code": "BOSS"},
|
||||
"ENTP": {"code": "JOKE-R", "name_zh": "小丑", "asset_code": "JOKE-R"},
|
||||
"INFJ": {"code": "LOVE-R", "name_zh": "多情者", "asset_code": "LOVE-R"},
|
||||
"INFP": {"code": "SOLO", "name_zh": "孤儿", "asset_code": "SOLO"},
|
||||
"ENFJ": {"code": "THAN-K", "name_zh": "感恩者", "asset_code": "THAN-K"},
|
||||
"ENFP": {"code": "GOGO", "name_zh": "行者", "asset_code": "GOGO"},
|
||||
"ISTJ": {"code": "OH-NO", "name_zh": "哦不人", "asset_code": "OH-NO"},
|
||||
"ISTP": {"code": "POOR", "name_zh": "贫困者", "asset_code": "POOR"},
|
||||
"ESTJ": {"code": "SHIT", "name_zh": "愤世者", "asset_code": "SHIT"},
|
||||
"ESTP": {"code": "WOC!", "name_zh": "握草人", "asset_code": "WOC"},
|
||||
"ISFJ": {"code": "MUM", "name_zh": "妈妈", "asset_code": "MUM"},
|
||||
"ISFP": {"code": "MALO", "name_zh": "吗喽", "asset_code": "MALO"},
|
||||
"ESFJ": {"code": "ATM-er", "name_zh": "送钱者", "asset_code": "ATM-er"},
|
||||
"ESFP": {"code": "SEXY", "name_zh": "尤物", "asset_code": "SEXY"},
|
||||
},
|
||||
"acgti": {
|
||||
"INTJ": {"code": "MRTS-X", "name_zh": "Mortis"},
|
||||
"INTP": {"code": "KNAN", "name_zh": "江户川柯南"},
|
||||
"ENTJ": {"code": "SAKI", "name_zh": "丰川祥子"},
|
||||
"ENTP": {"code": "CHKA", "name_zh": "藤原千花"},
|
||||
"INFJ": {"code": "DLRS", "name_zh": "三角初华"},
|
||||
"INFP": {"code": "BCHI", "name_zh": "后藤一里"},
|
||||
"ENFJ": {"code": "YCYO", "name_zh": "月见八千代"},
|
||||
"ENFP": {"code": "HTMK", "name_zh": "初音未来"},
|
||||
"ISTJ": {"code": "MRTS", "name_zh": "若叶睦"},
|
||||
"ISTP": {"code": "AYRE", "name_zh": "绫波丽"},
|
||||
"ESTJ": {"code": "MIKT", "name_zh": "御坂美琴"},
|
||||
"ESTP": {"code": "ASKA", "name_zh": "明日香"},
|
||||
"ISFJ": {"code": "SOYO", "name_zh": "长崎爽世"},
|
||||
"ISFP": {"code": "LTYI", "name_zh": "洛天依"},
|
||||
"ESFJ": {"code": "ANON", "name_zh": "千早爱音"},
|
||||
"ESFP": {"code": "FRNA", "name_zh": "芙宁娜"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ReportGenerator(IReportGenerator):
|
||||
"""报告生成器"""
|
||||
@@ -51,6 +109,148 @@ class ReportGenerator(IReportGenerator):
|
||||
MAX_CONCURRENT_DOWNLOADS
|
||||
)
|
||||
self._avatar_session = None
|
||||
self._profile_asset_manifest = self._load_profile_asset_manifest()
|
||||
|
||||
def _load_profile_asset_manifest(self) -> dict[str, dict]:
|
||||
"""加载人格资源清单。"""
|
||||
manifest_path = (
|
||||
Path(__file__).resolve().parents[3]
|
||||
/ "assets"
|
||||
/ "profile_assets"
|
||||
/ "manifest.json"
|
||||
)
|
||||
if not manifest_path.exists():
|
||||
logger.warning(f"人格资源清单不存在: {manifest_path}")
|
||||
return {"sbti": {}, "acgti": {}}
|
||||
|
||||
try:
|
||||
raw = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
||||
except Exception as e:
|
||||
logger.warning(f"加载人格资源清单失败: {e}")
|
||||
return {"sbti": {}, "acgti": {}}
|
||||
|
||||
manifest: dict[str, dict] = {"sbti": {}, "acgti": {}}
|
||||
for item in raw.get("sbti", []):
|
||||
code = str(item.get("code", "")).strip()
|
||||
if code:
|
||||
manifest["sbti"][code] = item
|
||||
for item in raw.get("acgti", []):
|
||||
code = str(item.get("code", "")).strip()
|
||||
if code:
|
||||
manifest["acgti"][code] = item
|
||||
return manifest
|
||||
|
||||
def _get_profile_mapping_overrides(self) -> dict[str, dict]:
|
||||
"""解析用户配置的人格映射覆盖项。"""
|
||||
raw = self.config_manager.get_profile_mapping_config()
|
||||
if not raw:
|
||||
return {}
|
||||
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.warning(f"人格映射配置 JSON 解析失败,已回退到默认映射: {e}")
|
||||
return {}
|
||||
|
||||
def _build_profile_image_from_manifest_pattern(
|
||||
self, profile_mode: str, asset_code: str
|
||||
) -> str:
|
||||
"""当 manifest 缺少具体 code 时,根据已有资源路径模式推导图片地址。"""
|
||||
system_manifest = self._profile_asset_manifest.get(profile_mode, {})
|
||||
for item in system_manifest.values():
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
sample_code = str(item.get("code", "")).strip()
|
||||
sample_file = str(item.get("file", "")).strip()
|
||||
if not sample_code or not sample_file:
|
||||
continue
|
||||
code_token = f"/{sample_code}."
|
||||
if code_token not in sample_file:
|
||||
continue
|
||||
return sample_file.replace(code_token, f"/{asset_code}.", 1)
|
||||
return ""
|
||||
|
||||
def _get_manifest_profile_item_by_mbti(
|
||||
self, profile_mode: str, mbti: str
|
||||
) -> dict | None:
|
||||
"""按 MBTI 从 manifest 中寻找可用资源。"""
|
||||
normalized_mbti = str(mbti or "").strip().upper()
|
||||
system_manifest = self._profile_asset_manifest.get(profile_mode, {})
|
||||
for item in system_manifest.values():
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
item_mbti = str(item.get("mbti", "")).strip().upper()
|
||||
if item_mbti == normalized_mbti:
|
||||
return item
|
||||
return None
|
||||
|
||||
def _resolve_profile_info(
|
||||
self,
|
||||
mbti: str,
|
||||
profile_mode: str,
|
||||
overrides: dict[str, dict],
|
||||
) -> dict[str, str | float]:
|
||||
"""根据当前展示模式解析人格标签展示信息。"""
|
||||
normalized_mbti = str(mbti or "").strip().upper()
|
||||
|
||||
# 1. 基础信息获取:从默认映射或用户覆盖中获取核心属性
|
||||
profile_defaults = DEFAULT_PROFILE_MAPPING.get(profile_mode, {})
|
||||
base_info = dict(profile_defaults.get(normalized_mbti, {}))
|
||||
|
||||
# 用户覆盖优先级最高
|
||||
user_override = overrides.get(profile_mode, {}).get(normalized_mbti, {})
|
||||
if isinstance(user_override, dict):
|
||||
base_info.update(user_override)
|
||||
|
||||
code = str(base_info.get("code", normalized_mbti)).strip() or normalized_mbti
|
||||
name_zh = str(base_info.get("name_zh", "")).strip()
|
||||
asset_code = str(base_info.get("asset_code", code)).strip() or code
|
||||
image = str(base_info.get("image", "")).strip()
|
||||
|
||||
# 2. 图片与属性补全 (基于 manifest.json 可信源)
|
||||
if not image:
|
||||
system_manifest = self._profile_asset_manifest.get(profile_mode, {})
|
||||
# A. 优先按 asset_code 索引
|
||||
asset_item = system_manifest.get(asset_code)
|
||||
if isinstance(asset_item, dict):
|
||||
image = str(asset_item.get("file", "")).strip()
|
||||
if not name_zh:
|
||||
name_zh = str(asset_item.get("name", "")).strip()
|
||||
|
||||
# B. 对于 acgti 模式,如果没找到,尝试通过 MBTI 反查第一个匹配的资源
|
||||
if not image and profile_mode == "acgti":
|
||||
fallback_item = self._get_manifest_profile_item_by_mbti(
|
||||
profile_mode, normalized_mbti
|
||||
)
|
||||
if isinstance(fallback_item, dict):
|
||||
image = str(fallback_item.get("file", "")).strip()
|
||||
if not name_zh:
|
||||
name_zh = str(fallback_item.get("name", "")).strip()
|
||||
if not code or code == normalized_mbti:
|
||||
code = str(fallback_item.get("code", code)).strip()
|
||||
|
||||
# C. 最后底线:根据 manifest 路径模式推导
|
||||
if not image:
|
||||
image = self._build_profile_image_from_manifest_pattern(
|
||||
profile_mode, asset_code
|
||||
)
|
||||
|
||||
# 3. 构造显示文本 (Code + 中文名)
|
||||
display = str(base_info.get("display", "")).strip()
|
||||
if not display:
|
||||
display = f"{code}({name_zh})" if name_zh else code
|
||||
|
||||
return {
|
||||
"profile_mode": profile_mode,
|
||||
"profile_code": code,
|
||||
"profile_name_zh": name_zh,
|
||||
"profile_display": display,
|
||||
"profile_image": image,
|
||||
"profile_image_opacity": self.config_manager.get_profile_image_opacity(),
|
||||
"profile_image_size_mode": self.config_manager.get_profile_image_size_mode(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_path_component(name: str) -> str:
|
||||
@@ -506,11 +706,16 @@ class ReportGenerator(IReportGenerator):
|
||||
# 使用Jinja2模板构建用户称号HTML(批量渲染,包含头像)
|
||||
max_user_titles = self.config_manager.get_max_user_titles()
|
||||
titles_list = []
|
||||
profile_mode = self.config_manager.get_profile_display_mode()
|
||||
profile_mapping_overrides = self._get_profile_mapping_overrides()
|
||||
for title in user_titles[:max_user_titles]:
|
||||
# 获取用户头像
|
||||
avatar_data = await self._get_user_avatar(
|
||||
str(title.user_id), avatar_url_getter
|
||||
)
|
||||
profile_info = self._resolve_profile_info(
|
||||
title.mbti, profile_mode, profile_mapping_overrides
|
||||
)
|
||||
title_data = {
|
||||
"name": title.name,
|
||||
"title": title.title,
|
||||
@@ -518,6 +723,7 @@ class ReportGenerator(IReportGenerator):
|
||||
"reason": title.reason,
|
||||
"avatar_data": avatar_data,
|
||||
}
|
||||
title_data.update(profile_info)
|
||||
titles_list.append(title_data)
|
||||
|
||||
titles_html = self.html_templates.render_template(
|
||||
|
||||
@@ -1,39 +1,42 @@
|
||||
{% set title_emojis = [
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132813334_1774881267181_得意.gif',
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132808652_1774881267385_得意-1.gif',
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132804506_1774881263342_观察.gif',
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132811492_1774881264336_不要.gif'
|
||||
] %}
|
||||
{% set title_badge_colors = [
|
||||
{'bg': 'linear-gradient(135deg, #ffcadf 0%, #ffc1cc 45%, #ffd9b8 100%)', 'text': '#b85c7b', 'shadow': 'rgba(255, 177, 203, 0.28)', 'border': 'rgba(255, 184, 210, 0.72)'},
|
||||
{'bg': 'linear-gradient(135deg, #bfe7ff 0%, #c8dbff 48%, #d8ccff 100%)', 'text': '#5d71b8', 'shadow': 'rgba(168, 198, 255, 0.26)', 'border': 'rgba(173, 204, 255, 0.74)'},
|
||||
{'bg': 'linear-gradient(135deg, #c7f3df 0%, #d3f1ff 50%, #e1ddff 100%)', 'text': '#4e8e88', 'shadow': 'rgba(161, 232, 205, 0.24)', 'border': 'rgba(178, 230, 214, 0.72)'},
|
||||
{'bg': 'linear-gradient(135deg, #ffe5b7 0%, #ffd6c7 48%, #ffd7ef 100%)', 'text': '#b57752', 'shadow': 'rgba(255, 210, 163, 0.28)', 'border': 'rgba(255, 214, 178, 0.76)'}
|
||||
] %}
|
||||
{% for title in titles %}
|
||||
{% set badge = title_badge_colors[loop.index0 % (title_badge_colors|length)] %}
|
||||
<li class="item item-with-emoji">
|
||||
<div class="item-main">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
{% if title.avatar_data %}
|
||||
<img src="{{ title.avatar_data }}"
|
||||
alt="头像"
|
||||
style="width: 50px; height: 50px; border-radius: 50%; object-fit: cover; border: 2px solid #667eea;">
|
||||
{% endif %}
|
||||
<div style="flex: 1;">
|
||||
<div class="item-title">
|
||||
{{ title.name }}
|
||||
<span style="display: inline-flex; align-items: center; margin-left: 10px; padding: 5px 10px; border-radius: 999px; font-size: 0.78em; font-weight: 700; letter-spacing: 0.2px; color: {{ badge.text }}; background: {{ badge.bg }}; border: 1px solid {{ badge.border }}; box-shadow: 0 6px 14px {{ badge.shadow }}, inset 0 1px 0 rgba(255,255,255,0.55); vertical-align: middle;">
|
||||
{{ title.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<p><strong>MBTI:</strong> {{ title.mbti }}</p>
|
||||
<p><strong>称号理由:</strong> {{ title.reason }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img class="item-emoji title-emoji" src="{{ title_emojis | random }}" alt="称号表情">
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% set title_emojis = [
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132813334_1774881267181_得意.gif',
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132808652_1774881267385_得意-1.gif',
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132804506_1774881263342_观察.gif',
|
||||
'https://tc.ciallo.ccwu.cc/file/1775132811492_1774881264336_不要.gif'
|
||||
] %}
|
||||
{% set title_badge_colors = [
|
||||
{'bg': 'linear-gradient(135deg, #ffcadf 0%, #ffc1cc 45%, #ffd9b8 100%)', 'text': '#b85c7b', 'shadow': 'rgba(255, 177, 203, 0.28)', 'border': 'rgba(255, 184, 210, 0.72)'},
|
||||
{'bg': 'linear-gradient(135deg, #bfe7ff 0%, #c8dbff 48%, #d8ccff 100%)', 'text': '#5d71b8', 'shadow': 'rgba(168, 198, 255, 0.26)', 'border': 'rgba(173, 204, 255, 0.74)'},
|
||||
{'bg': 'linear-gradient(135deg, #c7f3df 0%, #d3f1ff 50%, #e1ddff 100%)', 'text': '#4e8e88', 'shadow': 'rgba(161, 232, 205, 0.24)', 'border': 'rgba(178, 230, 214, 0.72)'},
|
||||
{'bg': 'linear-gradient(135deg, #ffe5b7 0%, #ffd6c7 48%, #ffd7ef 100%)', 'text': '#b57752', 'shadow': 'rgba(255, 210, 163, 0.28)', 'border': 'rgba(255, 214, 178, 0.76)'}
|
||||
] %}
|
||||
{% for title in titles %}
|
||||
{% set badge = title_badge_colors[loop.index0 % (title_badge_colors|length)] %}
|
||||
<li class="item item-with-emoji" style="position: relative; overflow: hidden;">
|
||||
<div class="item-main" style="position: relative; z-index: 1;">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
{% if title.avatar_data %}
|
||||
<img src="{{ title.avatar_data }}"
|
||||
alt="头像"
|
||||
style="width: 50px; height: 50px; border-radius: 50%; object-fit: cover; border: 2px solid #667eea;">
|
||||
{% endif %}
|
||||
<div style="flex: 1;">
|
||||
<div class="item-title">
|
||||
{{ title.name }}
|
||||
<span style="display: inline-flex; align-items: center; margin-left: 10px; padding: 5px 10px; border-radius: 999px; font-size: 0.78em; font-weight: 700; letter-spacing: 0.2px; color: {{ badge.text }}; background: {{ badge.bg }}; border: 1px solid {{ badge.border }}; box-shadow: 0 6px 14px {{ badge.shadow }}, inset 0 1px 0 rgba(255,255,255,0.55); vertical-align: middle;">
|
||||
{{ title.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="item-content">
|
||||
<p><strong>人格标签:</strong> {{ title.profile_display or title.mbti }}</p>
|
||||
<p><strong>称号理由:</strong> {{ title.reason }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 36%; max-height: 78%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
<img class="item-emoji title-emoji" src="{{ title_emojis | random }}" alt="称号表情" style="z-index: 2;">
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% if titles %}
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
|
||||
{% for title in titles %}
|
||||
<div class="card-common" style="padding: 24px; margin-bottom: 0; border-top: 4px solid var(--miku-light); display: flex; flex-direction: column; gap: 15px;">
|
||||
<div class="card-common" style="padding: 24px; margin-bottom: 0; border-top: 4px solid var(--miku-light); display: flex; flex-direction: column; gap: 15px; position: relative; overflow: hidden;">
|
||||
<div style="display: flex; align-items: center; gap: 15px;">
|
||||
<div style="width: 60px; height: 60px; border-radius: 50%; border: 3px solid white; box-shadow: 0 4px 10px rgba(57,197,187,0.2); overflow: hidden; flex-shrink: 0; background: var(--bg-page-base);">
|
||||
{% if title.avatar_data %}
|
||||
@@ -16,8 +16,8 @@
|
||||
<div style="font-weight: 800; font-size: 1.15rem; color: var(--accent-dark); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ title.name }}</div>
|
||||
<div style="display: flex; gap: 6px; margin-top: 6px;">
|
||||
<span style="background: var(--miku-blue); color: var(--miku-dark); font-size: 0.7rem; padding: 2px 8px; border-radius: 6px; font-weight: 800; border: 1px solid rgba(57,197,187,0.3);">{{ title.title }}</span>
|
||||
{% if title.mbti %}
|
||||
<span style="background: var(--miku-pink-light); color: white; font-size: 0.7rem; padding: 2px 8px; border-radius: 6px; font-family: var(--font-num); font-weight: 800; box-shadow: 0 2px 5px rgba(255,107,149,0.2);">{{ title.mbti }}</span>
|
||||
{% if title.profile_display or title.mbti %}
|
||||
<span style="background: var(--miku-pink-light); color: white; font-size: 0.7rem; padding: 2px 8px; border-radius: 6px; font-family: var(--font-num); font-weight: 800; box-shadow: 0 2px 5px rgba(255,107,149,0.2);">{{ title.profile_display or title.mbti }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,7 +26,11 @@
|
||||
{{ title.reason }}
|
||||
<svg viewBox="0 0 24 24" fill="var(--miku-base)" style="position: absolute; right: 8px; bottom: 8px; width: 16px; height: 16px; opacity: 0.2;">
|
||||
<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2H4c-1.25 0-2 .75-2 2v5c0 1.25.75 2 2 2h3c0 4-4 6-4 6Zm11 0c3 0 7-1 7-8V5c0-1.25-.75-2-2-2h-4c-1.25 0-2 .75-2 2v5c0 1.25.75 2 2 2h3c0 4-4 6-4 6Z"/>
|
||||
</svg> </div>
|
||||
</svg>
|
||||
</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 38%; max-height: 78%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h2 class="section-title">群友称号</h2>
|
||||
<div class="users-grid">
|
||||
{% for title in titles %}
|
||||
<div class="user-title">
|
||||
<div class="user-title" style="position: relative; overflow: hidden;">
|
||||
<div class="user-info">
|
||||
{% if title.avatar_data %}
|
||||
<img src="{{ title.avatar_data }}" class="user-avatar" alt="头像">
|
||||
@@ -14,13 +14,16 @@
|
||||
<div class="user-name">{{ title.name }}</div>
|
||||
<div class="user-badges">
|
||||
<div class="user-title-badge">{{ title.title }}</div>
|
||||
<div class="user-mbti">{{ title.mbti }}</div>
|
||||
<div class="user-mbti">{{ title.profile_display or title.mbti }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-reason">{{ title.reason }}</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 38%; max-height: 82%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% if titles %}
|
||||
<div class="user-grid">
|
||||
{% for title in titles %}
|
||||
<div class="user-card avoid-break">
|
||||
<div class="user-card avoid-break" style="position: relative; overflow: hidden;">
|
||||
<div class="user-avatar">
|
||||
{% if title.avatar_data %}
|
||||
<img src="{{ title.avatar_data }}" alt="{{ title.name }}"
|
||||
@@ -15,12 +15,15 @@
|
||||
<div class="user-info">
|
||||
<h4>{{ title.name }}</h4>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<span class="tag mbti">{{ title.mbti }}</span>
|
||||
<span class="tag mbti">{{ title.profile_display or title.mbti }}</span>
|
||||
<span class="tag title">{{ title.title }}</span>
|
||||
</div>
|
||||
<div style="font-size: 0.85rem; color: var(--text-secondary); line-height: 1.4;">{{ title.reason }}</div>
|
||||
</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 36%; max-height: 78%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% if titles %}
|
||||
{% for title in titles %}
|
||||
<div class="title-item">
|
||||
<div class="title-item" style="position: relative; overflow: hidden;">
|
||||
<div class="title-avatar-wrap">
|
||||
{% if title.avatar_data %}
|
||||
<img src="{{ title.avatar_data }}" alt="{{ title.name }}">
|
||||
@@ -13,12 +13,15 @@
|
||||
<div class="title-info">
|
||||
<h4>{{ title.name }}</h4>
|
||||
<div class="title-badge">
|
||||
<span>{{ title.mbti }}</span>
|
||||
<span>{{ title.profile_display or title.mbti }}</span>
|
||||
<span style="color:var(--c-dec-3);">//</span>
|
||||
<span>{{ title.title }}</span>
|
||||
</div>
|
||||
<div class="title-reason">{{ title.reason }}</div>
|
||||
</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 35%; max-height: 80%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div class="masonry-grid">
|
||||
{% for title in titles %}
|
||||
<div class="user-card avoid-break">
|
||||
<div class="user-card avoid-break" style="position: relative; overflow: hidden;">
|
||||
<div class="card-tape"></div>
|
||||
<div class="user-header">
|
||||
<div class="u-avatar">
|
||||
@@ -26,15 +26,18 @@
|
||||
<div class="u-name">{{ title.name }}</div>
|
||||
<div class="badges">
|
||||
<span class="badge title">{{ title.title }}</span>
|
||||
<span class="badge mbti">{{ title.mbti }}</span>
|
||||
<span class="badge mbti">{{ title.profile_display or title.mbti }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-reason">
|
||||
{{ title.reason }}
|
||||
</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 38%; max-height: 80%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
<div class="section">
|
||||
<h2>群友称号</h2>
|
||||
{% for item in titles %}
|
||||
<div style="margin-bottom: 10px; border: 1px solid #eee; padding: 5px;">
|
||||
<div style="margin-bottom: 10px; border: 1px solid #eee; padding: 5px; position: relative; overflow: hidden;">
|
||||
<div style="font-weight: bold;">{{ item.name }} - {{ item.title }}</div>
|
||||
<div style="font-size: 0.8em; background: #eee; display: inline-block; padding: 2px 5px; border-radius: 3px;">{{ item.mbti }}</div>
|
||||
<div style="font-size: 0.9em; color: #555; margin-top: 5px;">{{ item.reason }}</div>
|
||||
<div style="font-size: 0.8em; background: #eee; display: inline-block; padding: 2px 5px; border-radius: 3px; position: relative; z-index: 1;">{{ item.profile_display or item.mbti }}</div>
|
||||
<div style="font-size: 0.9em; color: #555; margin-top: 5px; position: relative; z-index: 1;">{{ item.reason }}</div>
|
||||
{% if item.profile_image %}
|
||||
<img src="{{ item.profile_image }}" alt="" style="position: absolute; right: 4px; bottom: 4px; width: 36%; max-height: 90%; object-fit: {{ item.profile_image_size_mode or 'contain' }}; opacity: {{ item.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 2;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% if titles %}
|
||||
<div class="sf-user-masonry">
|
||||
{% for title in titles %}
|
||||
<div class="sf-user-card avoid-break">
|
||||
<div class="sf-user-card avoid-break" style="position: relative; overflow: hidden;">
|
||||
<div class="sf-card-header">
|
||||
<div class="sf-avatar-wrap">
|
||||
{% if title.avatar_data %}
|
||||
@@ -14,13 +14,16 @@
|
||||
<div class="sf-user-info">
|
||||
<h4 class="sf-user-name" style="margin: 0 0 5px; font-size: 1.3rem;">{{ title.name }}</h4>
|
||||
<div class="sf-user-badges">
|
||||
<span class="sf-badge mbti">{{ title.mbti }}</span>
|
||||
<span class="sf-badge mbti">{{ title.profile_display or title.mbti }}</span>
|
||||
<span class="sf-badge sf-title">{{ title.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sf-user-reason">{{ title.reason }}</div>
|
||||
{% if title.profile_image %}
|
||||
<img src="{{ title.profile_image }}" alt="" style="position: absolute; right: 8px; bottom: 8px; width: 36%; max-height: 78%; object-fit: {{ title.profile_image_size_mode or 'contain' }}; opacity: {{ title.profile_image_opacity or 0.12 }}; pointer-events: none; z-index: 1;">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user