强制用户输入联系方式

This commit is contained in:
zhx 2026-06-29 18:27:25 +08:00
parent 25c5e65f39
commit 909b70e63d
17 changed files with 733 additions and 7 deletions

View File

@ -535,6 +535,12 @@ var default_api = 'https://api.global-interaction.com/';
method: 'GET', method: 'GET',
}); });
}, },
updateContact: function (payload) {
return request('/api/v1/users/me/contact-info', {
method: 'POST',
body: payload || {},
});
},
resolveDisplayUserID: function (displayUserID) { resolveDisplayUserID: function (displayUserID) {
return request( return request(
'/api/v1/users/by-display-user-id/' + '/api/v1/users/by-display-user-id/' +

View File

@ -670,5 +670,20 @@
"lalu_site.contactLead": "أرسل أسئلة المنتج أو الحساب أو التعاون إلى البريد الرسمي.", "lalu_site.contactLead": "أرسل أسئلة المنتج أو الحساب أو التعاون إلى البريد الرسمي.",
"lalu_site.emailSupport": "مراسلة الدعم", "lalu_site.emailSupport": "مراسلة الدعم",
"lalu_site.footerCompany": "Global Interactive Technology Co., Limited", "lalu_site.footerCompany": "Global Interactive Technology Co., Limited",
"lalu_site.footerContact": "postmaster@haiyinet.com.cn" "lalu_site.footerContact": "postmaster@haiyinet.com.cn",
"contact_card.open": "تعديل جهة اتصال WhatsApp",
"contact_card.title": "WhatsApp",
"contact_card.add_whatsapp": "إضافة WhatsApp",
"contact_card.add": "إضافة",
"contact_card.edit": "تعديل",
"contact_card.close": "إغلاق",
"contact_card.sheet_title": "جهة اتصال WhatsApp",
"contact_card.field_label": "رقم أو رابط WhatsApp",
"contact_card.placeholder": "+6512345678",
"contact_card.save": "حفظ",
"contact_card.saving": "جار الحفظ...",
"contact_card.required": "أدخل جهة اتصال WhatsApp.",
"contact_card.api_unavailable": "واجهة جهة الاتصال غير متاحة.",
"contact_card.saved": "تم الحفظ.",
"contact_card.save_failed": "فشل الحفظ."
} }

View File

@ -676,5 +676,20 @@
"lalu_site.contactLead": "Send product, account, or cooperation questions to the official email address.", "lalu_site.contactLead": "Send product, account, or cooperation questions to the official email address.",
"lalu_site.emailSupport": "Email support", "lalu_site.emailSupport": "Email support",
"lalu_site.footerCompany": "Global Interactive Technology Co., Limited", "lalu_site.footerCompany": "Global Interactive Technology Co., Limited",
"lalu_site.footerContact": "postmaster@haiyinet.com.cn" "lalu_site.footerContact": "postmaster@haiyinet.com.cn",
"contact_card.open": "Edit WhatsApp contact",
"contact_card.title": "WhatsApp",
"contact_card.add_whatsapp": "Add WhatsApp",
"contact_card.add": "Add",
"contact_card.edit": "Edit",
"contact_card.close": "Close",
"contact_card.sheet_title": "WhatsApp Contact",
"contact_card.field_label": "WhatsApp phone or link",
"contact_card.placeholder": "+6512345678",
"contact_card.save": "Save",
"contact_card.saving": "Saving...",
"contact_card.required": "Enter WhatsApp contact.",
"contact_card.api_unavailable": "Contact API unavailable.",
"contact_card.saved": "Saved.",
"contact_card.save_failed": "Save failed."
} }

View File

