不知名提交

This commit is contained in:
2025-12-13 20:53:50 +08:00
parent c147502b4d
commit 1221d6faf1
120 changed files with 11005 additions and 1092 deletions

View File

@@ -0,0 +1,25 @@
// 环境配置文件 - AI中国亲戚称呼计算器
// 复用 InfoGenie 的全局 ENV_CONFIG支持独立打开的回退地址
const DEFAULT_API = (window.ENV_CONFIG && window.ENV_CONFIG.API_URL) || 'http://127.0.0.1:5002';
window.API_CONFIG = {
baseUrl: window.parent?.ENV_CONFIG?.API_URL || DEFAULT_API,
endpoints: {
kinshipCalculator: '/api/aimodelapp/kinship-calculator'
}
};
window.AUTH_CONFIG = {
tokenKey: 'token',
getToken: () => localStorage.getItem('token'),
isAuthenticated: () => !!localStorage.getItem('token')
};
window.APP_CONFIG = {
name: 'InfoGenie 中国亲戚称呼计算器',
version: '1.0.0',
debug: false
};
console.log('中国亲戚称呼计算器 环境配置已加载');

View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<title>中国亲戚称呼计算器</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="app-container">
<header class="app-header">
<h1>中国亲戚称呼计算器</h1>
<p class="subtitle">输入亲属关系链(如“妈妈的爸爸”、“爸爸的姐姐的儿子”),快速得到标准普通话称呼与各地方言称呼</p>
</header>
<main class="card">
<section class="input-section">
<label for="relationInput" class="label">亲属关系链</label>
<textarea id="relationInput" class="textarea" rows="2" placeholder="例如:妈妈的爸爸"></textarea>
<div class="hint">
· 使用“的”连接每一层关系,例如:
<span class="chip" onclick="setExample('妈妈的爸爸')">妈妈的爸爸</span>
<span class="chip" onclick="setExample('爸爸的姐姐的儿子')">爸爸的姐姐的儿子</span>
<span class="chip" onclick="setExample('妈妈的弟弟的女儿')">妈妈的弟弟的女儿</span>
</div>
<button id="calcBtn" class="button primary">计算称呼</button>
<div id="loading" class="loading" style="display:none">正在计算,请稍候…</div>
<div id="error" class="error" style="display:none"></div>
</section>
<section id="resultSection" class="result-section" style="display:none">
<h2>计算结果</h2>
<div class="result-block">
<div class="result-title">标准普通话称呼</div>
<div id="mandarinTitle" class="result-value"></div>
<div class="actions">
<button id="copyMandarinBtn" class="button">复制称呼</button>
</div>
</div>
<div class="result-block">
<div class="result-title">各地方言称呼</div>
<div id="dialectList" class="dialect-list"></div>
</div>
<div id="notesBlock" class="result-block" style="display:none">
<div class="result-title">说明</div>
<div id="notes" class="notes"></div>
</div>
</section>
</main>
</div>
<script src="env.js"></script>
<script src="script.js"></script>
</body>
</html>

View File

