466 lines
17 KiB
JavaScript
466 lines
17 KiB
JavaScript
(function (root, factory) {
|
|
if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory(root);
|
|
return;
|
|
}
|
|
|
|
root.FeedbackPages = factory(root);
|
|
if (root.document) {
|
|
root.document.addEventListener('DOMContentLoaded', function () {
|
|
root.FeedbackPages.init();
|
|
});
|
|
}
|
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
|
'use strict';
|
|
|
|
var FEEDBACK_OPTION_STATES = ['ACTIVE', 'DISABLED', 'UNKNOWN'];
|
|
var HANDLE_STATUS = {
|
|
'0': '待处理',
|
|
'1': '处理中',
|
|
'2': '已处理',
|
|
'3': '已关闭'
|
|
};
|
|
|
|
function escapeHtml(value) {
|
|
return String(value === undefined || value === null ? '' : value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function normalizeId(value) {
|
|
var text;
|
|
|
|
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
|
|
if (value === undefined || value === null) return '';
|
|
text = String(value).trim();
|
|
return /^[1-9][0-9]*$/.test(text) ? text : '';
|
|
}
|
|
|
|
function optionalText(value) {
|
|
if (value === undefined || value === null) return '';
|
|
return String(value).trim();
|
|
}
|
|
|
|
function getFeedbackId(search) {
|
|
var params = new URLSearchParams(String(search === undefined && root.location ? root.location.search : search || '').replace(/^\?/, ''));
|
|
|
|
return normalizeId(params.get('feedbackId'));
|
|
}
|
|
|
|
function buildFeedbackBody(values) {
|
|
var source = values || {};
|
|
var body = {};
|
|
var feedbackType = optionalText(source.feedbackType);
|
|
var feedbackContent = optionalText(source.feedbackContent);
|
|
var contactInfo = optionalText(source.contactInfo);
|
|
|
|
if (feedbackType) body.feedbackType = feedbackType;
|
|
if (feedbackContent) body.feedbackContent = feedbackContent;
|
|
if (contactInfo) body.contactInfo = contactInfo;
|
|
return body;
|
|
}
|
|
|
|
function validateFeedbackBody(body, allowedFeedbackTypes) {
|
|
var source = body || {};
|
|
var allowedTypes = Array.isArray(allowedFeedbackTypes) ? allowedFeedbackTypes : [];
|
|
|
|
if (!optionalText(source.feedbackContent)) return '请填写反馈内容';
|
|
if (source.feedbackType && allowedTypes.length && allowedTypes.indexOf(source.feedbackType) < 0) {
|
|
return '反馈类型无效';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function normalizeFeedbackTypeOptions(data) {
|
|
if (!Array.isArray(data)) return [];
|
|
return data.reduce(function (options, item) {
|
|
var source = item || {};
|
|
var value = optionalText(source.value);
|
|
var label = optionalText(source.label);
|
|
|
|
if (value && label && source.enabled === true) options.push({ value: value, label: label });
|
|
return options;
|
|
}, []);
|
|
}
|
|
|
|
function normalizeFeedback(item) {
|
|
var source = item || {};
|
|
var feedbackId = normalizeId(source.feedbackId);
|
|
var feedbackType = optionalText(source.feedbackType);
|
|
var feedbackTypeLabel = optionalText(source.feedbackTypeLabel) || feedbackType;
|
|
var feedbackTypeOptionState = optionalText(source.feedbackTypeOptionState);
|
|
var feedbackContent = optionalText(source.feedbackContent);
|
|
var handleStatus = String(source.handleStatus === undefined || source.handleStatus === null ? '' : source.handleStatus);
|
|
var status = String(source.status === undefined || source.status === null ? '' : source.status);
|
|
|
|
if (!feedbackId || !feedbackContent || !feedbackType || !feedbackTypeLabel ||
|
|
FEEDBACK_OPTION_STATES.indexOf(feedbackTypeOptionState) < 0 ||
|
|
!Object.prototype.hasOwnProperty.call(HANDLE_STATUS, handleStatus) ||
|
|
['0', '1'].indexOf(status) < 0) {
|
|
return null;
|
|
}
|
|
return {
|
|
feedbackId: feedbackId,
|
|
feedbackType: feedbackType,
|
|
feedbackTypeLabel: feedbackTypeLabel,
|
|
feedbackTypeOptionState: feedbackTypeOptionState,
|
|
feedbackContent: feedbackContent,
|
|
contactInfo: optionalText(source.contactInfo),
|
|
handleStatus: handleStatus,
|
|
handleResult: optionalText(source.handleResult),
|
|
handleTime: optionalText(source.handleTime),
|
|
status: status,
|
|
remark: optionalText(source.remark)
|
|
};
|
|
}
|
|
|
|
function normalizeFeedbackList(data) {
|
|
var items;
|
|
|
|
if (!Array.isArray(data)) return [];
|
|
items = data.map(normalizeFeedback);
|
|
return items.some(function (item) { return !item; }) ? [] : items;
|
|
}
|
|
|
|
function findFeedbackById(data, feedbackId) {
|
|
var targetId = normalizeId(feedbackId);
|
|
var items = normalizeFeedbackList(data);
|
|
var match;
|
|
|
|
if (!targetId) return null;
|
|
match = items.find(function (item) { return item.feedbackId === targetId; });
|
|
return match || null;
|
|
}
|
|
|
|
function buildFeedbackDetailUrl(feedbackId, detailPage) {
|
|
var id = normalizeId(feedbackId);
|
|
var params;
|
|
|
|
if (!id) return '';
|
|
params = new URLSearchParams();
|
|
params.set('feedbackId', id);
|
|
return (detailPage || 'ticket-detail.html') + '?' + params.toString();
|
|
}
|
|
|
|
function renderFeedbackList(data, options) {
|
|
var settings = options || {};
|
|
var items = normalizeFeedbackList(data);
|
|
var detailPage = settings.detailPage || 'ticket-detail.html';
|
|
|
|
if (!items.length) return '<div class="api-empty">当前没有反馈记录</div>';
|
|
return items.map(function (item) {
|
|
return '<a class="module-row" href="' +
|
|
escapeHtml(buildFeedbackDetailUrl(item.feedbackId, detailPage)) +
|
|
'"><div><h3>' + escapeHtml(item.feedbackTypeLabel) +
|
|
'</h3><p>' + escapeHtml(item.feedbackContent) +
|
|
'</p></div><span class="pill">' + HANDLE_STATUS[item.handleStatus] +
|
|
'</span></a>';
|
|
}).join('');
|
|
}
|
|
|
|
function renderFeedbackDetail(item) {
|
|
var feedback = normalizeFeedback(item);
|
|
var details;
|
|
|
|
if (!feedback) return '<div class="api-empty">反馈记录不存在或无权查看</div>';
|
|
details = [
|
|
feedback.contactInfo ? '<dt>提交联系方式</dt><dd>' + escapeHtml(feedback.contactInfo) + '</dd>' : '',
|
|
'<dt>处理状态</dt><dd>' + HANDLE_STATUS[feedback.handleStatus] + '</dd>',
|
|
feedback.handleResult ? '<dt>处理结果</dt><dd>' + escapeHtml(feedback.handleResult) + '</dd>' : '',
|
|
feedback.handleTime ? '<dt>处理时间</dt><dd>' + escapeHtml(feedback.handleTime) + '</dd>' : '',
|
|
feedback.remark ? '<dt>备注</dt><dd>' + escapeHtml(feedback.remark) + '</dd>' : ''
|
|
].filter(Boolean).join('');
|
|
|
|
return '<article class="module-panel"><div class="module-kicker">' +
|
|
escapeHtml(feedback.feedbackTypeLabel) +
|
|
'</div><h2>问题说明</h2><p>' + escapeHtml(feedback.feedbackContent) +
|
|
'</p><dl class="detail-list">' + details + '</dl></article>';
|
|
}
|
|
|
|
function shouldRedirectToLogin(api, error) {
|
|
var status = error && (error.status || error.code);
|
|
|
|
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
|
|
}
|
|
|
|
function redirectToLogin(api) {
|
|
if (api && api.clearToken) api.clearToken();
|
|
if (!root.location) return;
|
|
if (typeof root.location.replace === 'function') root.location.replace('login.html');
|
|
else root.location.href = 'login.html';
|
|
}
|
|
|
|
function isForbidden(error) {
|
|
return Number(error && (error.status || error.code)) === 403;
|
|
}
|
|
|
|
function getFormValues(form) {
|
|
var values = {};
|
|
|
|
Array.prototype.forEach.call(form.querySelectorAll('[name]'), function (field) {
|
|
values[field.name] = field.value;
|
|
});
|
|
return values;
|
|
}
|
|
|
|
function setApiState(target, type, message) {
|
|
if (!target) return;
|
|
if (root.ProfileUI && root.ProfileUI.setApiState && type) {
|
|
root.ProfileUI.setApiState(target, type, message);
|
|
return;
|
|
}
|
|
target.textContent = message || '';
|
|
}
|
|
|
|
function setStatus(type, message) {
|
|
var status = root.document && root.document.querySelector('[data-feedback-status]');
|
|
|
|
setApiState(status, type, message);
|
|
}
|
|
|
|
function setFeedbackList(list, items, detailPage) {
|
|
if (!list) return;
|
|
if (!items.length) {
|
|
setApiState(list, 'empty', '当前没有反馈记录。');
|
|
return;
|
|
}
|
|
list.innerHTML = renderFeedbackList(items, { detailPage: detailPage || 'ticket-detail.html' });
|
|
}
|
|
|
|
function setPendingButton(button, pending, label) {
|
|
if (!button) return;
|
|
if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
|
|
button.disabled = pending;
|
|
button.textContent = pending ? label : button.dataset.defaultLabel;
|
|
}
|
|
|
|
async function loadFeedbackTypeOptions(api, select) {
|
|
var options = normalizeFeedbackTypeOptions(await api.businessDictionary('gen_feedback_type'));
|
|
|
|
if (!options.length) throw new Error('反馈类型选项响应无效');
|
|
if (select) {
|
|
select.innerHTML = '<option value="">请选择反馈类型(可选)</option>' + options.map(function (item) {
|
|
return '<option value="' + escapeHtml(item.value) + '">' + escapeHtml(item.label) + '</option>';
|
|
}).join('');
|
|
}
|
|
return options;
|
|
}
|
|
|
|
async function readMyFeedback(api) {
|
|
var data = await api.myFeedback();
|
|
var items = normalizeFeedbackList(data);
|
|
|
|
if (!Array.isArray(data) || items.length !== data.length) {
|
|
throw new Error('反馈列表响应无效');
|
|
}
|
|
return items;
|
|
}
|
|
|
|
async function initFeedbackFormPage() {
|
|
var form = root.document && root.document.querySelector('[data-feedback-form]');
|
|
var list = root.document && root.document.querySelector('[data-feedback-list]');
|
|
var refresh = root.document && root.document.querySelector('[data-feedback-refresh]');
|
|
var page = root.document && root.document.querySelector('[data-feedback-page]');
|
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
|
var pageMode = page && page.getAttribute('data-feedback-page');
|
|
var feedbackTypeSelect = form && form.querySelector('[data-feedback-type-options]');
|
|
var allowedFeedbackTypes = [];
|
|
var writePending = false;
|
|
|
|
if (!form) return;
|
|
if (shouldRedirectToLogin(api)) {
|
|
redirectToLogin(api);
|
|
return;
|
|
}
|
|
try {
|
|
allowedFeedbackTypes = (await loadFeedbackTypeOptions(api, feedbackTypeSelect)).map(function (item) {
|
|
return item.value;
|
|
});
|
|
} catch (error) {
|
|
if (feedbackTypeSelect) feedbackTypeSelect.innerHTML = '<option value="">暂不选择反馈类型</option>';
|
|
}
|
|
|
|
async function loadList() {
|
|
var items;
|
|
|
|
if (!list) return [];
|
|
setApiState(list, 'loading', '正在读取反馈记录…');
|
|
setStatus('loading', '正在读取反馈记录…');
|
|
try {
|
|
items = await readMyFeedback(api);
|
|
setFeedbackList(list, items, 'ticket-detail.html');
|
|
setStatus('', '');
|
|
return items;
|
|
} catch (error) {
|
|
if (shouldRedirectToLogin(api, error)) {
|
|
redirectToLogin(api);
|
|
return [];
|
|
}
|
|
setApiState(list, isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看反馈记录。' : '读取反馈记录失败,请稍后重试。');
|
|
setStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看反馈记录。' : (error.message || '读取反馈记录失败,请稍后重试。'));
|
|
return [];
|
|
}
|
|
}
|
|
|
|
if (refresh) refresh.addEventListener('click', async function () {
|
|
setPendingButton(refresh, true, '正在刷新…');
|
|
try {
|
|
await loadList();
|
|
} finally {
|
|
setPendingButton(refresh, false, '正在刷新…');
|
|
}
|
|
});
|
|
form.addEventListener('submit', async function (event) {
|
|
var body;
|
|
var validation;
|
|
var created;
|
|
var refreshed;
|
|
var button = form.querySelector('[data-feedback-submit]');
|
|
|
|
event.preventDefault();
|
|
if (writePending) return;
|
|
body = buildFeedbackBody(getFormValues(form));
|
|
validation = validateFeedbackBody(body, allowedFeedbackTypes);
|
|
if (validation) {
|
|
setStatus('error', validation);
|
|
return;
|
|
}
|
|
writePending = true;
|
|
setPendingButton(button, true, '正在提交…');
|
|
setStatus('loading', '正在提交反馈…');
|
|
try {
|
|
created = normalizeFeedback(await api.submitFeedback(body));
|
|
if (!created) throw new Error('提交响应缺少有效反馈信息');
|
|
refreshed = await readMyFeedback(api);
|
|
if (!refreshed.some(function (item) { return item.feedbackId === created.feedbackId; })) {
|
|
throw new Error('提交后无法读取同一反馈记录');
|
|
}
|
|
if (pageMode === 'ticket') {
|
|
if (root.location) root.location.href = buildFeedbackDetailUrl(created.feedbackId);
|
|
return;
|
|
}
|
|
setFeedbackList(list, refreshed, 'ticket-detail.html');
|
|
form.reset();
|
|
setStatus('', '反馈已提交');
|
|
} catch (error) {
|
|
if (shouldRedirectToLogin(api, error)) {
|
|
redirectToLogin(api);
|
|
return;
|
|
}
|
|
setStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限提交反馈。' : (error.message || '提交反馈失败,请稍后重试。'));
|
|
} finally {
|
|
writePending = false;
|
|
setPendingButton(button, false, '正在提交…');
|
|
}
|
|
});
|
|
await loadList();
|
|
}
|
|
|
|
async function initFeedbackListPage() {
|
|
var page = root.document && root.document.querySelector('[data-feedback-list-page]');
|
|
var list = root.document && root.document.querySelector('[data-feedback-list]');
|
|
var refresh = root.document && root.document.querySelector('[data-feedback-refresh]');
|
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
|
|
|
if (!page || !list) return;
|
|
if (shouldRedirectToLogin(api)) {
|
|
redirectToLogin(api);
|
|
return;
|
|
}
|
|
|
|
async function loadList() {
|
|
try {
|
|
setApiState(list, 'loading', '正在读取反馈记录…');
|
|
setStatus('loading', '正在读取反馈记录…');
|
|
setFeedbackList(list, await readMyFeedback(api), 'ticket-detail.html');
|
|
setStatus('', '');
|
|
} catch (error) {
|
|
if (shouldRedirectToLogin(api, error)) {
|
|
redirectToLogin(api);
|
|
return;
|
|
}
|
|
setApiState(list, isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看反馈记录。' : '读取反馈记录失败,请稍后重试。');
|
|
setStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看反馈记录。' : (error.message || '读取反馈记录失败,请稍后重试。'));
|
|
}
|
|
}
|
|
|
|
if (refresh) refresh.addEventListener('click', async function () {
|
|
setPendingButton(refresh, true, '正在刷新…');
|
|
try {
|
|
await loadList();
|
|
} finally {
|
|
setPendingButton(refresh, false, '正在刷新…');
|
|
}
|
|
});
|
|
await loadList();
|
|
}
|
|
|
|
async function initFeedbackDetailPage() {
|
|
var page = root.document && root.document.querySelector('[data-feedback-detail-page]');
|
|
var detail = root.document && root.document.querySelector('[data-feedback-detail]');
|
|
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
|
|
var feedbackId = getFeedbackId();
|
|
var item;
|
|
|
|
if (!page || !detail) return;
|
|
if (!feedbackId) {
|
|
setApiState(detail, 'empty', '反馈记录不存在或无权查看。');
|
|
return;
|
|
}
|
|
if (shouldRedirectToLogin(api)) {
|
|
redirectToLogin(api);
|
|
return;
|
|
}
|
|
try {
|
|
setApiState(detail, 'loading', '正在读取反馈详情…');
|
|
setStatus('loading', '正在读取反馈详情…');
|
|
item = findFeedbackById(await api.myFeedback(), feedbackId);
|
|
if (item) detail.innerHTML = renderFeedbackDetail(item);
|
|
else setApiState(detail, 'empty', '反馈记录不存在或无权查看。');
|
|
setStatus('', item ? '' : '反馈记录不存在或无权查看。');
|
|
} catch (error) {
|
|
if (shouldRedirectToLogin(api, error)) {
|
|
redirectToLogin(api);
|
|
return;
|
|
}
|
|
setApiState(detail, isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看该反馈详情。' : '反馈详情读取失败,请稍后重试。');
|
|
setStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看该反馈详情。' : (error.message || '反馈详情读取失败,请稍后重试。'));
|
|
}
|
|
}
|
|
|
|
async function init() {
|
|
if (root.document && root.document.querySelector('[data-feedback-form]')) {
|
|
await initFeedbackFormPage();
|
|
return;
|
|
}
|
|
if (root.document && root.document.querySelector('[data-feedback-list-page]')) {
|
|
await initFeedbackListPage();
|
|
return;
|
|
}
|
|
if (root.document && root.document.querySelector('[data-feedback-detail-page]')) {
|
|
await initFeedbackDetailPage();
|
|
}
|
|
}
|
|
|
|
return {
|
|
getFeedbackId: getFeedbackId,
|
|
buildFeedbackBody: buildFeedbackBody,
|
|
validateFeedbackBody: validateFeedbackBody,
|
|
normalizeFeedbackTypeOptions: normalizeFeedbackTypeOptions,
|
|
normalizeFeedback: normalizeFeedback,
|
|
normalizeFeedbackList: normalizeFeedbackList,
|
|
findFeedbackById: findFeedbackById,
|
|
buildFeedbackDetailUrl: buildFeedbackDetailUrl,
|
|
renderFeedbackList: renderFeedbackList,
|
|
renderFeedbackDetail: renderFeedbackDetail,
|
|
isForbidden: isForbidden,
|
|
initFeedbackFormPage: initFeedbackFormPage,
|
|
initFeedbackListPage: initFeedbackListPage,
|
|
initFeedbackDetailPage: initFeedbackDetailPage,
|
|
init: init
|
|
};
|
|
});
|