@ -670,5 +670,20 @@
"lalu_site.contactLead": "Envía preguntas de producto, cuenta o cooperación al email oficial.", "lalu_site.contactLead": "Envía preguntas de producto, cuenta o cooperación al email oficial.",
"lalu_site.emailSupport": "Enviar email a soporte", "lalu_site.emailSupport": "Enviar email a soporte",
"lalu_site.footerCompany": "Global Interactive Technology Co., Limited", "lalu_site.footerCompany": "Global Interactive Technology Co., Limited",
"lalu_site.footerContact": "postmaster@haiyinet.com.cn" "lalu_site.footerContact": "postmaster@haiyinet.com.cn",
"contact_card.open": "Editar contacto de WhatsApp",
"contact_card.title": "WhatsApp",
"contact_card.add_whatsapp": "Agregar WhatsApp",
"contact_card.add": "Agregar",
"contact_card.edit": "Editar",
"contact_card.close": "Cerrar",
"contact_card.sheet_title": "Contacto de WhatsApp",
"contact_card.field_label": "Teléfono o enlace de WhatsApp",
"contact_card.placeholder": "+6512345678",
"contact_card.save": "Guardar",
"contact_card.saving": "Guardando...",
"contact_card.required": "Ingresa el contacto de WhatsApp.",
"contact_card.api_unavailable": "API de contacto no disponible.",
"contact_card.saved": "Guardado.",
"contact_card.save_failed": "No se pudo guardar."
} }

View File

@ -598,5 +598,20 @@
"achievement.notCollected": "Belum dikoleksi", "achievement.notCollected": "Belum dikoleksi",
"achievement.collected": "Dikoleksi", "achievement.collected": "Dikoleksi",
"achievement.collectedWithDate": "Dikoleksi pada {date}", "achievement.collectedWithDate": "Dikoleksi pada {date}",
"achievement.ownerCount": "Hanya {count} orang yang memilikinya" "achievement.ownerCount": "Hanya {count} orang yang memilikinya",
"contact_card.open": "Ubah kontak WhatsApp",
"contact_card.title": "WhatsApp",
"contact_card.add_whatsapp": "Tambahkan WhatsApp",
"contact_card.add": "Tambah",
"contact_card.edit": "Ubah",
"contact_card.close": "Tutup",
"contact_card.sheet_title": "Kontak WhatsApp",
"contact_card.field_label": "Nomor atau link WhatsApp",
"contact_card.placeholder": "+6512345678",
"contact_card.save": "Simpan",
"contact_card.saving": "Menyimpan...",
"contact_card.required": "Masukkan kontak WhatsApp.",
"contact_card.api_unavailable": "API kontak tidak tersedia.",
"contact_card.saved": "Tersimpan.",
"contact_card.save_failed": "Gagal menyimpan."
} }

View File

@ -637,5 +637,20 @@
"lalu_site.contactLead": "Ürün, hesap veya iş birliği sorularını resmi e-posta adresine gönder.", "lalu_site.contactLead": "Ürün, hesap veya iş birliği sorularını resmi e-posta adresine gönder.",
"lalu_site.emailSupport": "Desteğe e-posta gönder", "lalu_site.emailSupport": "Desteğe e-posta gönder",
"lalu_site.footerCompany": "Global Interactive Technology Co., Limited", "lalu_site.footerCompany": "Global Interactive Technology Co., Limited",
"lalu_site.footerContact": "postmaster@haiyinet.com.cn" "lalu_site.footerContact": "postmaster@haiyinet.com.cn",
"contact_card.open": "WhatsApp iletişimini düzenle",
"contact_card.title": "WhatsApp",
"contact_card.add_whatsapp": "WhatsApp ekle",
"contact_card.add": "Ekle",
"contact_card.edit": "Düzenle",
"contact_card.close": "Kapat",
"contact_card.sheet_title": "WhatsApp İletişimi",
"contact_card.field_label": "WhatsApp telefonu veya bağlantısı",
"contact_card.placeholder": "+6512345678",
"contact_card.save": "Kaydet",
"contact_card.saving": "Kaydediliyor...",
"contact_card.required": "WhatsApp iletişimini gir.",
"contact_card.api_unavailable": "İletişim API kullanılamıyor.",
"contact_card.saved": "Kaydedildi.",
"contact_card.save_failed": "Kaydetme başarısız."
} }

View File

