105 lines
5.4 KiB
JavaScript
105 lines
5.4 KiB
JavaScript
import { apiClient } from '/static/portal/common/api-client.js?v=20260913';
|
|
import { requireCustomerOnly } from '/static/portal/common/auth.js?v=20260913';
|
|
import { escapeHtml, formatDateTime } from '/static/portal/common/formatters.js';
|
|
import { mountShell } from '/static/portal/common/layout/app-shell.js';
|
|
import { showToast } from '/static/portal/common/notifications.js';
|
|
import { renderError, renderLoading } from '/static/portal/common/state-view.js';
|
|
|
|
if (requireCustomerOnly()) {
|
|
mountShell({ active: 'risk-questionnaire', mode: 'customer' });
|
|
const root = document.querySelector('[data-questionnaire]');
|
|
const progressText = document.querySelector('[data-progress-text]');
|
|
const progressBar = document.querySelector('[data-progress-bar]');
|
|
const state = { questions: [], declaration: '', answers: {}, index: 0, controller: null };
|
|
|
|
function updateProgress(completed = false) {
|
|
const total = state.questions.length || 1;
|
|
const current = completed ? total : Math.min(state.index + 1, total);
|
|
progressText.textContent = completed ? '已完成' : `${current} / ${total}`;
|
|
progressBar.style.width = `${completed ? 100 : (current / total) * 100}%`;
|
|
}
|
|
|
|
function renderComplete(validUntil) {
|
|
updateProgress(true);
|
|
root.innerHTML = `<div class="questionnaire-complete"><div><div class="questionnaire-complete__mark" aria-hidden="true">✓</div><h2>风险测评已完成</h2><p>${validUntil ? `本次测评有效至 ${escapeHtml(formatDateTime(validUntil))}。` : '当前测评仍在有效期内。'}您现在可以访问资产、持仓与模拟交易服务。</p><a class="button button--primary" href="/portal/customer/dashboard/">进入资产总览</a></div></div>`;
|
|
}
|
|
|
|
function renderQuestion() {
|
|
const question = state.questions[state.index];
|
|
if (!question) return;
|
|
updateProgress();
|
|
const options = question.options.map((label, index) => {
|
|
const value = index + 1;
|
|
return `<label class="questionnaire-option"><input type="radio" name="answer" value="${value}"${state.answers[question.id] === value ? ' checked' : ''}><span>${escapeHtml(label)}</span></label>`;
|
|
}).join('');
|
|
const finalStep = state.index === state.questions.length - 1;
|
|
root.innerHTML = `<form class="questionnaire-content" data-question-form><p class="questionnaire-count">第 ${state.index + 1} 题,共 ${state.questions.length} 题</p><h2 class="questionnaire-title">${escapeHtml(question.title)}</h2><div class="questionnaire-options">${options}</div>${finalStep ? `<label class="questionnaire-declaration"><input type="checkbox" name="declaration"><span>${escapeHtml(state.declaration)}</span></label>` : ''}<div class="form-alert" data-question-alert></div><div class="questionnaire-actions"><button class="button" type="button" data-previous${state.index === 0 ? ' disabled' : ''}>上一题</button><button class="button button--primary" type="submit">${finalStep ? '提交测评' : '下一题'}</button></div></form>`;
|
|
root.querySelector('[data-previous]')?.addEventListener('click', () => { state.index -= 1; renderQuestion(); });
|
|
root.querySelector('[data-question-form]').addEventListener('submit', submitStep);
|
|
}
|
|
|
|
async function submitStep(event) {
|
|
event.preventDefault();
|
|
const form = event.currentTarget;
|
|
const formData = new FormData(form);
|
|
const selected = Number(formData.get('answer'));
|
|
const alert = form.querySelector('[data-question-alert]');
|
|
if (!selected) {
|
|
alert.textContent = '请选择一项后继续。';
|
|
alert.classList.add('form-alert--visible');
|
|
return;
|
|
}
|
|
state.answers[state.questions[state.index].id] = selected;
|
|
const finalStep = state.index === state.questions.length - 1;
|
|
if (!finalStep) {
|
|
state.index += 1;
|
|
renderQuestion();
|
|
return;
|
|
}
|
|
if (!formData.get('declaration')) {
|
|
alert.textContent = '请阅读并确认声明后提交。';
|
|
alert.classList.add('form-alert--visible');
|
|
return;
|
|
}
|
|
const submit = form.querySelector('[type="submit"]');
|
|
submit.disabled = true;
|
|
submit.textContent = '正在提交';
|
|
try {
|
|
const response = await apiClient.post('ONB002', { answers: state.answers, declaration_accepted: true });
|
|
showToast('风险测评提交成功');
|
|
renderComplete(response.data?.valid_until);
|
|
} catch (error) {
|
|
apiClient.reportError(error);
|
|
alert.textContent = error.message || '测评提交失败,请稍后重试。';
|
|
alert.classList.add('form-alert--visible');
|
|
submit.disabled = false;
|
|
submit.textContent = '提交测评';
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
state.controller?.abort();
|
|
state.controller = new AbortController();
|
|
renderLoading(root, 3);
|
|
try {
|
|
const response = await apiClient.get('ONB001', { signal: state.controller.signal });
|
|
if (!response.data?.required) {
|
|
renderComplete(response.data?.valid_until);
|
|
return;
|
|
}
|
|
const questionnaire = response.data.questionnaire || {};
|
|
state.questions = Array.isArray(questionnaire.questions) ? questionnaire.questions : [];
|
|
state.declaration = questionnaire.declaration || '';
|
|
if (!state.questions.length) throw new Error('服务端未返回有效问卷');
|
|
renderQuestion();
|
|
} catch (error) {
|
|
apiClient.reportError(error);
|
|
renderError(root, error, load);
|
|
}
|
|
}
|
|
|
|
window.addEventListener('pagehide', () => state.controller?.abort());
|
|
apiClient.track('page_view', { page: 'risk-questionnaire', portal: 'customer' });
|
|
load();
|
|
}
|