@@ -0,0 +1,135 @@
// 环境与认证在 env.js 中定义
const relationInput = document.getElementById('relationInput');
const calcBtn = document.getElementById('calcBtn');
const loadingDiv = document.getElementById('loading');
const errorDiv = document.getElementById('error');
const resultSection = document.getElementById('resultSection');
const mandarinTitleEl = document.getElementById('mandarinTitle');
const dialectListEl = document.getElementById('dialectList');
const copyMandarinBtn = document.getElementById('copyMandarinBtn');
const notesBlock = document.getElementById('notesBlock');
const notesEl = document.getElementById('notes');
function setExample(text) {
relationInput.value = text;
}
window.setExample = setExample;
function showLoading(show) {
loadingDiv.style.display = show ? 'block' : 'none';
calcBtn.disabled = show;
}
function showError(msg) {
errorDiv.textContent = msg || '';
errorDiv.style.display = msg ? 'block' : 'none';
}
function clearResults() {
resultSection.style.display = 'none';
mandarinTitleEl.textContent = '';
dialectListEl.innerHTML = '';
notesBlock.style.display = 'none';
notesEl.textContent = '';
}
async function callKinshipAPI(relationChain) {
const token = window.AUTH_CONFIG.getToken();
if (!token) throw new Error('未登录请先登录后使用AI功能');
const url = `${window.API_CONFIG.baseUrl}${window.API_CONFIG.endpoints.kinshipCalculator}`;
const resp = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ relation_chain: relationChain })
});
if (!resp.ok) {
if (resp.status === 402) throw new Error('您的萌芽币余额不足,无法使用此功能');
const err = await resp.json().catch(() => ({}));
throw new Error(err.error || `API请求失败: ${resp.status} ${resp.statusText}`);
}
const data = await resp.json();
if (!data.success) throw new Error(data.error || 'API响应异常');
return data;
}
function renderDialects(dialectTitles) {
dialectListEl.innerHTML = '';
const order = ['粤语','闽南语','上海话','四川话','东北话','客家话'];
const names = order.concat(Object.keys(dialectTitles || {}).filter(k => !order.includes(k)));
names.forEach(name => {
const info = dialectTitles?.[name];
if (!info || (!info.title && !info.romanization && !info.notes)) return;
const item = document.createElement('div');
item.className = 'dialect-item';
const title = (info.title || '').toString();
const roman = (info.romanization || '').toString();
const notes = (info.notes || '').toString();
item.innerHTML = `
<div class="dialect-name">${name}</div>
<div class="dialect-title">${title}</div>
${roman ? `<div class="dialect-roman">${roman}</div>` : ''}
${notes ? `<div class="dialect-notes">${notes}</div>` : ''}
`;
dialectListEl.appendChild(item);
});
}
async function doCalculate() {
const relation = (relationInput.value || '').trim();
if (!relation) {
showError('请输入亲属关系链');
return;
}
showError('');
showLoading(true);
clearResults();
try {
const data = await callKinshipAPI(relation);
mandarinTitleEl.textContent = data.mandarin_title || '';
renderDialects(data.dialect_titles || {});
if (data.notes) {
notesEl.textContent = data.notes;
notesBlock.style.display = 'block';
}
resultSection.style.display = 'block';
} catch (e) {
console.error('计算失败:', e);
showError(`计算失败: ${e.message}`);
} finally {
showLoading(false);
}
}
function copyText(text) {
try {
navigator.clipboard.writeText(text);
} catch (e) {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
}
copyMandarinBtn.addEventListener('click', () => {
const t = mandarinTitleEl.textContent || '';
if (!t) return;
copyText(t);
});
calcBtn.addEventListener('click', doCalculate);
document.addEventListener('DOMContentLoaded', () => {
showError('');
});

View File

@@ -0,0 +1,189 @@
/* 渐变背景与毛玻璃风格,参考 AI文章排版 */
:root {
--green: #a8e6cf;
--lime: #dcedc1;
--dark: #2e7d32;
--text: #1b5e20;
--muted: #558b2f;
--white: #ffffff;
--shadow: rgba(0, 0, 0, 0.08);
}
html, body {
height: 100%;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--text);
background: linear-gradient(135deg, var(--green) 0%, var(--lime) 100%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
}
.app-header {
text-align: center;
margin: 8px 0 16px;
}
.app-header h1 {
font-size: 22px;
margin: 0;
color: var(--dark);
}
.subtitle {
margin-top: 8px;
font-size: 13px;
color: var(--muted);
}
.card {
width: 100%;
max-width: 720px;
background: rgba(255,255,255,0.75);
backdrop-filter: blur(10px);
border-radius: 14px;
box-shadow: 0 8px 24px var(--shadow);
padding: 16px;
}
.label {
display: block;
font-size: 13px;
color: var(--muted);
margin-bottom: 6px;
}
.textarea {
width: 100%;
box-sizing: border-box;
border: 1px solid rgba(0,0,0,0.05);
border-radius: 10px;
padding: 10px 12px;
font-size: 14px;
color: var(--text);
outline: none;
}
.textarea:focus {
border-color: rgba(46,125,50,0.35);
box-shadow: 0 0 0 3px rgba(46,125,50,0.12);
}
.hint {
margin: 10px 0 12px;
font-size: 12px;
color: var(--muted);
}
.chip {
display: inline-block;
background: rgba(255,255,255,0.9);
border: 1px solid rgba(46,125,50,0.2);
color: var(--dark);
border-radius: 999px;
padding: 4px 10px;
margin-right: 6px;
cursor: pointer;
user-select: none;
}
.chip:hover { filter: brightness(0.98); }
.button {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 12px;
padding: 10px 14px;
font-size: 14px;
cursor: pointer;
background: rgba(46,125,50,0.15);
color: var(--dark);
}
.button.primary {
background: linear-gradient(135deg, #81c784, #aed581);
color: #fff;
}
.button:disabled { opacity: 0.6; cursor: not-allowed; }
.loading {
margin-top: 10px;
font-size: 13px;
color: var(--muted);
}
.error {
margin-top: 8px;
padding: 8px 10px;
border-left: 3px solid #e53935;
background: rgba(229,57,53,0.08);
border-radius: 8px;
color: #c62828;
font-size: 13px;
}
.result-section { margin-top: 14px; }
.result-section h2 {
font-size: 16px;
margin: 0 0 8px;
color: var(--dark);
}
.result-block {
background: rgba(255,255,255,0.9);
border: 1px solid rgba(0,0,0,0.05);
border-radius: 12px;
padding: 10px;
margin-bottom: 10px;
}
.result-title {
font-size: 13px;
color: var(--muted);
margin-bottom: 6px;
}
.result-value {
font-size: 18px;
color: var(--text);
}
.actions { margin-top: 8px; }
.dialect-list {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
@media (min-width: 540px) {
.dialect-list { grid-template-columns: 1fr 1fr; }
}
.dialect-item {
border: 1px solid rgba(46,125,50,0.18);
border-radius: 10px;
padding: 8px;
background: rgba(255,255,255,0.95);
}
.dialect-item .dialect-name {
font-weight: 600;
color: var(--dark);
margin-bottom: 4px;
}
.dialect-item .dialect-title { font-size: 15px; }
.dialect-item .dialect-roman { font-size: 12px; color: var(--muted); }
.dialect-item .dialect-notes { font-size: 12px; color: var(--muted); margin-top: 4px; }
.notes { font-size: 13px; color: var(--muted); }
.app-footer {
margin-top: 12px;
font-size: 12px;
color: var(--muted);
text-align: center;
}