@ -604,5 +604,20 @@
"achievement.notCollected": "未收集", "achievement.notCollected": "未收集",
"achievement.collected": "已收集", "achievement.collected": "已收集",
"achievement.collectedWithDate": "{date} 已收集", "achievement.collectedWithDate": "{date} 已收集",
"achievement.ownerCount": "仅 {count} 人拥有" "achievement.ownerCount": "仅 {count} 人拥有",
"contact_card.open": "修改 WhatsApp 联系方式",
"contact_card.title": "WhatsApp",
"contact_card.add_whatsapp": "添加 WhatsApp",
"contact_card.add": "添加",
"contact_card.edit": "修改",
"contact_card.close": "关闭",
"contact_card.sheet_title": "WhatsApp 联系方式",
"contact_card.field_label": "WhatsApp 手机号或链接",
"contact_card.placeholder": "+6512345678",
"contact_card.save": "保存",
"contact_card.saving": "保存中...",
"contact_card.required": "请输入 WhatsApp 联系方式。",
"contact_card.api_unavailable": "联系方式接口不可用。",
"contact_card.saved": "已保存。",
"contact_card.save_failed": "保存失败。"
} }

View File

@ -113,6 +113,8 @@
</button> </button>
</section> </section>
<div data-contact-card></div>
<section class="card salary-card"> <section class="card salary-card">
<div class="salary-head"> <div class="salary-head">
<div class="salary-title"> <div class="salary-title">
@ -450,6 +452,7 @@
<script src="../../common/params.js?v=20260603-agency-center"></script> <script src="../../common/params.js?v=20260603-agency-center"></script>
<script src="../../common/jsbridge.js"></script> <script src="../../common/jsbridge.js"></script>
<script src="../../common/i18n.js"></script> <script src="../../common/i18n.js"></script>
<script src="../common/contact-card.js?v=20260629-user-contact"></script>
<script src="./script.js?v=20260605-agency-real-policy"></script> <script src="./script.js?v=20260605-agency-real-policy"></script>
</body> </body>
</html> </html>

View File

@ -77,6 +77,8 @@
</div> </div>
</section> </section>
<div data-contact-card></div>
<section class="card salary-card"> <section class="card salary-card">
<div class="salary-head"> <div class="salary-head">
<h2 data-i18n="bd_center.salary">Salary</h2> <h2 data-i18n="bd_center.salary">Salary</h2>
@ -357,6 +359,7 @@
<script src="../../common/jsbridge.js"></script> <script src="../../common/jsbridge.js"></script>
<script src="../../common/i18n.js"></script> <script src="../../common/i18n.js"></script>
<script src="../../common/api.js?v=20260604-bd-center"></script> <script src="../../common/api.js?v=20260604-bd-center"></script>
<script src="../common/contact-card.js?v=20260629-user-contact"></script>
<script src="./script.js?v=20260604-bd-center-real-skeleton"></script> <script src="./script.js?v=20260604-bd-center-real-skeleton"></script>
</body> </body>
</html> </html>

View File

@ -74,6 +74,8 @@
</div> </div>
</section> </section>
<div data-contact-card></div>
<section class="card balance-card"> <section class="card balance-card">
<div class="balance-head"> <div class="balance-head">
<div> <div>
@ -434,6 +436,7 @@
<script src="../../common/jsbridge.js"></script> <script src="../../common/jsbridge.js"></script>
<script src="../../common/api.js?v=20260604-coinseller-records"></script> <script src="../../common/api.js?v=20260604-coinseller-records"></script>
<script src="../../common/i18n.js?v=20260604-coinseller"></script> <script src="../../common/i18n.js?v=20260604-coinseller"></script>
<script src="../common/contact-card.js?v=20260629-user-contact"></script>
<script src="./script.js?v=20260604-record-user-profile"></script> <script src="./script.js?v=20260604-record-user-profile"></script>
</body> </body>
</html> </html>

View File

