| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- {include file="header" /}
- {php}
- $feedbackContactPhone = '';
- $feedbackContactTel = '';
- foreach (['cooperation_phone', 'contact_phone', 'service_phone', 'phone', 'tel'] as $phoneConfigKey) {
- $phoneConfigValue = trim((string)config('site.' . $phoneConfigKey));
- if ($phoneConfigValue === '') {
- continue;
- }
- $phoneDigits = '';
- $phoneAllowed = true;
- $phoneTel = '';
- foreach (preg_split('//u', $phoneConfigValue, -1, PREG_SPLIT_NO_EMPTY) as $phoneChar) {
- if (ctype_digit($phoneChar)) {
- $phoneDigits .= $phoneChar;
- $phoneTel .= $phoneChar;
- continue;
- }
- if ($phoneChar === '+') {
- $phoneTel .= $phoneChar;
- continue;
- }
- if (!in_array($phoneChar, ['(', ')', '-', ' '], true)) {
- $phoneAllowed = false;
- break;
- }
- }
- if ($phoneAllowed && strlen($phoneDigits) >= 6 && mb_strlen($phoneConfigValue, 'UTF-8') <= 40) {
- $feedbackContactPhone = $phoneConfigValue;
- $feedbackContactTel = $phoneTel;
- break;
- }
- }
- {/php}
- <link rel="stylesheet" href="/assets/xtu/cooperation-topbar.css?v=20260508-5">
- <link rel="stylesheet" href="/assets/xtu/cooperation.css?v=20260515-feedback-1">
- <main class="cooperation-page feedback-message-page" id="feedbackMessagePage" style="--cooperation-bg:url('{$category.image|default='/assets/xtu/vendor/img/appbanner.jpg'}')">
- <div class="cooperation-topbar">
- <div class="cooperation-topbar-inner">
- <div class="cooperation-topbar-title">{$title|default=cms_t('feedback_message_title','意见反馈')}</div>
- </div>
- </div>
- <div class="cooperation-shell">
- <section class="cooperation-copy">
- <div class="cooperation-bar-inner">
- <h1>{$title|default=cms_t('feedback_message_title','意见反馈')}</h1>
- <p>{if getCategory($catid,'description')}{:getCategory($catid,'description')}{elseif !empty($category.description) /}{$category.description}{else /}{:cms_t('feedback_message_intro','欢迎提出您的意见、建议或问题反馈,我们会认真阅读并持续优化服务体验。')}{/if}</p>
- </div>
- </section>
- <section class="cooperation-card" aria-labelledby="feedbackMessageFormTitle">
- <div class="cooperation-card-head">
- <h2 id="feedbackMessageFormTitle">{:cms_t('feedback_message_form_title','填写反馈')}</h2>
- <p>{:cms_t('feedback_message_form_desc','请留下您的联系方式和反馈内容,便于我们进一步沟通处理。')}</p>
- </div>
- <form id="feedbackMessageForm" class="cooperation-form" data-submit-url="{:url('index/feedback/message')}">
- <input type="hidden" name="lang" id="feedbackMessageLang" value="{$site_lang|default='cn'}">
- <input type="hidden" name="source_page" id="feedbackMessageSourcePage" value="">
- <input type="hidden" name="form_token" id="feedbackMessageFormToken" value="{:md5(session_id().'message_feedback')}">
- <div class="cooperation-honeypot" aria-hidden="true"><input type="text" name="website" tabindex="-1" autocomplete="off" value=""></div>
- <div class="cooperation-form-grid">
- <div class="cooperation-field">
- <select name="feedback_type" id="feedbackMessageType" aria-label="{:cms_t('feedback_message_type_label','反馈类型')}" required>
- <option value="">{:cms_t('feedback_message_type_placeholder','反馈类型 *')}</option>
- <option value="suggestion">{:cms_t('feedback_message_type_suggestion','意见建议')}</option>
- <option value="problem">{:cms_t('feedback_message_type_problem','问题反馈')}</option>
- <option value="complaint">{:cms_t('feedback_message_type_complaint','投诉建议')}</option>
- <option value="other">{:cms_t('feedback_message_type_other','其他')}</option>
- </select>
- </div>
- <div class="cooperation-field"><input type="text" name="name" placeholder="{:cms_t('cooperation_name_placeholder','姓名 *')}" maxlength="80" minlength="2" autocomplete="name" required></div>
- <div class="cooperation-field"><input type="tel" name="phone" placeholder="{:cms_t('cooperation_phone_placeholder','电话 *')}" maxlength="40" minlength="6" autocomplete="tel" inputmode="tel" pattern="[0-9+()\-\s]{6,40}" required></div>
- <div class="cooperation-field"><input type="email" name="email" placeholder="{:cms_t('cooperation_email_placeholder','电子邮箱 *')}" maxlength="120" autocomplete="email" required></div>
- <div class="cooperation-field is-full">
- <textarea name="content" id="feedbackMessageContent" placeholder="{:cms_t('feedback_message_content_placeholder','请填写反馈内容 *')}" maxlength="500" required></textarea>
- <span class="cooperation-count" id="feedbackMessageContentCount">0/500</span>
- </div>
- <div class="cooperation-field is-full" id="feedbackMessageCaptchaWrap" style="display:none;">
- <label class="screen-reader-text" for="feedbackMessageCaptchaInput">{:cms_t('cooperation_captcha_label','验证码 *')}</label>
- <div class="cooperation-captcha-row">
- <input type="text" name="verify" id="feedbackMessageCaptchaInput" placeholder="{:cms_t('cooperation_captcha_placeholder','请输入验证码')}" maxlength="6" autocomplete="off">
- <a href="javascript:;" class="cooperation-captcha-img-wrap" id="feedbackMessageCaptchaImageLink" aria-label="{:cms_t('cooperation_refresh_captcha','刷新验证码')}"><img src="" alt="{:cms_t('cooperation_captcha_label','验证码')}" id="feedbackMessageCaptchaImage"></a>
- </div>
- </div>
- <div class="cooperation-checks">
- <label><input type="checkbox" name="privacy_agree" value="1" required><span>{:cms_t('cooperation_privacy_prefix','已阅读并同意')}<a href="{:getCategory(44,'url')}" target="_blank" rel="noopener">{:cms_t('cooperation_privacy_link','《隐私政策》')}</a></span></label>
- </div>
- <button class="cooperation-submit" id="feedbackMessageSubmitBtn" type="submit">{:cms_t('cooperation_submit','提交信息')}</button>
- <div class="cooperation-message" id="feedbackMessageMessage" role="status" aria-live="polite"></div>
- <div class="cooperation-contact">
- {if condition="$feedbackContactPhone neq ''"}
- <a href="tel:{$feedbackContactTel}" class="cooperation-contact-link"><i class="bi bi-telephone"></i> {$feedbackContactPhone}</a>
- {/if}
- </div>
- </div>
- </form>
- </section>
- </div>
- </main>
- {if condition="!empty($content)"}
- <section class="cooperation-content">
- <div class="cooperation-content-inner">{$content|raw}</div>
- </section>
- {/if}
- <script>
- document.body.classList.add('cooperation-page-body');
- (function () {
- const siteHeader = document.getElementById('siteHeader');
- const syncTopbarPosition = function () {
- if (!siteHeader) return;
- const headerRect = siteHeader.getBoundingClientRect();
- document.documentElement.style.setProperty('--xtu-header-height', Math.max(0, Math.ceil(headerRect.bottom)) + 'px');
- };
- syncTopbarPosition();
- window.addEventListener('load', syncTopbarPosition);
- window.addEventListener('resize', syncTopbarPosition);
- window.addEventListener('scroll', syncTopbarPosition, { passive: true });
- if (window.ResizeObserver && siteHeader) new ResizeObserver(syncTopbarPosition).observe(siteHeader);
- })();
- (function () {
- const form = document.getElementById('feedbackMessageForm');
- if (!form) return;
- const submitBtn = document.getElementById('feedbackMessageSubmitBtn');
- const messageBox = document.getElementById('feedbackMessageMessage');
- const contentInput = document.getElementById('feedbackMessageContent');
- const contentCount = document.getElementById('feedbackMessageContentCount');
- const sourcePageInput = document.getElementById('feedbackMessageSourcePage');
- const submitUrl = form.getAttribute('data-submit-url') || '/index/feedback/message';
- const text = {
- required: {:json_encode(cms_t('cooperation_required','请完整填写必填信息,并同意隐私政策'), JSON_UNESCAPED_UNICODE)},
- invalidEmail: {:json_encode(cms_t('cooperation_invalid_email_hint','请输入正确的电子邮箱格式,如 name@example.com'), JSON_UNESCAPED_UNICODE)},
- invalidPhone: {:json_encode(cms_t('cooperation_invalid_phone','请输入正确的电话号码,可包含数字、+、-、空格和括号'), JSON_UNESCAPED_UNICODE)},
- fieldTooLong: {:json_encode(cms_t('cooperation_field_too_long','输入内容超过长度限制'), JSON_UNESCAPED_UNICODE)},
- captchaHint: {:json_encode(cms_t('cooperation_captcha_hint','当前提交较频繁,请先完成验证码'), JSON_UNESCAPED_UNICODE)},
- captchaError: {:json_encode(cms_t('cooperation_captcha_error','验证码错误,请重新输入'), JSON_UNESCAPED_UNICODE)},
- sending: {:json_encode(cms_t('cooperation_sending','正在提交...'), JSON_UNESCAPED_UNICODE)},
- success: {:json_encode(cms_t('feedback_message_success','提交成功,感谢您的反馈'), JSON_UNESCAPED_UNICODE)},
- failed: {:json_encode(cms_t('cooperation_failed','提交失败,请稍后再试'), JSON_UNESCAPED_UNICODE)}
- };
- function setMessage(msg, type) {
- if (!messageBox) return;
- messageBox.textContent = msg || '';
- messageBox.className = 'cooperation-message' + (type ? ' is-' + type : '');
- }
- function focusField(input) {
- if (!input) return;
- input.scrollIntoView({ behavior: 'smooth', block: 'center' });
- input.focus({ preventScroll: true });
- }
- function showSuccessModal(message) {
- let modal = document.getElementById('feedbackMessageSuccessModal');
- if (!modal) {
- modal = document.createElement('div');
- modal.id = 'feedbackMessageSuccessModal';
- modal.className = 'custom-modal custom-modal-cooperation-success';
- modal.setAttribute('aria-hidden', 'true');
- modal.innerHTML = '<div class="custom-modal-dialog cooperation-success-modal-dialog"><div class="custom-modal-box cooperation-success-modal-box"><button class="custom-modal-close" type="button" data-close-modal="feedbackMessageSuccessModal" aria-label="关闭">×</button><div class="cooperation-success-icon"><i class="bi bi-check2-circle"></i></div><h3 class="cooperation-success-title">提交成功</h3><p class="cooperation-success-text"></p><div class="cooperation-success-actions"><button type="button" class="cooperation-success-btn" data-close-modal="feedbackMessageSuccessModal">知道了</button></div></div></div>';
- document.body.appendChild(modal);
- modal.addEventListener('click', function (e) { if (e.target === modal) closeSuccessModal(); });
- }
- const textNode = modal.querySelector('.cooperation-success-text');
- if (textNode) textNode.textContent = message || text.success;
- modal.classList.add('is-open');
- modal.setAttribute('aria-hidden', 'false');
- document.body.classList.add('modal-open');
- modal.querySelector('.cooperation-success-btn')?.focus();
- }
- function closeSuccessModal() {
- const modal = document.getElementById('feedbackMessageSuccessModal');
- if (!modal) return;
- modal.classList.remove('is-open');
- modal.setAttribute('aria-hidden', 'true');
- if (!document.querySelector('.custom-modal.is-open') && !document.querySelector('.header-inline-search.is-open')) document.body.classList.remove('modal-open');
- }
- document.addEventListener('click', function (e) {
- const closeBtn = e.target.closest && e.target.closest('[data-close-modal="feedbackMessageSuccessModal"]');
- if (closeBtn) { e.preventDefault(); closeSuccessModal(); }
- });
- document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeSuccessModal(); });
- function updateContentCount() {
- if (!contentInput || !contentCount) return;
- const max = Number(contentInput.getAttribute('maxlength') || 500);
- const current = contentInput.value.length;
- contentCount.textContent = current + '/' + max;
- contentCount.style.color = current > max * 0.9 ? '#d92d20' : '#98a2b3';
- }
- function isValidEmail(value) {
- const email = String(value || '').trim();
- if (!email || email.length > 120) return false;
- return /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(email);
- }
- function isValidPhone(value) {
- const phone = String(value || '').trim();
- if (phone.length < 6 || phone.length > 40) return false;
- if (!/^[0-9+()\-\s]+$/.test(phone)) return false;
- return phone.replace(/\D/g, '').length >= 6;
- }
- function limitInputLength(input) {
- if (!input) return;
- const max = Number(input.getAttribute('maxlength') || 0);
- if (max > 0 && input.value.length > max) input.value = input.value.slice(0, max);
- }
- function showCaptcha(force) {
- const wrap = document.getElementById('feedbackMessageCaptchaWrap');
- const input = document.getElementById('feedbackMessageCaptchaInput');
- const image = document.getElementById('feedbackMessageCaptchaImage');
- const link = document.getElementById('feedbackMessageCaptchaImageLink');
- if (!wrap || !input || !image) return;
- wrap.style.display = force ? '' : 'none';
- input.required = !!force;
- if (force && image.src.indexOf('/captcha') === -1) image.src = '/index/feedback/captcha?t=' + Date.now();
- if (link) link.onclick = function () { image.src = '/index/feedback/captcha?t=' + Date.now(); return false; };
- }
- Array.prototype.forEach.call(form.querySelectorAll('input[maxlength], textarea[maxlength]'), function (input) {
- input.addEventListener('input', function () { limitInputLength(input); if (input === contentInput) updateContentCount(); });
- });
- if (contentInput) { contentInput.addEventListener('input', updateContentCount); updateContentCount(); }
- form.addEventListener('submit', async function (e) {
- e.preventDefault();
- const emailInput = form.querySelector('input[name="email"]');
- const phoneInput = form.querySelector('input[name="phone"]');
- const privacyInput = form.querySelector('input[name="privacy_agree"]');
- if (!form.checkValidity() || !privacyInput || !privacyInput.checked) {
- form.reportValidity && form.reportValidity();
- setMessage(text.required, 'error');
- return;
- }
- const captchaWrap = document.getElementById('feedbackMessageCaptchaWrap');
- const captchaInput = document.getElementById('feedbackMessageCaptchaInput');
- if (captchaWrap && captchaWrap.style.display !== 'none' && (!captchaInput || !captchaInput.value.trim())) {
- focusField(captchaInput); setMessage(text.captchaHint, 'error'); return;
- }
- if (!isValidPhone(phoneInput ? phoneInput.value.trim() : '')) { focusField(phoneInput); setMessage(text.invalidPhone, 'error'); return; }
- if (!isValidEmail(emailInput ? emailInput.value.trim() : '')) { focusField(emailInput); setMessage(text.invalidEmail, 'error'); return; }
- Array.prototype.forEach.call(form.querySelectorAll('input[type="text"], input[type="tel"], input[type="email"], textarea'), function (input) { input.value = input.value.trim(); });
- if (sourcePageInput) sourcePageInput.value = window.location.href;
- if (submitBtn) submitBtn.disabled = true;
- setMessage(text.sending, 'loading');
- try {
- const body = new URLSearchParams(new FormData(form));
- const res = await fetch(submitUrl, { method:'POST', headers:{'X-Requested-With':'XMLHttpRequest','Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}, body: body.toString() });
- const data = await res.json();
- if (data.code === 1) {
- form.reset(); updateContentCount(); showCaptcha(false); setMessage('', ''); showSuccessModal(data.msg || text.success);
- } else {
- if ((data.data && Number(data.data.need_captcha || 0) === 1) || String(data.msg || '').indexOf(text.captchaError) >= 0) showCaptcha(true);
- setMessage(data.msg || text.failed, 'error');
- }
- } catch (err) {
- setMessage(text.failed, 'error');
- } finally {
- if (submitBtn) submitBtn.disabled = false;
- }
- });
- })();
- </script>
- {include file="footer-links" /}
- {include file="footer" /}
|