Files
group_fqcd_jr/app/static/portal/customer/risk-questionnaire/risk-questionnaire.js
T

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();
}