@ -218,6 +218,129 @@ input:focus-visible {
box-shadow: var(--shadow); box-shadow: var(--shadow);
} }
.contact-card {
overflow: hidden;
}
.contact-card .contact-row {
display: grid;
grid-template-columns: 46px minmax(0, 1fr) auto;
gap: 12px;
align-items: center;
width: 100%;
min-height: 70px;
padding: 14px 18px;
text-align: left;
}
.contact-card .contact-icon {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 46px;
border-radius: 50%;
background: rgba(49, 198, 168, 0.14);
color: var(--success);
}
.contact-card .contact-icon svg {
width: 24px;
height: 24px;
}
.contact-card .contact-icon path,
.contact-card .contact-icon circle {
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2.2;
}
.contact-card .contact-copy {
display: grid;
gap: 4px;
min-width: 0;
}
.contact-card .contact-title {
color: var(--text);
font-size: 15px;
font-weight: 900;
line-height: 1.1;
}
.contact-card .contact-value {
overflow: hidden;
color: var(--muted);
font-size: 13px;
font-weight: 800;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.contact-card .contact-action {
color: var(--primary-deep);
font-size: 13px;
font-weight: 950;
white-space: nowrap;
}
.contact-sheet .contact-field {
display: grid;
gap: 8px;
margin-top: 6px;
}
.contact-sheet .contact-field span {
color: var(--muted);
font-size: 13px;
font-weight: 850;
}
.contact-sheet .contact-input {
width: 100%;
height: 52px;
border: 1px solid var(--line);
border-radius: 8px;
padding: 0 14px;
background: rgba(255, 255, 255, 0.9);
color: var(--text);
font-size: 15px;
font-weight: 850;
}
.contact-sheet .contact-input::placeholder {
color: rgba(141, 135, 154, 0.72);
}
.contact-sheet .contact-save {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 52px;
margin-top: 14px;
border-radius: 8px;
background: var(--button);
color: #fff;
font-size: 16px;
font-weight: 950;
}
.contact-sheet .contact-save:disabled {
background: var(--button-disabled);
}
.contact-sheet .contact-status {
margin-top: 10px;
color: var(--danger);
font-size: 13px;
font-weight: 850;
}
.profile-card { .profile-card {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -0,0 +1,215 @@
(function () {
var mounted = false;
function t(key, fallback) {
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(key, fallback)
: fallback;
}
function toast(message) {
if (window.HyAppToast && window.HyAppToast.show) {
window.HyAppToast.show(message);
}
}
function contactFrom(value) {
if (!value || typeof value !== 'object') return '';
var source = value.profile || value.user || value;
var keys = [
'contact_info',
'contactInfo',
'whatsapp_phone',
'whatsappPhone',
'contact',
];
for (var i = 0; i < keys.length; i += 1) {
var text = String(source[keys[i]] || '').trim();
if (text) return text;
}
return '';
}
function render(mount) {
mount.innerHTML =
'<section class="card contact-card">' +
' <button class="contact-row" type="button" data-contact-open data-i18n-aria="contact_card.open" aria-label="Edit WhatsApp contact">' +
' <span class="contact-icon" aria-hidden="true">' +
' <svg viewBox="0 0 24 24"><path d="M7 18.5 4 20l.8-3.5A8 8 0 1 1 12 20a8.8 8.8 0 0 1-5-1.5Z"/><path d="M9 8.8c.2 3 2.2 5 5.2 5.4l1-1.6c.2-.3.6-.4.9-.2l1.3.7"/><path d="M8.4 7.4 9 8.8"/></svg>' +
' </span>' +
' <span class="contact-copy">' +
' <span class="contact-title" data-i18n="contact_card.title">WhatsApp</span>' +
' <span class="contact-value" data-contact-value data-i18n="contact_card.add_whatsapp">Add WhatsApp</span>' +
' </span>' +
' <span class="contact-action" data-contact-action data-i18n="contact_card.add">Add</span>' +
' </button>' +
'</section>' +
'<div class="sheet-modal contact-modal" data-contact-modal hidden aria-hidden="true">' +
' <button class="modal-backdrop" type="button" data-contact-close data-i18n-aria="contact_card.close" aria-label="Close"></button>' +
' <section class="bottom-sheet contact-sheet" role="dialog" aria-modal="true" aria-labelledby="contactSheetTitle">' +
' <div class="sheet-head">' +
' <h2 id="contactSheetTitle" data-i18n="contact_card.sheet_title">WhatsApp Contact</h2>' +
' <button class="sheet-close" type="button" data-contact-close data-i18n-aria="contact_card.close" aria-label="Close">&times;</button>' +
' </div>' +
' <form data-contact-form>' +
' <label class="contact-field">' +
' <span data-i18n="contact_card.field_label">WhatsApp phone or link</span>' +
' <input class="contact-input" data-contact-input type="text" inputmode="text" autocomplete="off" placeholder="+6512345678" data-i18n-placeholder="contact_card.placeholder" />' +
' </label>' +
' <button class="contact-save" type="submit" data-contact-save data-i18n="contact_card.save">Save</button>' +
' <div class="contact-status" data-contact-status hidden></div>' +
' </form>' +
' </section>' +
'</div>';
if (window.HyAppI18n && window.HyAppI18n.apply) {
window.HyAppI18n.apply(mount);
}
}
function setModalOpen(root, open) {
var modal = root.querySelector('[data-contact-modal]');
if (!modal) return;
modal.hidden = !open;
modal.setAttribute('aria-hidden', open ? 'false' : 'true');
document.body.classList.toggle('modal-open', open);
if (open) {
var input = root.querySelector('[data-contact-input]');
if (input) input.focus();
}
}
function setContact(root, value) {
var contact = String(value || '').trim();
var valueNode = root.querySelector('[data-contact-value]');
var actionNode = root.querySelector('[data-contact-action]');
if (valueNode) {
valueNode.textContent =
contact || t('contact_card.add_whatsapp', 'Add WhatsApp');
if (contact) {
valueNode.removeAttribute('data-i18n');
} else {
valueNode.setAttribute('data-i18n', 'contact_card.add_whatsapp');
}
}
if (actionNode) {
actionNode.textContent = contact
? t('contact_card.edit', 'Edit')
: t('contact_card.add', 'Add');
if (contact) {
actionNode.removeAttribute('data-i18n');
} else {
actionNode.setAttribute('data-i18n', 'contact_card.add');
}
}
root.dataset.contactValue = contact;
}
function setStatus(root, message) {
var status = root.querySelector('[data-contact-status]');
if (!status) return;
status.textContent = message || '';
status.hidden = !message;
}
function setSaving(root, saving) {
var button = root.querySelector('[data-contact-save]');
if (!button) return;
button.disabled = saving;
button.textContent = saving
? t('contact_card.saving', 'Saving...')
: t('contact_card.save', 'Save');
}
function loadCurrentContact(root) {
var api = window.HyAppAPI && window.HyAppAPI.user;
if (!api || !api.me) return;
api.me()
.then(function (profile) {
setContact(root, contactFrom(profile));
})
.catch(function () {
setContact(root, '');
});
}
function bind(root) {
root.addEventListener('click', function (event) {
if (event.target.closest('[data-contact-open]')) {
var input = root.querySelector('[data-contact-input]');
if (input) input.value = root.dataset.contactValue || '';
setStatus(root, '');
setModalOpen(root, true);
return;
}
if (event.target.closest('[data-contact-close]')) {
setModalOpen(root, false);
}
});
var form = root.querySelector('[data-contact-form]');
if (!form) return;
form.addEventListener('submit', function (event) {
event.preventDefault();
var api = window.HyAppAPI && window.HyAppAPI.user;
var input = root.querySelector('[data-contact-input]');
var contact = input ? String(input.value || '').trim() : '';
if (!contact) {
setStatus(
root,
t('contact_card.required', 'Enter WhatsApp contact.')
);
return;
}
if (!api || !api.updateContact) {
setStatus(
root,
t('contact_card.api_unavailable', 'Contact API unavailable.')
);
return;
}
// 联系方式是用户主资料,不依赖当前中心页身份;保存成功后用后端返回值刷新展示,避免本地输入和服务端裁剪值不一致。
setSaving(root, true);
setStatus(root, '');
api.updateContact({ contact_info: contact })
.then(function (response) {
setContact(root, contactFrom(response) || contact);
setModalOpen(root, false);
toast(t('contact_card.saved', 'Saved.'));
})
.catch(function () {
setStatus(
root,
t('contact_card.save_failed', 'Save failed.')
);
})
.finally(function () {
setSaving(root, false);
});
});
}
function init() {
if (mounted) return;
var mounts = document.querySelectorAll('[data-contact-card]');
if (!mounts.length) return;
mounted = true;
mounts.forEach(function (mount) {
render(mount);
bind(mount);
loadCurrentContact(mount);
});
}
window.addEventListener('hyapp:i18n-ready', function () {
document.querySelectorAll('[data-contact-card]').forEach(function (root) {
setContact(root, root.dataset.contactValue || '');
});
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

View File

@ -75,6 +75,8 @@
</div> </div>
</section> </section>
<div data-contact-card></div>
<section class="card income-card"> <section class="card income-card">
<div class="income-head"> <div class="income-head">
<h2 data-i18n="host_center.available_salary"> <h2 data-i18n="host_center.available_salary">
@ -270,6 +272,7 @@
<script src="../../common/params.js?v=20260602-api"></script> <script src="../../common/params.js?v=20260602-api"></script>
<script src="../../common/jsbridge.js"></script> <script src="../../common/jsbridge.js"></script>
<script src="../../common/i18n.js?v=20260604-diamonds-label"></script> <script src="../../common/i18n.js?v=20260604-diamonds-label"></script>
<script src="../common/contact-card.js?v=20260629-user-contact"></script>
<script src="./script.js?v=20260604-no-zero-scroll"></script> <script src="./script.js?v=20260604-no-zero-scroll"></script>
</body> </body>
</html> </html>

View File

@ -80,6 +80,8 @@
</div> </div>
</section> </section>
<div data-contact-card></div>
<section class="card salary-card"> <section class="card salary-card">
<div class="salary-head"> <div class="salary-head">
<h2 data-i18n="superadmin_center.salary">Salary</h2> <h2 data-i18n="superadmin_center.salary">Salary</h2>
@ -310,6 +312,7 @@
<script src="../../common/jsbridge.js"></script> <script src="../../common/jsbridge.js"></script>
<script src="../../common/api.js?v=20260604-superadmin-api"></script> <script src="../../common/api.js?v=20260604-superadmin-api"></script>
<script src="../../common/i18n.js?v=20260604"></script> <script src="../../common/i18n.js?v=20260604"></script>
<script src="../common/contact-card.js?v=20260629-user-contact"></script>
<script src="./script.js?v=20260604-api"></script> <script src="./script.js?v=20260604-api"></script>
</body> </body>
</html> </html>

View File

@ -368,6 +368,70 @@
</section> </section>
</div> </div>
<div
class="contact-modal"
id="contactModal"
hidden
aria-hidden="true"
>
<button
class="contact-backdrop"
id="contactBackdrop"
type="button"
aria-label="Close"
data-i18n-aria="contact_card.close"
></button>
<section
class="contact-dialog"
role="dialog"
aria-modal="true"
aria-labelledby="contactTitle"
>
<div class="contact-head">
<h2 id="contactTitle" data-i18n="contact_card.sheet_title">
WhatsApp Contact
</h2>
<button
class="contact-close"
id="contactCloseButton"
type="button"
aria-label="Close"
data-i18n-aria="contact_card.close"
>
×
</button>
</div>
<form class="contact-form" id="contactForm">
<label class="field contact-field">
<span data-i18n="contact_card.field_label"
>WhatsApp phone or link</span
>
<input
id="contactInput"
type="text"
inputmode="text"
autocomplete="off"
placeholder="+6512345678"
data-i18n-placeholder="contact_card.placeholder"
/>
</label>
<div
class="contact-status"
id="contactStatus"
hidden
></div>
<button
class="primary-button contact-save-button"
id="saveContactButton"
type="submit"
data-i18n="contact_card.save"
>
Save
</button>
</form>
</section>
</div>
<div class="home-indicator" aria-hidden="true"></div> <div class="home-indicator" aria-hidden="true"></div>
</div> </div>
@ -376,6 +440,6 @@
<script src="../../common/i18n.js?v=20260602"></script> <script src="../../common/i18n.js?v=20260602"></script>
<script src="../../common/params.js?v=20260604"></script> <script src="../../common/params.js?v=20260604"></script>
<script src="../../common/api.js?v=20260604-salary-wallet"></script> <script src="../../common/api.js?v=20260604-salary-wallet"></script>
<script src="./script.js?v=20260604-bd-wallet"></script> <script src="./script.js?v=20260629-contact-required"></script>
</body> </body>
</html> </html>

View File

@ -14,7 +14,9 @@
selectedPayee: null, selectedPayee: null,
isSearching: false, isSearching: false,
isSaving: false, isSaving: false,
isSavingContact: false,
isSubmitting: false, isSubmitting: false,
contactInfo: '',
historyItems: [], historyItems: [],
transferRateTiers: [], transferRateTiers: [],
}; };
@ -123,6 +125,12 @@
: null; : null;
} }
function userApi() {
if (window.HyAppAPI && window.HyAppAPI.user)
return window.HyAppAPI.user;
return window.HyApp && window.HyApp.api ? window.HyApp.api.user : null;
}
function commandId(prefix) { function commandId(prefix) {
return prefix + '-' + Date.now() + '-' + Math.random().toString(16).slice(2); return prefix + '-' + Date.now() + '-' + Math.random().toString(16).slice(2);
} }
@ -246,6 +254,20 @@
return ''; return '';
} }
function contactFrom(value) {
if (!value || typeof value !== 'object') return '';
var source = value.profile || value.user || value;
return String(
firstValue(source, [
'contact_info',
'contactInfo',
'whatsapp_phone',
'whatsappPhone',
'contact',
]) || ''
).trim();
}
function timeLabelFromMS(value) { function timeLabelFromMS(value) {
var number = Number(value || 0); var number = Number(value || 0);
if (!Number.isFinite(number) || number <= 0) return ''; if (!Number.isFinite(number) || number <= 0) return '';
@ -498,6 +520,10 @@
button.disabled = disabled; button.disabled = disabled;
} }
function needsContactForSubmit() {
return currentMode === 'exchange' || currentMode === 'transfer';
}
function normalizeAmount(event) { function normalizeAmount(event) {
var input = event.target; var input = event.target;
var raw = String(input.value || '').replace(/[^\d.]/g, ''); var raw = String(input.value || '').replace(/[^\d.]/g, '');
@ -543,6 +569,75 @@
document.body.classList.remove('modal-open'); document.body.classList.remove('modal-open');
} }
function setContactStatus(message) {
var node = $('contactStatus');
node.textContent = message || '';
node.hidden = !message;
}
function setContactSaving(saving) {
state.isSavingContact = saving;
$('saveContactButton').disabled = saving;
$('saveContactButton').textContent = saving
? t('contact_card.saving', 'Saving...')
: t('contact_card.save', 'Save');
}
function openContactModal() {
$('contactInput').value = state.contactInfo || '';
setContactStatus('');
$('contactModal').hidden = false;
$('contactModal').setAttribute('aria-hidden', 'false');
document.body.classList.add('modal-open');
window.setTimeout(function () {
$('contactInput').focus();
}, 0);
}
function closeContactModal() {
$('contactModal').hidden = true;
$('contactModal').setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
}
function saveContact(event) {
if (event) event.preventDefault();
var contact = String($('contactInput').value || '').trim();
if (!contact) {
setContactStatus(
t('contact_card.required', 'Enter WhatsApp contact.')
);
return;
}
var apiClient = userApi();
if (!apiClient || !apiClient.updateContact) {
setContactStatus(
t('contact_card.api_unavailable', 'Contact API unavailable.')
);
return;
}
// Exchange/Transfer 需要运营联系方式用于后台跟进;联系方式是用户主资料,保存成功后才允许继续提交工资兑换或转账请求。
setContactSaving(true);
setContactStatus('');
apiClient
.updateContact({ contact_info: contact })
.then(function (response) {
state.contactInfo = contactFrom(response) || contact;
closeContactModal();
updateSubmit();
toast(t('contact_card.saved', 'Saved.'));
})
.catch(function () {
setContactStatus(
t('contact_card.save_failed', 'Save failed.')
);
})
.finally(function () {
setContactSaving(false);
});
}
function saveAddress() { function saveAddress() {
var address = String($('trc20Address').value || '').trim(); var address = String($('trc20Address').value || '').trim();
if (!isValidTrc20(address)) { if (!isValidTrc20(address)) {
@ -681,6 +776,10 @@
toast(error); toast(error);
return; return;
} }
if (needsContactForSubmit() && !state.contactInfo) {
openContactModal();
return;
}
if (!isMock && currentMode === 'withdraw') { if (!isMock && currentMode === 'withdraw') {
toast( toast(
t( t(
@ -824,6 +923,9 @@
bindTap('salaryHistoryButton', openHistory); bindTap('salaryHistoryButton', openHistory);
bindTap('salaryHistoryBackdrop', closeHistory); bindTap('salaryHistoryBackdrop', closeHistory);
bindTap('salaryHistoryCloseButton', closeHistory); bindTap('salaryHistoryCloseButton', closeHistory);
bindTap('contactBackdrop', closeContactModal);
bindTap('contactCloseButton', closeContactModal);
$('contactForm').addEventListener('submit', saveContact);
} }
function renderStaticDynamicText() { function renderStaticDynamicText() {
@ -832,6 +934,9 @@
'1$ = {amount} golds' '1$ = {amount} golds'
).replace('{amount}', EXCHANGE_RATE.toLocaleString('en-US')); ).replace('{amount}', EXCHANGE_RATE.toLocaleString('en-US'));
if (!$('salaryHistoryModal').hidden) renderHistory(); if (!$('salaryHistoryModal').hidden) renderHistory();
if (!$('contactModal').hidden && !state.isSavingContact) {
$('saveContactButton').textContent = t('contact_card.save', 'Save');
}
updateSubmit(); updateSubmit();
renderPayee(); renderPayee();
} }
@ -843,6 +948,7 @@
renderStaticDynamicText(); renderStaticDynamicText();
loadOverview(); loadOverview();
loadPaymentInfo(); loadPaymentInfo();
loadContactInfo();
if (window.HyAppBridge) { if (window.HyAppBridge) {
window.HyAppBridge.ready({ window.HyAppBridge.ready({
page: 'withdraw-exchange', page: 'withdraw-exchange',
@ -909,6 +1015,30 @@
renderPaymentInfo(); renderPaymentInfo();
} }
function loadContactInfo() {
if (isMock) {
state.contactInfo = String(params.get('contact') || '').trim();
updateSubmit();
return;
}
var apiClient = userApi();
if (!apiClient || !apiClient.me) {
state.contactInfo = '';
updateSubmit();
return;
}
apiClient
.me()
.then(function (profile) {
state.contactInfo = contactFrom(profile);
updateSubmit();
})
.catch(function () {
state.contactInfo = '';
updateSubmit();
});
}
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', init);
window.addEventListener('hyapp:i18n-ready', renderStaticDynamicText); window.addEventListener('hyapp:i18n-ready', renderStaticDynamicText);
})(); })();

View File

@ -655,6 +655,21 @@ input {
background: rgba(14, 19, 24, 0.44); background: rgba(14, 19, 24, 0.44);
} }
.contact-modal {
position: fixed;
inset: 0;
z-index: 70;
display: flex;
align-items: flex-end;
justify-content: center;
}
.contact-backdrop {
position: absolute;
inset: 0;
background: rgba(14, 19, 24, 0.44);
}
.salary-history-dialog { .salary-history-dialog {
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -669,6 +684,16 @@ input {
box-shadow: 0 -14px 36px rgba(26, 35, 44, 0.16); box-shadow: 0 -14px 36px rgba(26, 35, 44, 0.16);
} }
.contact-dialog {
position: relative;
z-index: 1;
width: min(100%, 430px);
padding: 18px 18px calc(20px + env(safe-area-inset-bottom));
border-radius: 18px 18px 0 0;
background: #fff;
box-shadow: 0 -14px 36px rgba(26, 35, 44, 0.16);
}
.salary-history-head { .salary-history-head {
display: flex; display: flex;
align-items: center; align-items: center;
@ -677,6 +702,14 @@ input {
padding: 18px 18px 12px; padding: 18px 18px 12px;
} }
.contact-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.salary-history-head h2 { .salary-history-head h2 {
min-width: 0; min-width: 0;
margin: 0; margin: 0;
@ -689,6 +722,18 @@ input {
white-space: nowrap; white-space: nowrap;
} }
.contact-head h2 {
min-width: 0;
margin: 0;
overflow: hidden;
color: #24282e;
font-size: 18px;
font-weight: 950;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.salary-history-close { .salary-history-close {
display: flex; display: flex;
align-items: center; align-items: center;
@ -703,6 +748,44 @@ input {
line-height: 1; line-height: 1;
} }
.contact-close {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
flex: 0 0 34px;
border-radius: 50%;
background: #f3f5f5;
color: #676b72;
font-size: 28px;
line-height: 1;
}
.contact-form {
display: grid;
gap: 12px;
}
.contact-field input {
min-height: 52px;
padding: 0 14px;
border-radius: 8px;
background: var(--hy-theme-surface-soft, #fdfaff);
font-size: 15px;
font-weight: 850;
}
.contact-status {
color: var(--hy-theme-danger, #d95d72);
font-size: 13px;
font-weight: 850;
}
.contact-save-button {
margin-top: 0;
}
.salary-history-summary { .salary-history-summary {
display: flex; display: flex;
align-items: center; align-items: center;