现在有的接口对接测试完成

This commit is contained in:
2026-07-25 19:19:36 +08:00
parent 8870136d1b
commit 817d85e117
59 changed files with 26623 additions and 1576 deletions
+27
View File
@@ -125,6 +125,21 @@
padding: 28px;
}
.rich-editor-host {
overflow: hidden;
border: 1px solid var(--line);
border-radius: 12px;
background: var(--white);
}
.rich-editor-toolbar {
border-bottom: 1px solid var(--line);
}
.rich-editor-content {
height: 320px;
}
.module-panel h2 {
margin-bottom: 10px;
font-family: "SimSun", "Songti SC", serif;
@@ -523,6 +538,18 @@
gap: 14px;
}
.notification-raw {
margin: 0;
padding: 16px;
overflow-wrap: anywhere;
white-space: pre-wrap;
border: 1px solid var(--line);
border-radius: 15px;
background: #fffdf8;
color: var(--ink);
line-height: 1.6;
}
.module-row {
display: grid;
grid-template-columns: 1fr auto;
+29 -25
View File
@@ -18,22 +18,22 @@
var FORM_CONFIG = {
login: {
selector: '#login-password-form',
captchaScene: 'PC_SMS_LOGIN',
operationCode: 'password-login',
successUrl: 'profile.html'
},
'sms-login': {
selector: '#login-sms-form',
captchaScene: 'PC_SMS_LOGIN',
operationCode: 'sms-login',
successUrl: 'profile.html'
},
register: {
selector: '#register-form',
captchaScene: 'PC_REGISTER',
operationCode: 'register',
successUrl: 'login.html'
},
'password-reset': {
selector: '#password-reset-form',
captchaScene: 'PC_FORGOT_PASSWORD',
operationCode: 'forgot-password',
successUrl: 'login.html'
}
};
@@ -47,16 +47,19 @@
}
function buildLoginBody(values, hashFn) {
// 这里只组装登录 DTO 字段,grantType、tenantId、clientId 由 api-client 统一补齐。
// 这里只组装登录 DTO 字段,grantType、tenantId 由 api-client 统一补齐clientid 只走请求头
var hash = hashFn || hashPassword;
return {
var body = {
phone: values.phone,
password: hash(values.password)
};
if (values.validToken) body.validToken = values.validToken;
return body;
}
function buildSmsLoginBody(values) {
// 短信登录接口只需要页面字段,grantType、tenantId、clientId 由 api-client 统一补齐。
// 短信登录接口只需要页面字段,grantType、tenantId 由 api-client 统一补齐clientid 只走请求头
return {
phone: values.phone,
smsCode: values.smsCode
@@ -88,11 +91,11 @@
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function getCaptchaScene(formType) {
// PC/H5 认证场景来自后台验证码配置,客户端 Key 为 web_pc
function getCaptchaOperation(formType) {
// PC 验证中心按业务动作解析服务端已激活的验证场景
var config = FORM_CONFIG[formType] || {};
return config.captchaScene || '';
return config.operationCode || '';
}
function getSuccessUrl(formType) {
@@ -101,16 +104,16 @@
return config.successUrl || '';
}
function getFormCaptchaScene(form, formType) {
// 场景码由当前脚本统一管理,避免散落在 HTML 属性中。
var sceneCode = getCaptchaScene(formType);
function getFormCaptchaOperation(form, formType) {
// 业务动作由当前脚本统一管理,避免散落在 HTML 属性中。
var operationCode = getCaptchaOperation(formType);
return sceneCode;
return operationCode;
}
async function ensureCaptcha(form, sceneCode, subject) {
// 未配置 PC/H5 验证场景时不调用验证中心,也不伪造 validToken。
if (!sceneCode) return true;
async function ensureCaptcha(form, operationCode, subject) {
// 未配置 PC 认证动作时不调用验证中心,也不伪造 validToken。
if (!operationCode) return true;
if (!root.CaptchaPages || !root.CaptchaPages.ensureToken) {
showMessage('验证码组件未加载,请刷新后重试');
@@ -118,7 +121,7 @@
}
return Boolean(await root.CaptchaPages.ensureToken(form, {
sceneCode: sceneCode,
operationCode: operationCode,
subject: subject
}));
}
@@ -310,6 +313,8 @@
return;
}
if (!validateBeforeSubmit('login', values)) return;
if (!await ensureCaptcha(form, getFormCaptchaOperation(form, 'login'), values.phone)) return;
values = readForm(form);
setBusy(button, true);
try {
await api.login(buildLoginBody(values));
@@ -364,26 +369,25 @@
}
async function sendCode(button) {
// 短信验证码场景由表单配置决定,不在按钮属性中重复声明。
// 短信验证码认证动作由表单配置决定,不在按钮属性中重复声明。
var api = getApi();
var form = button.closest('form');
var values = readForm(form);
var formType = getFormType(form);
var sceneCode = getFormCaptchaScene(form, formType);
var operationCode = getFormCaptchaOperation(form, formType);
if (!api) {
showMessage('接口初始化失败,请刷新后重试');
return;
}
if (!validateBeforeSendCode(values)) return;
if (!await ensureCaptcha(form, sceneCode, values.phone)) return;
if (!await ensureCaptcha(form, operationCode, values.phone)) return;
values = readForm(form);
setBusy(button, true);
try {
await api.sendSmsCode({
sceneCode: sceneCode,
await api.sendSmsCode(operationCode, {
phone: values.phone,
validToken: values.validToken || ''
});
@@ -473,8 +477,8 @@
buildPasswordResetBody: buildPasswordResetBody,
clearCaptchaToken: clearCaptchaToken,
startSmsCooldown: startSmsCooldown,
getCaptchaScene: getCaptchaScene,
getFormCaptchaScene: getFormCaptchaScene,
getCaptchaOperation: getCaptchaOperation,
getFormCaptchaOperation: getFormCaptchaOperation,
getSuccessUrl: getSuccessUrl,
validateAuthValues: validateAuthValues,
init: init
+7 -12
View File
@@ -9,7 +9,6 @@
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var DEFAULT_SCENE_CODE = 'PC_SMS_LOGIN';
var CAPTCHA_LAYER_AREA = ['318px', '318px'];
var captchaLayerSeed = 0;
@@ -31,13 +30,11 @@
}
function buildChallengeBody(options, api) {
// 请求体字段严格对应 PC YAML 的 VerificationChallengeBody
// operationCode 属于路径,PC 验证请求体只提交租户和验证主体
var settings = options || {};
return {
tenantId: settings.tenantId || api.tenantId,
clientId: settings.clientId || api.clientId,
sceneCode: settings.sceneCode || DEFAULT_SCENE_CODE,
subject: settings.subject || ''
};
}
@@ -74,8 +71,6 @@
return {
tenantId: settings.tenantId || api.tenantId,
clientId: settings.clientId || api.clientId,
sceneCode: settings.sceneCode || DEFAULT_SCENE_CODE,
subject: settings.subject || '',
challengeId: settings.challengeId || source.challengeId || source.id,
providerCode: settings.providerCode || 'tianai',
@@ -87,7 +82,7 @@
}
function normalizeVerifyTrack(data) {
// 后端 /captcha/verify 要求轨迹统一放在 payload.track,并显式携带 left/top。
// PC 验证接口要求轨迹统一放在 payload.track,并显式携带 left/top。
var track = Object.assign({}, data || {});
var trackList = Array.isArray(track.trackList) ? track.trackList : [];
var first = trackList[0] || {};
@@ -184,7 +179,7 @@
}
function shouldRequireCaptcha(result) {
// /captcha/require 返回 required=false 时允许直接继续业务请求。
// PC require 返回 required=false 时允许直接继续业务请求。
if (!result) return true;
return result.required !== false;
}
@@ -213,8 +208,8 @@
config = new root.CaptchaConfig({
bindEl: box,
requestCaptchaDataUrl: api.buildApiUrl('/captcha/challenge'),
validCaptchaUrl: api.buildApiUrl('/captcha/verify'),
requestCaptchaDataUrl: api.captchaChallengeUrl(settings.operationCode),
validCaptchaUrl: api.captchaVerifyUrl(settings.operationCode),
requestHeaders: {
clientid: api.clientId
},
@@ -301,10 +296,10 @@
if (!api) return '';
if (field && field.value) return field.value;
if (!settings.sceneCode) return '';
if (!settings.operationCode) return '';
try {
requirement = await api.captchaRequirement(buildChallengeBody(settings, api));
requirement = await api.captchaRequirement(settings.operationCode, buildChallengeBody(settings, api));
if (!shouldRequireCaptcha(requirement)) return 'CAPTCHA_NOT_REQUIRED';
} catch (error) {
// 查询策略失败时仍尝试直接拉起验证,避免认证接口缺少 validToken。
+1 -1
View File
@@ -377,7 +377,7 @@
genealogyId = requireGenealogyId();
if (!genealogyId) return;
// 富文本编辑器在提交前同步回 textarea,保证发送的是用户当前输入。
if (root.KindEditor && root.KindEditor.sync) root.KindEditor.sync('#feedContent');
if (root.AppRichEditor && root.AppRichEditor.syncAll) root.AppRichEditor.syncAll();
body = buildFeedBody(getFormValues(form));
if (!body.feedContent) {
showMessage('请填写动态内容');
+67
View File
@@ -0,0 +1,67 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.GenealogyEntryPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.GenealogyEntryPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var TARGET_LABELS = {
'profile-family-home.html': '家谱主页',
'profile-feed.html': '家族圈动态',
'profile-growth.html': '成长记录',
'profile-relative.html': '亲友往来',
'profile-memo.html': '备忘录',
'profile-tree.html': '世系图',
'profile-generation.html': '字辈谱'
};
function getTargetPage(search) {
var params = new URLSearchParams(String(search === undefined && root.location ? root.location.search : search || '').replace(/^\?/, ''));
var target = params.get('next') || '';
return /^profile-[a-z-]+\.html$/.test(target) ? target : 'profile-family-home.html';
}
function buildStatus(quota, target) {
var source = quota || {};
var details = [];
var label = TARGET_LABELS[target] || '该家谱业务';
if (Number(source.createRemaining) >= 0) details.push('还可创建 ' + source.createRemaining + ' 部');
if (Number(source.joinRemaining) >= 0) details.push('还可加入 ' + source.joinRemaining + ' 部');
return '当前 PC 接口尚未提供“我的家谱”列表或家谱详情,不能伪造家谱编号进入“' + label + '”。' +
(details.length ? ' 已读取额度:' + details.join('') + '。' : '');
}
async function init() {
var status = root.document && root.document.querySelector('[data-genealogy-entry-status]');
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
var target = getTargetPage();
if (!status) return;
if (!api || !api.genealogyQuota) {
status.textContent = '家谱入口接口初始化失败,请刷新后重试。';
return;
}
try {
status.textContent = buildStatus(await api.genealogyQuota(), target);
} catch (error) {
status.textContent = '无法读取家谱额度,请稍后重试。当前 PC 接口仍未提供家谱列表或详情,不能伪造家谱编号。';
}
}
return {
getTargetPage: getTargetPage,
buildStatus: buildStatus,
init: init
};
});
+301
View File
@@ -0,0 +1,301 @@
(function (root, factory) {
// 成长记录页面只消费 ApiClient 中已在 Apifox PC 目录核验的方法。
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.GrowthPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.GrowthPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var writePending = false;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function query(selector, rootNode) {
return documentRef ? (rootNode || documentRef).querySelector(selector) : null;
}
function queryAll(selector, rootNode) {
return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : [];
}
function trimOrUndefined(value) {
var text = String(value === undefined || value === null ? '' : value).trim();
return text || undefined;
}
function toSafeIntegerOrUndefined(value) {
var text = trimOrUndefined(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isInteger(number) && Number.isSafeInteger(number) ? number : undefined;
}
function getQueryParam(search, name) {
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
return params.get(name) || '';
}
function getCurrentGenealogyId(search) {
var page = query('[data-growth-page], [data-growth-edit-page]');
var source = search === undefined && root.location ? root.location.search : search;
var profileGenealogyId;
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
profileGenealogyId = root.ProfileUI.getGenealogyId();
if (profileGenealogyId) return profileGenealogyId;
}
return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || '';
}
function buildGrowthRecordBody(values) {
var source = values || {};
var body = {
recordTitle: String(source.recordTitle || '').trim()
};
var lineagePersonId = trimOrUndefined(source.lineagePersonId);
var recordType = trimOrUndefined(source.recordType);
var recordContent = trimOrUndefined(source.recordContent);
var recordDate = trimOrUndefined(source.recordDate);
var remindTime = trimOrUndefined(source.remindTime);
var mediaOssIds = trimOrUndefined(source.mediaOssIds);
var status = trimOrUndefined(source.status);
var sortOrderText = trimOrUndefined(source.sortOrder);
if (lineagePersonId !== undefined) body.lineagePersonId = lineagePersonId;
if (recordType !== undefined) body.recordType = recordType;
if (recordContent !== undefined) body.recordContent = recordContent;
if (recordDate !== undefined) body.recordDate = recordDate;
if (remindTime !== undefined) body.remindTime = remindTime;
if (mediaOssIds !== undefined) body.mediaOssIds = mediaOssIds;
if (sortOrderText !== undefined) body.sortOrder = toSafeIntegerOrUndefined(sortOrderText);
if (status !== undefined) body.status = status;
return body;
}
function validateGrowthRecordBody(body) {
if (!body || !body.recordTitle) return '请填写记录标题';
if (body.lineagePersonId !== undefined && !/^\d+$/.test(body.lineagePersonId)) return '世系人物 ID 必须是整数';
if (body.mediaOssIds !== undefined && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(body.mediaOssIds)) {
return '附件 OSS ID 请使用英文逗号分隔的正整数';
}
if (Object.prototype.hasOwnProperty.call(body, 'sortOrder') && body.sortOrder === undefined) return '排序值必须是安全整数';
return '';
}
function normalizeGrowthList(data) {
return Array.isArray(data) ? data : [];
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function stringifyRecord(record) {
try {
return typeof record === 'string' ? record : JSON.stringify(record);
} catch (error) {
return String(record);
}
}
function renderGrowthRecords(data) {
var container = query('[data-growth-list]');
var records = normalizeGrowthList(data);
if (!container) return;
if (!Array.isArray(data)) {
container.innerHTML = '<div class="api-empty">成长记录响应未按 Apifox ListResult 返回数组,无法安全展示或操作记录。</div>';
return;
}
if (!records.length) {
container.innerHTML = '<div class="api-empty">暂无成长记录</div>';
return;
}
// ListResult 的元素 DTO 尚未展开,不能假设 recordId、标题或权限字段。
container.innerHTML = records.map(function (record, index) {
return '<article class="module-row"><div><h3>成长记录 ' + (index + 1) + '</h3><p>' +
escapeHtml(stringifyRecord(record)) + '</p></div></article>';
}).join('');
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
if (root.alert) root.alert(message);
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken();
if (!root.location) return true;
if (typeof root.location.replace === 'function') {
root.location.replace('login.html');
return true;
}
root.location.href = 'login.html';
return true;
}
function getFormValues(form) {
var values = {};
queryAll('[name]', form).forEach(function (field) {
values[field.name] = field.value;
});
return values;
}
function setFormStatus(message) {
var status = query('[data-growth-form-status]');
if (status) status.textContent = message || '';
}
function setWritePending(pending) {
writePending = Boolean(pending);
queryAll('[data-growth-form] button, [data-growth-form] input, [data-growth-form] textarea').forEach(function (control) {
control.disabled = writePending;
});
}
function syncGenealogyLinks(genealogyId) {
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) {
root.ProfileUI.syncGenealogyContextLinks();
return;
}
queryAll('[data-genealogy-context-link]').forEach(function (link) {
var href = link.getAttribute('href');
var base;
if (!href || href === '#') return;
base = href.split('?')[0];
link.href = base + '?genealogyId=' + encodeURIComponent(genealogyId);
});
}
function renderNoContext() {
var list = query('[data-growth-list]');
if (list) list.innerHTML = '<div class="api-empty">等待家谱入口接口;请从具体家谱进入成长记录。</div>';
setFormStatus('等待家谱入口接口;请从具体家谱进入成长记录。');
}
function requireGenealogyId() {
var genealogyId = getCurrentGenealogyId();
if (!genealogyId) {
renderNoContext();
showMessage('等待家谱入口接口;请从具体家谱进入成长记录。');
}
return genealogyId;
}
function buildListUrl(genealogyId) {
return 'profile-growth.html?genealogyId=' + encodeURIComponent(genealogyId);
}
async function loadGrowthRecords() {
var api = getApi();
var genealogyId;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
try {
renderGrowthRecords(await api.growthRecords(genealogyId));
} catch (error) {
if (redirectUnauthorized(api, error)) return;
renderGrowthRecords(null);
showMessage(error.message || '成长记录加载失败');
}
}
async function submitGrowthRecord(form) {
var api = getApi();
var genealogyId;
var body;
var validation;
if (writePending || redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
body = buildGrowthRecordBody(getFormValues(form));
validation = validateGrowthRecordBody(body);
if (validation) {
setFormStatus(validation);
return;
}
setWritePending(true);
setFormStatus('正在保存成长记录...');
try {
await api.createGrowthRecord(genealogyId, body);
if (root.location) root.location.href = buildListUrl(genealogyId);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
setFormStatus(error.message || '成长记录保存失败');
} finally {
setWritePending(false);
}
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('submit', function (event) {
var form = event.target.closest('[data-growth-form]');
if (!form) return;
event.preventDefault();
submitGrowthRecord(form);
});
}
function init() {
var genealogyId;
if (!documentRef) return;
bindActions();
if (query('[data-growth-page]')) loadGrowthRecords();
if (query('[data-growth-edit-page]')) {
genealogyId = requireGenealogyId();
if (genealogyId) syncGenealogyLinks(genealogyId);
}
}
return {
getCurrentGenealogyId: getCurrentGenealogyId,
buildGrowthRecordBody: buildGrowthRecordBody,
validateGrowthRecordBody: validateGrowthRecordBody,
normalizeGrowthList: normalizeGrowthList,
shouldRedirectToLogin: shouldRedirectToLogin,
init: init
};
});
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

-948
View File
@@ -1,948 +0,0 @@
/* common */
.ke-inline-block {
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
}
.ke-clearfix {
zoom: 1;
}
.ke-clearfix:after {
content: ".";
display: block;
clear: both;
font-size: 0;
height: 0;
line-height: 0;
visibility: hidden;
}
.ke-shadow {
box-shadow: 1px 1px 3px #A0A0A0;
-moz-box-shadow: 1px 1px 3px #A0A0A0;
-webkit-box-shadow: 1px 1px 3px #A0A0A0;
filter: progid:DXImageTransform.Microsoft.Shadow(color='#A0A0A0', Direction=135, Strength=3);
background-color: #F0F0EE;
}
.ke-menu a,
.ke-menu a:hover,
.ke-dialog a,
.ke-dialog a:hover {
color: #337FE5;
text-decoration: none;
}
/* icons */
.ke-icon-source {
background-position: 0px 0px;
width: 16px;
height: 16px;
}
.ke-icon-preview {
background-position: 0px -16px;
width: 16px;
height: 16px;
}
.ke-icon-print {
background-position: 0px -32px;
width: 16px;
height: 16px;
}
.ke-icon-undo {
background-position: 0px -48px;
width: 16px;
height: 16px;
}
.ke-icon-redo {
background-position: 0px -64px;
width: 16px;
height: 16px;
}
.ke-icon-cut {
background-position: 0px -80px;
width: 16px;
height: 16px;
}
.ke-icon-copy {
background-position: 0px -96px;
width: 16px;
height: 16px;
}
.ke-icon-paste {
background-position: 0px -112px;
width: 16px;
height: 16px;
}
.ke-icon-selectall {
background-position: 0px -128px;
width: 16px;
height: 16px;
}
.ke-icon-justifyleft {
background-position: 0px -144px;
width: 16px;
height: 16px;
}
.ke-icon-justifycenter {
background-position: 0px -160px;
width: 16px;
height: 16px;
}
.ke-icon-justifyright {
background-position: 0px -176px;
width: 16px;
height: 16px;
}
.ke-icon-justifyfull {
background-position: 0px -192px;
width: 16px;
height: 16px;
}
.ke-icon-insertorderedlist {
background-position: 0px -208px;
width: 16px;
height: 16px;
}
.ke-icon-insertunorderedlist {
background-position: 0px -224px;
width: 16px;
height: 16px;
}
.ke-icon-indent {
background-position: 0px -240px;
width: 16px;
height: 16px;
}
.ke-icon-outdent {
background-position: 0px -256px;
width: 16px;
height: 16px;
}
.ke-icon-subscript {
background-position: 0px -272px;
width: 16px;
height: 16px;
}
.ke-icon-superscript {
background-position: 0px -288px;
width: 16px;
height: 16px;
}
.ke-icon-date {
background-position: 0px -304px;
width: 25px;
height: 16px;
}
.ke-icon-time {
background-position: 0px -320px;
width: 25px;
height: 16px;
}
.ke-icon-formatblock {
background-position: 0px -336px;
width: 25px;
height: 16px;
}
.ke-icon-fontname {
background-position: 0px -352px;
width: 21px;
height: 16px;
}
.ke-icon-fontsize {
background-position: 0px -368px;
width: 23px;
height: 16px;
}
.ke-icon-forecolor {
background-position: 0px -384px;
width: 20px;
height: 16px;
}
.ke-icon-hilitecolor {
background-position: 0px -400px;
width: 23px;
height: 16px;
}
.ke-icon-bold {
background-position: 0px -416px;
width: 16px;
height: 16px;
}
.ke-icon-italic {
background-position: 0px -432px;
width: 16px;
height: 16px;
}
.ke-icon-underline {
background-position: 0px -448px;
width: 16px;
height: 16px;
}
.ke-icon-strikethrough {
background-position: 0px -464px;
width: 16px;
height: 16px;
}
.ke-icon-removeformat {
background-position: 0px -480px;
width: 16px;
height: 16px;
}
.ke-icon-image {
background-position: 0px -496px;
width: 16px;
height: 16px;
}
.ke-icon-flash {
background-position: 0px -512px;
width: 16px;
height: 16px;
}
.ke-icon-media {
background-position: 0px -528px;
width: 16px;
height: 16px;
}
.ke-icon-div {
background-position: 0px -544px;
width: 16px;
height: 16px;
}
.ke-icon-formula {
background-position: 0px -576px;
width: 16px;
height: 16px;
}
.ke-icon-hr {
background-position: 0px -592px;
width: 16px;
height: 16px;
}
.ke-icon-emoticons {
background-position: 0px -608px;
width: 16px;
height: 16px;
}
.ke-icon-link {
background-position: 0px -624px;
width: 16px;
height: 16px;
}
.ke-icon-unlink {
background-position: 0px -640px;
width: 16px;
height: 16px;
}
.ke-icon-fullscreen {
background-position: 0px -656px;
width: 16px;
height: 16px;
}
.ke-icon-about {
background-position: 0px -672px;
width: 16px;
height: 16px;
}
.ke-icon-plainpaste {
background-position: 0px -704px;
width: 16px;
height: 16px;
}
.ke-icon-wordpaste {
background-position: 0px -720px;
width: 16px;
height: 16px;
}
.ke-icon-table {
background-position: 0px -784px;
width: 16px;
height: 16px;
}
.ke-icon-tablemenu {
background-position: 0px -768px;
width: 16px;
height: 16px;
}
.ke-icon-tableinsert {
background-position: 0px -784px;
width: 16px;
height: 16px;
}
.ke-icon-tabledelete {
background-position: 0px -800px;
width: 16px;
height: 16px;
}
.ke-icon-tablecolinsertleft {
background-position: 0px -816px;
width: 16px;
height: 16px;
}
.ke-icon-tablecolinsertright {
background-position: 0px -832px;
width: 16px;
height: 16px;
}
.ke-icon-tablerowinsertabove {
background-position: 0px -848px;
width: 16px;
height: 16px;
}
.ke-icon-tablerowinsertbelow {
background-position: 0px -864px;
width: 16px;
height: 16px;
}
.ke-icon-tablecoldelete {
background-position: 0px -880px;
width: 16px;
height: 16px;
}
.ke-icon-tablerowdelete {
background-position: 0px -896px;
width: 16px;
height: 16px;
}
.ke-icon-tablecellprop {
background-position: 0px -912px;
width: 16px;
height: 16px;
}
.ke-icon-tableprop {
background-position: 0px -928px;
width: 16px;
height: 16px;
}
.ke-icon-checked {
background-position: 0px -944px;
width: 16px;
height: 16px;
}
.ke-icon-code {
background-position: 0px -960px;
width: 16px;
height: 16px;
}
.ke-icon-map {
background-position: 0px -976px;
width: 16px;
height: 16px;
}
.ke-icon-baidumap {
background-position: 0px -976px;
width: 16px;
height: 16px;
}
.ke-icon-lineheight {
background-position: 0px -992px;
width: 16px;
height: 16px;
}
.ke-icon-clearhtml {
background-position: 0px -1008px;
width: 16px;
height: 16px;
}
.ke-icon-pagebreak {
background-position: 0px -1024px;
width: 16px;
height: 16px;
}
.ke-icon-insertfile {
background-position: 0px -1040px;
width: 16px;
height: 16px;
}
.ke-icon-quickformat {
background-position: 0px -1056px;
width: 16px;
height: 16px;
}
.ke-icon-template {
background-position: 0px -1072px;
width: 16px;
height: 16px;
}
.ke-icon-tablecellsplit {
background-position: 0px -1088px;
width: 16px;
height: 16px;
}
.ke-icon-tablerowmerge {
background-position: 0px -1104px;
width: 16px;
height: 16px;
}
.ke-icon-tablerowsplit {
background-position: 0px -1120px;
width: 16px;
height: 16px;
}
.ke-icon-tablecolmerge {
background-position: 0px -1136px;
width: 16px;
height: 16px;
}
.ke-icon-tablecolsplit {
background-position: 0px -1152px;
width: 16px;
height: 16px;
}
.ke-icon-anchor {
background-position: 0px -1168px;
width: 16px;
height: 16px;
}
.ke-icon-search {
background-position: 0px -1184px;
width: 16px;
height: 16px;
}
.ke-icon-new {
background-position: 0px -1200px;
width: 16px;
height: 16px;
}
.ke-icon-specialchar {
background-position: 0px -1216px;
width: 16px;
height: 16px;
}
.ke-icon-multiimage {
background-position: 0px -1232px;
width: 16px;
height: 16px;
}
.ke-icon-zijianju {
background-position: 0px -1248px;
width: 16px;
height: 16px;
}
.ke-icon-duansuo {
background-position: 0px -1264px;
width: 16px;
height: 16px;
}
/* container */
.ke-container {
display: block;
/*border: 1px solid #CCCCCC;*/
background-color: #FFF;
overflow: hidden;
padding: 0;
}
/* toolbar */
.ke-toolbar {
border-bottom: 1px solid #CCC;
background-color: #F0F0EE;
text-align: left;
overflow: hidden;
zoom: 1;
}
.ke-toolbar-icon {
background-repeat: no-repeat;
font-size: 0;
line-height: 0;
overflow: hidden;
display: block;
}
.ke-toolbar-icon-url {
background-image: url(default.png);
}
.ke-toolbar .ke-outline {
border: 1px solid #F0F0EE;
/*margin: 1px;*/
margin: 8px 9px;
padding: 1px 2px;
font-size: 0;
line-height: 0;
overflow: hidden;
cursor: pointer;
display: block;
float: left;
}
.ke-toolbar .ke-on {
border: 1px solid #5690D2;
}
.ke-toolbar .ke-selected {
border: 1px solid #5690D2;
background-color: #E9EFF6;
}
.ke-toolbar .ke-disabled {
cursor: default;
}
.ke-toolbar .ke-separator {
height: 16px;
margin: 2px 3px;
border-left: 1px solid #A0A0A0;
border-right: 1px solid #FFFFFF;
border-top:0;
border-bottom:0;
width: 0;
font-size: 0;
line-height: 0;
overflow: hidden;
display: block;
float: left;
}
.ke-toolbar .ke-hr {
overflow: hidden;
height: 1px;
clear: both;
}
/* edit */
.ke-edit {
padding: 0;
}
.ke-edit-iframe,
.ke-edit-textarea {
border: 0;
margin: 0;
padding: 0;
overflow: auto;
}
.ke-edit-textarea {
font: 12px/1.5 "Consolas", "Monaco", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
color: #000;
overflow: auto;
resize: none;
}
.ke-edit-textarea:focus {
outline: none;
}
/* statusbar */
.ke-statusbar {
position: relative;
background-color: #F0F0EE;
border-top: 1px solid #CCCCCC;
font-size: 0;
line-height: 0;
*height: 12px;
overflow: hidden;
text-align: center;
cursor: s-resize;
}
.ke-statusbar-center-icon {
background-position: -0px -754px;
width: 15px;
height: 11px;
background-image: url(default.png);
}
.ke-statusbar-right-icon {
position: absolute;
right: 0;
bottom: 0;
cursor: se-resize;
background-position: -5px -741px;
width: 11px;
height: 11px;
background-image: url(default.png);
}
/* menu */
.ke-menu {
border: 1px solid #A0A0A0;
background-color: #F1F1F1;
color: #222222;
padding: 2px;
font-family: "sans serif",tahoma,verdana,helvetica;
font-size: 12px;
text-align: left;
overflow: hidden;
}
.ke-menu-item {
border: 1px solid #F1F1F1;
background-color: #F1F1F1;
color: #222222;
height: 24px;
overflow: hidden;
cursor: pointer;
}
.ke-menu-item-on {
border: 1px solid #5690D2;
background-color: #E9EFF6;
}
.ke-menu-item-left {
width: 27px;
text-align: center;
overflow: hidden;
}
.ke-menu-item-center {
width: 0;
height: 24px;
border-left: 1px solid #E3E3E3;
border-right: 1px solid #FFFFFF;
border-top: 0;
border-bottom: 0;
}
.ke-menu-item-center-on {
border-left: 1px solid #E9EFF6;
border-right: 1px solid #E9EFF6;
}
.ke-menu-item-right {
border: 0;
padding: 0 0 0 5px;
line-height: 24px;
text-align: left;
overflow: hidden;
}
.ke-menu-separator {
margin: 2px 0;
height: 0;
overflow: hidden;
border-top: 1px solid #CCCCCC;
border-bottom: 1px solid #FFFFFF;
border-left: 0;
border-right: 0;
}
/* colorpicker */
.ke-colorpicker {
border: 1px solid #A0A0A0;
background-color: #F1F1F1;
color: #222222;
padding: 2px;
}
.ke-colorpicker-table {
border:0;
margin:0;
padding:0;
border-collapse: separate;
}
.ke-colorpicker-cell {
font-size: 0;
line-height: 0;
border: 1px solid #F0F0EE;
cursor: pointer;
margin:3px;
padding:0;
}
.ke-colorpicker-cell-top {
font-family: "sans serif",tahoma,verdana,helvetica;
font-size: 12px;
line-height: 24px;
border: 1px solid #F0F0EE;
cursor: pointer;
margin:0;
padding:0;
text-align: center;
}
.ke-colorpicker-cell-on {
border: 1px solid #5690D2;
}
.ke-colorpicker-cell-selected {
border: 1px solid #2446AB;
}
.ke-colorpicker-cell-color {
width: 14px;
height: 14px;
margin: 3px;
padding: 0;
border: 0;
}
/* dialog */
.ke-dialog {
position: absolute;
margin: 0;
padding: 0;
}
.ke-dialog .ke-header {
width: 100%;
margin-bottom: 10px;
}
.ke-dialog .ke-header .ke-left {
float: left;
}
.ke-dialog .ke-header .ke-right {
float: right;
}
.ke-dialog .ke-header label {
margin-right: 0;
cursor: pointer;
font-weight: normal;
display: inline;
vertical-align: top;
}
.ke-dialog-content {
background-color: #FFF;
width: 100%;
height: 100%;
color: #333;
border: 1px solid #A0A0A0;
}
.ke-dialog-shadow {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: 3px 3px 7px #999;
-moz-box-shadow: 3px 3px 7px #999;
-webkit-box-shadow: 3px 3px 7px #999;
filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='3', MakeShadow='true', ShadowOpacity='0.4');
background-color: #F0F0EE;
}
.ke-dialog-header {
border:0;
margin:0;
padding: 0 10px;
background: url(background.png) repeat scroll 0 0 #F0F0EE;
border-bottom: 1px solid #CFCFCF;
height: 24px;
font: 12px/24px "sans serif",tahoma,verdana,helvetica;
text-align: left;
color: #222;
cursor: move;
}
.ke-dialog-icon-close {
display: block;
background: url(default.png) no-repeat scroll 0px -688px;
width: 16px;
height: 16px;
position: absolute;
right: 6px;
top: 6px;
cursor: pointer;
}
.ke-dialog-body {
font: 12px/1.5 "sans serif",tahoma,verdana,helvetica;
text-align: left;
overflow: hidden;
width: 100%;
}
.ke-dialog-body textarea {
display: block;
overflow: auto;
padding: 0;
resize: none;
}
.ke-dialog-body textarea:focus,
.ke-dialog-body input:focus,
.ke-dialog-body select:focus {
outline: none;
}
.ke-dialog-body label {
margin-right: 10px;
cursor: pointer;
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
}
.ke-dialog-body img {
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
}
.ke-dialog-body select {
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
width: auto;
}
.ke-dialog-body .ke-textarea {
display: block;
width: 408px;
height: 260px;
font-family: "sans serif",tahoma,verdana,helvetica;
font-size: 12px;
border-color: #848484 #E0E0E0 #E0E0E0 #848484;
border-style: solid;
border-width: 1px;
}
.ke-dialog-body .ke-form {
margin: 0;
padding: 0;
}
.ke-dialog-loading {
position: absolute;
top: 0;
left: 1px;
z-index: 1;
text-align: center;
}
.ke-dialog-loading-content {
background: url("../common/loading.gif") no-repeat;
color: #666;
font-size: 14px;
font-weight: bold;
height: 31px;
line-height: 31px;
padding-left: 36px;
}
.ke-dialog-row {
margin-bottom: 10px;
}
.ke-dialog-footer {
font: 12px/1 "sans serif",tahoma,verdana,helvetica;
text-align: right;
padding:0 0 5px 0;
background-color: #FFF;
width: 100%;
}
.ke-dialog-preview,
.ke-dialog-yes {
margin: 5px;
}
.ke-dialog-no {
margin: 5px 10px 5px 5px;
}
.ke-dialog-mask {
background-color:#FFF;
filter:alpha(opacity=50);
opacity:0.5;
}
.ke-button-common {
background: url(background.png) no-repeat;
cursor: pointer;
height: 23px;
line-height: 23px;
overflow: visible;
display: inline-block;
vertical-align: top;
cursor: pointer;
}
.ke-button-outer {
background-position: 0 -25px;
padding: 0;
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
}
.ke-button {
background-position: right -25px;
padding: 0 14px 0 12px;
margin: 0 0 0 2px;
font-family: "sans serif",tahoma,verdana,helvetica;
border: 0 none;
color: #333;
font-size: 12px;
text-decoration: none;
}
/* inputbox */
.ke-input-text {
background-color:#FFFFFF;
font-family: "sans serif",tahoma,verdana,helvetica;
font-size: 12px;
line-height: 17px;
height: 17px;
padding: 2px 4px;
border-color: #848484 #E0E0E0 #E0E0E0 #848484;
border-style: solid;
border-width: 1px;
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
}
.ke-input-number {
width: 50px;
}
.ke-input-color {
border: 1px solid #A0A0A0;
background-color: #FFFFFF;
font-size: 12px;
width: 60px;
height: 20px;
line-height: 20px;
padding-left: 5px;
overflow: hidden;
cursor: pointer;
display: -moz-inline-stack;
display: inline-block;
vertical-align: middle;
zoom: 1;
*display: inline;
}
.ke-upload-button {
position: relative;
}
.ke-upload-area {
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
*height: 25px;
}
.ke-upload-area .ke-upload-file {
position: absolute;
font-size: 60px;
top: 0;
right: 0;
padding: 0;
margin: 0;
z-index: 811212;
border: 0 none;
opacity: 0;
filter: alpha(opacity=0);
}
/* tabs */
.ke-tabs {
font: 12px/1 "sans serif",tahoma,verdana,helvetica;
border-bottom:1px solid #A0A0A0;
padding-left:5px;
margin-bottom:20px;
}
.ke-tabs-ul {
list-style-image:none;
list-style-position:outside;
list-style-type:none;
margin:0;
padding:0;
}
.ke-tabs-li {
position: relative;
border: 1px solid #A0A0A0;
background-color: #F0F0EE;
margin: 0 2px -1px 0;
padding: 0 20px;
float: left;
line-height: 25px;
text-align: center;
color: #555555;
cursor: pointer;
}
.ke-tabs-li-selected {
background-color: #FFF;
border-bottom: 1px solid #FFF;
color: #000;
cursor: default;
}
.ke-tabs-li-on {
background-color: #FFF;
color: #000;
}
/* progressbar */
.ke-progressbar {
position: relative;
margin: 0;
padding: 0;
}
.ke-progressbar-bar {
border: 1px solid #6FA5DB;
width: 80px;
height: 5px;
margin: 10px 10px 0 10px;
padding: 0;
}
.ke-progressbar-bar-inner {
width: 0;
height: 5px;
background-color: #6FA5DB;
overflow: hidden;
margin: 0;
padding: 0;
}
.ke-progressbar-percent {
position: absolute;
top: 0;
left: 40%;
display: none;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

+289
View File
@@ -0,0 +1,289 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.MemoPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.MemoPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var writePending = false;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function query(selector, rootNode) {
return documentRef ? (rootNode || documentRef).querySelector(selector) : null;
}
function queryAll(selector, rootNode) {
return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : [];
}
function trimOrUndefined(value) {
var text = String(value === undefined || value === null ? '' : value).trim();
return text || undefined;
}
function toSafeIntegerOrUndefined(value) {
var text = trimOrUndefined(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isInteger(number) && Number.isSafeInteger(number) ? number : undefined;
}
function getQueryParam(search, name) {
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
return params.get(name) || '';
}
function getCurrentGenealogyId(search) {
var page = query('[data-memo-page], [data-memo-edit-page]');
var source = search === undefined && root.location ? root.location.search : search;
var profileGenealogyId;
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
profileGenealogyId = root.ProfileUI.getGenealogyId();
if (profileGenealogyId) return profileGenealogyId;
}
return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || '';
}
function buildMemoBody(values) {
var source = values || {};
var body = { memoTitle: String(source.memoTitle || '').trim() };
var fields = ['memoContent', 'remindTime', 'completed', 'mediaOssIds', 'status'];
var sortOrderText = trimOrUndefined(source.sortOrder);
fields.forEach(function (field) {
var value = trimOrUndefined(source[field]);
if (value !== undefined) body[field] = value;
});
if (sortOrderText !== undefined) body.sortOrder = toSafeIntegerOrUndefined(sortOrderText);
return body;
}
function validateMemoBody(body) {
if (!body || !body.memoTitle) return '请填写备忘标题';
if (body.mediaOssIds !== undefined && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(body.mediaOssIds)) {
return '附件 OSS ID 请使用英文逗号分隔的正整数';
}
if (Object.prototype.hasOwnProperty.call(body, 'sortOrder') && body.sortOrder === undefined) return '排序值必须是安全整数';
return '';
}
function normalizeMemoList(data) {
return Array.isArray(data) ? data : [];
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function stringifyMemo(memo) {
try {
return typeof memo === 'string' ? memo : JSON.stringify(memo);
} catch (error) {
return String(memo);
}
}
function renderMemos(data) {
var container = query('[data-memo-list]');
var memos = normalizeMemoList(data);
if (!container) return;
if (!Array.isArray(data)) {
container.innerHTML = '<div class="api-empty">备忘录响应未按 Apifox ListResult 返回数组,无法安全展示或操作记录。</div>';
return;
}
if (!memos.length) {
container.innerHTML = '<div class="api-empty">暂无备忘录</div>';
return;
}
// ListResult 的元素 DTO 尚未展开,不能假设 memoId、标题或权限字段。
container.innerHTML = memos.map(function (memo, index) {
return '<article class="module-row"><div><h3>备忘录 ' + (index + 1) + '</h3><p>' +
escapeHtml(stringifyMemo(memo)) + '</p></div></article>';
}).join('');
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
if (root.alert) root.alert(message);
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken();
if (!root.location) return true;
if (typeof root.location.replace === 'function') {
root.location.replace('login.html');
return true;
}
root.location.href = 'login.html';
return true;
}
function getFormValues(form) {
var values = {};
queryAll('[name]', form).forEach(function (field) {
values[field.name] = field.value;
});
return values;
}
function setFormStatus(message) {
var status = query('[data-memo-form-status]');
if (status) status.textContent = message || '';
}
function setWritePending(pending) {
writePending = Boolean(pending);
queryAll('[data-memo-form] button, [data-memo-form] input, [data-memo-form] textarea, [data-memo-form] select').forEach(function (control) {
control.disabled = writePending;
});
}
function syncGenealogyLinks(genealogyId) {
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) {
root.ProfileUI.syncGenealogyContextLinks();
return;
}
queryAll('[data-genealogy-context-link]').forEach(function (link) {
var href = link.getAttribute('href');
var base;
if (!href || href === '#') return;
base = href.split('?')[0];
link.href = base + '?genealogyId=' + encodeURIComponent(genealogyId);
});
}
function renderNoContext() {
var list = query('[data-memo-list]');
if (list) list.innerHTML = '<div class="api-empty">等待家谱入口接口;请从具体家谱进入备忘录。</div>';
setFormStatus('等待家谱入口接口;请从具体家谱进入备忘录。');
}
function requireGenealogyId() {
var genealogyId = getCurrentGenealogyId();
if (!genealogyId) {
renderNoContext();
showMessage('等待家谱入口接口;请从具体家谱进入备忘录。');
}
return genealogyId;
}
function buildListUrl(genealogyId) {
return 'profile-memo.html?genealogyId=' + encodeURIComponent(genealogyId);
}
async function loadMemos() {
var api = getApi();
var genealogyId;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
try {
renderMemos(await api.memos(genealogyId));
} catch (error) {
if (redirectUnauthorized(api, error)) return;
renderMemos(null);
showMessage(error.message || '备忘录加载失败');
}
}
async function submitMemo(form) {
var api = getApi();
var genealogyId;
var body;
var validation;
if (writePending || redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
body = buildMemoBody(getFormValues(form));
validation = validateMemoBody(body);
if (validation) {
setFormStatus(validation);
return;
}
setWritePending(true);
setFormStatus('正在保存备忘录...');
try {
await api.createMemo(genealogyId, body);
if (root.location) root.location.href = buildListUrl(genealogyId);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
setFormStatus(error.message || '备忘录保存失败');
} finally {
setWritePending(false);
}
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('submit', function (event) {
var form = event.target.closest('[data-memo-form]');
if (!form) return;
event.preventDefault();
submitMemo(form);
});
}
function init() {
var genealogyId;
if (!documentRef) return;
bindActions();
if (query('[data-memo-page]')) loadMemos();
if (query('[data-memo-edit-page]')) {
genealogyId = requireGenealogyId();
if (genealogyId) syncGenealogyLinks(genealogyId);
}
}
return {
getCurrentGenealogyId: getCurrentGenealogyId,
buildMemoBody: buildMemoBody,
validateMemoBody: validateMemoBody,
normalizeMemoList: normalizeMemoList,
shouldRedirectToLogin: shouldRedirectToLogin,
init: init
};
});
+132
View File
@@ -0,0 +1,132 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.NotificationPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.NotificationPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function normalizeNotifications(data) {
return Array.isArray(data) ? data : [];
}
function formatNotification(data) {
return JSON.stringify(data || {}, null, 2);
}
function getUnreadCount(data) {
var count = Number(data);
return Number.isFinite(count) && count >= 0 ? count : 0;
}
function query(selector, rootNode) {
return (rootNode || documentRef).querySelector(selector);
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
if (root.alert) root.alert(message);
}
function renderNotifications(container, notifications) {
if (!container) return;
container.innerHTML = '';
if (!notifications.length) {
container.textContent = '暂无通知';
return;
}
notifications.forEach(function (notification) {
var row = documentRef.createElement('pre');
row.className = 'notification-raw';
row.textContent = formatNotification(notification);
container.appendChild(row);
});
}
function updateStatus(message) {
var status = query('[data-notification-status]');
if (status) status.textContent = message;
}
async function loadNotifications() {
var api = getApi();
var container = query('[data-notification-list]');
var countTarget = query('[data-notification-unread-count]');
var result;
if (!api || !api.notifications || !api.unreadNotificationCount) {
updateStatus('消息接口初始化失败,请刷新后重试');
return;
}
updateStatus('正在同步消息…');
try {
result = await Promise.all([api.notifications(), api.unreadNotificationCount()]);
renderNotifications(container, normalizeNotifications(result[0]));
if (countTarget) countTarget.textContent = String(getUnreadCount(result[1]));
updateStatus('已同步消息。列表元素 DTO 尚未在 PC 契约展开,以下保留服务端原始记录。');
} catch (error) {
updateStatus(error.message || '消息同步失败');
}
}
async function markAllRead(button) {
var api = getApi();
if (!api || !api.markAllNotificationsRead) {
updateStatus('消息接口初始化失败,请刷新后重试');
return;
}
button.disabled = true;
try {
await api.markAllNotificationsRead();
showMessage('已全部标记为已读');
await loadNotifications();
} catch (error) {
updateStatus(error.message || '全部标记已读失败');
} finally {
button.disabled = false;
}
}
function init() {
var readAll = query('[data-notification-read-all]');
var refresh = query('[data-notification-refresh]');
if (!documentRef || !query('[data-notification-list]')) return;
if (readAll) readAll.addEventListener('click', function () { markAllRead(readAll); });
if (refresh) refresh.addEventListener('click', loadNotifications);
loadNotifications();
}
return {
normalizeNotifications: normalizeNotifications,
formatNotification: formatNotification,
getUnreadCount: getUnreadCount,
loadNotifications: loadNotifications,
init: init
};
});
+10 -2
View File
@@ -147,14 +147,21 @@
return parts[0] + '?' + params.toString() + hash;
}
function getGenealogyEntryUrl(href) {
var value = String(href || '');
if (!value || value.charAt(0) === '#' || /^(?:https?:|mailto:|tel:)/i.test(value)) return value;
if (value.indexOf('profile-families.html') === 0) return value;
return 'profile-families.html?next=' + encodeURIComponent(value.split('?')[0]);
}
function syncGenealogyContextLinks() {
var genealogyId = getGenealogyId();
if (!genealogyId) return;
$('[data-genealogy-context-link]').each(function () {
var $link = $(this);
$link.attr('href', withGenealogyId($link.attr('href'), genealogyId));
$link.attr('href', genealogyId ? withGenealogyId($link.attr('href'), genealogyId) : getGenealogyEntryUrl($link.attr('href')));
});
}
@@ -272,6 +279,7 @@
initLayui: initLayui,
getGenealogyId: getGenealogyId,
withGenealogyId: withGenealogyId,
getGenealogyEntryUrl: getGenealogyEntryUrl,
syncGenealogyContextLinks: syncGenealogyContextLinks
};
})(window, window.jQuery || (window.layui && window.layui.$));
+168
View File
@@ -0,0 +1,168 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.RelativePages = factory(root);
if (root.document) root.document.addEventListener('DOMContentLoaded', function () { root.RelativePages.init(); });
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var writePending = false;
function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; }
function query(selector, node) { return documentRef ? (node || documentRef).querySelector(selector) : null; }
function queryAll(selector, node) { return documentRef ? Array.prototype.slice.call((node || documentRef).querySelectorAll(selector)) : []; }
function trim(value) { var text = String(value === undefined || value === null ? '' : value).trim(); return text || undefined; }
function queryParam(search, name) { return new URLSearchParams(String(search || '').replace(/^\?/, '')).get(name) || ''; }
function getCurrentGenealogyId(search) {
var page = query('[data-relative-page], [data-relative-edit-page]');
var source = search === undefined && root.location ? root.location.search : search;
var profileGenealogyId;
if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) {
profileGenealogyId = root.ProfileUI.getGenealogyId();
if (profileGenealogyId) return profileGenealogyId;
}
return queryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || '';
}
function toSafeNumber(value) {
var text = trim(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isFinite(number) ? number : undefined;
}
function toSafeInteger(value) {
var number = toSafeNumber(value);
return Number.isInteger(number) && Number.isSafeInteger(number) ? number : undefined;
}
function buildRelativeRecordBody(values) {
var source = values || {};
var body = { relativeName: String(source.relativeName || '').trim() };
var fields = ['relationName', 'eventName', 'eventTime', 'recordContent', 'mediaOssIds', 'status'];
var amountText = trim(source.giftAmount);
var sortOrderText = trim(source.sortOrder);
fields.forEach(function (field) {
var value = trim(source[field]);
if (value !== undefined) body[field] = value;
});
if (amountText !== undefined) body.giftAmount = toSafeNumber(amountText);
if (sortOrderText !== undefined) body.sortOrder = toSafeInteger(sortOrderText);
return body;
}
function validateRelativeRecordBody(body) {
if (!body || !body.relativeName) return '请填写亲友姓名';
if (body.mediaOssIds !== undefined && !/^[1-9][0-9]*(,[1-9][0-9]*)*$/.test(body.mediaOssIds)) {
return '附件 OSS ID 请使用英文逗号分隔的正整数';
}
if (Object.prototype.hasOwnProperty.call(body, 'giftAmount') && body.giftAmount === undefined) return '礼金金额必须是数字';
if (Object.prototype.hasOwnProperty.call(body, 'sortOrder') && body.sortOrder === undefined) return '排序值必须是安全整数';
return '';
}
function normalizeRelativeList(data) { return Array.isArray(data) ? data : []; }
function escapeHtml(value) { return String(value === undefined || value === null ? '' : value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
function stringify(value) { try { return typeof value === 'string' ? value : JSON.stringify(value); } catch (error) { return String(value); } }
function renderRelativeRecords(data) {
var container = query('[data-relative-list]');
var records = normalizeRelativeList(data);
if (!container) return;
if (!Array.isArray(data)) {
container.innerHTML = '<div class="api-empty">亲友往来响应未按 Apifox ListResult 返回数组,无法安全展示或操作记录。</div>';
return;
}
if (!records.length) {
container.innerHTML = '<div class="api-empty">暂无亲友往来记录</div>';
return;
}
container.innerHTML = records.map(function (record, index) {
return '<article class="module-row"><div><h3>亲友往来 ' + (index + 1) + '</h3><p>' + escapeHtml(stringify(record)) + '</p></div></article>';
}).join('');
}
function showMessage(message) { if (root.layui && root.layui.layer) root.layui.layer.msg(message); else if (root.alert) root.alert(message); }
function shouldRedirectToLogin(api, error) { var status = error && (error.status || error.code); return !api || !api.getToken || !api.getToken() || Number(status) === 401; }
function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken();
if (root.location && typeof root.location.replace === 'function') root.location.replace('login.html');
else if (root.location) root.location.href = 'login.html';
return true;
}
function setFormStatus(message) { var target = query('[data-relative-form-status]'); if (target) target.textContent = message || ''; }
function setWritePending(value) { writePending = Boolean(value); queryAll('[data-relative-form] button, [data-relative-form] input, [data-relative-form] textarea').forEach(function (control) { control.disabled = writePending; }); }
function syncLinks(genealogyId) {
if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) { root.ProfileUI.syncGenealogyContextLinks(); return; }
queryAll('[data-genealogy-context-link]').forEach(function (link) { var href = link.getAttribute('href'); if (href) link.href = href.split('?')[0] + '?genealogyId=' + encodeURIComponent(genealogyId); });
}
function requireGenealogyId() {
var genealogyId = getCurrentGenealogyId();
var list = query('[data-relative-list]');
if (genealogyId) return genealogyId;
if (list) list.innerHTML = '<div class="api-empty">等待家谱入口接口;请从具体家谱进入亲友往来。</div>';
setFormStatus('等待家谱入口接口;请从具体家谱进入亲友往来。');
showMessage('等待家谱入口接口;请从具体家谱进入亲友往来。');
return '';
}
async function loadRelativeRecords() {
var api = getApi();
var genealogyId;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncLinks(genealogyId);
try { renderRelativeRecords(await api.relativeRecords(genealogyId)); }
catch (error) { if (!redirectUnauthorized(api, error)) { renderRelativeRecords(null); showMessage(error.message || '亲友往来加载失败'); } }
}
async function submitRelativeRecord(form) {
var api = getApi();
var genealogyId;
var values = {};
var body;
var validation;
if (writePending || redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; });
body = buildRelativeRecordBody(values);
validation = validateRelativeRecordBody(body);
if (validation) { setFormStatus(validation); return; }
setWritePending(true);
setFormStatus('正在保存亲友往来...');
try {
await api.createRelativeRecord(genealogyId, body);
if (root.location) root.location.href = 'profile-relative.html?genealogyId=' + encodeURIComponent(genealogyId);
} catch (error) {
if (!redirectUnauthorized(api, error)) setFormStatus(error.message || '亲友往来保存失败');
} finally { setWritePending(false); }
}
function init() {
var genealogyId;
if (!documentRef) return;
documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-relative-form]'); if (form) { event.preventDefault(); submitRelativeRecord(form); } });
if (query('[data-relative-page]')) loadRelativeRecords();
if (query('[data-relative-edit-page]')) { genealogyId = requireGenealogyId(); if (genealogyId) syncLinks(genealogyId); }
}
return { getCurrentGenealogyId: getCurrentGenealogyId, buildRelativeRecordBody: buildRelativeRecordBody, validateRelativeRecordBody: validateRelativeRecordBody, normalizeRelativeList: normalizeRelativeList, shouldRedirectToLogin: shouldRedirectToLogin, init: init };
});
+122 -72
View File
@@ -1,93 +1,143 @@
(function (window, $) {
(function (root) {
'use strict';
var defaultItems = [
'source', '|',
'undo', 'redo', '|',
'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',
'plainpaste', 'wordpaste', '|',
'justifyleft', 'justifycenter', 'justifyright', 'justifyfull',
'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', '|',
'formatblock', 'fontname', 'fontsize', '|',
'forecolor', 'hilitecolor', 'bold', 'italic', 'underline',
'strikethrough', 'lineheight', 'removeformat', '|',
'image', 'multiimage', 'table', 'hr', 'emoticons', 'baidumap',
'pagebreak', 'anchor', 'link', 'unlink', '|',
'about'
var documentRef = root.document;
var editors = {};
var submitBound = false;
var toolbarKeys = [
'headerSelect', 'fontSize', 'color', 'bgColor', '|',
'bold', 'italic', 'underline', 'through', '|',
'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyJustify', '|',
'insertTable', 'clearStyle', 'insertLink', 'undo', 'redo'
];
function getJquery() {
return $ || window.jQuery || (window.layui && window.layui.$);
function findTextarea(target) {
if (!target || !documentRef) return null;
if (typeof target === 'string') return documentRef.querySelector(target);
if (target.nodeType === 1) return target;
return target[0] || null;
}
function normalizeSelector(target) {
var jq = getJquery();
if (!target) return '';
if (typeof target === 'string') return target;
if (jq && target instanceof jq) target = target.get(0);
if (target && target.id) return '#' + target.id;
return target;
function ensureId(textarea) {
if (!textarea.id) textarea.id = 'rich-editor-' + Math.floor(Math.random() * 1000000);
return textarea.id;
}
function initRichEditor(target, options) {
var jq = getJquery();
var selector = normalizeSelector(target);
var $textarea;
function fieldValue(editor) {
return String(editor.getText() || '').trim() ? editor.getHtml() : '';
}
if (!jq) return null;
$textarea = jq(selector).first();
if (!$textarea.length || !window.KindEditor) return null;
function syncEditor(instance) {
instance.textarea.value = fieldValue(instance.editor);
}
if (!$textarea.attr('id')) {
$textarea.attr('id', 'rich-editor-' + Math.floor(Math.random() * 1000000));
selector = '#' + $textarea.attr('id');
function createRichEditor(target, options) {
var textarea = findTextarea(target);
var settings = options || {};
var id;
var host;
var toolbar;
var content;
var editor;
var wangEditor = root.wangEditor;
var instance;
if (!textarea || !wangEditor || !wangEditor.createEditor || !wangEditor.createToolbar) return null;
if (textarea.dataset.wangEditorInitialized === 'true') return editors[textarea.id] || null;
id = ensureId(textarea);
host = documentRef.createElement('div');
host.className = 'rich-editor-host';
toolbar = documentRef.createElement('div');
toolbar.className = 'rich-editor-toolbar';
toolbar.id = id + '-toolbar';
content = documentRef.createElement('div');
content.className = 'rich-editor-content';
content.id = id + '-content';
host.appendChild(toolbar);
host.appendChild(content);
textarea.insertAdjacentElement('afterend', host);
try {
editor = wangEditor.createEditor({
selector: '#' + content.id,
html: textarea.value || '',
config: Object.assign({
placeholder: textarea.getAttribute('placeholder') || '请输入内容',
onChange: function (currentEditor) {
textarea.value = fieldValue(currentEditor);
},
onBlur: function (currentEditor) {
textarea.value = fieldValue(currentEditor);
}
}, settings.editorConfig || {}),
mode: settings.mode || 'default'
});
wangEditor.createToolbar({
editor: editor,
selector: '#' + toolbar.id,
config: Object.assign({ toolbarKeys: toolbarKeys }, settings.toolbarConfig || {}),
mode: settings.mode || 'default'
});
} catch (error) {
host.remove();
return null;
}
return window.KindEditor.create(selector, jq.extend({
width: '100%',
minWidth: '100%',
height: '460px',
resizeType: 1,
allowPreviewEmoticons: true,
allowImageUpload: true,
filterMode: false,
newlineTag: 'p',
items: defaultItems,
afterBlur: function () {
this.sync();
}
}, options || {}));
textarea.hidden = true;
textarea.setAttribute('aria-hidden', 'true');
textarea.dataset.wangEditorInitialized = 'true';
instance = {
textarea: textarea,
editor: editor,
sync: function () { syncEditor(instance); },
getHtml: function () { return fieldValue(editor); },
getText: function () { return editor.getText(); }
};
editors[id] = instance;
instance.sync();
return instance;
}
function initRichEditors(root, options) {
var jq = getJquery();
var editors = {};
var $root;
function initRichEditors(rootNode, options) {
var scope = rootNode || documentRef;
var instances = {};
if (!jq) return editors;
$root = root ? jq(root) : jq(document);
$root.find('.js-rich-editor').each(function (index) {
var $textarea = jq(this);
var id = $textarea.attr('id') || ('rich-editor-' + (index + 1));
if (!scope || !scope.querySelectorAll) return instances;
Array.prototype.forEach.call(scope.querySelectorAll('.js-rich-editor'), function (textarea) {
var instance = createRichEditor(textarea, options);
$textarea.attr('id', id);
editors[id] = initRichEditor('#' + id, options);
if (instance) instances[textarea.id] = instance;
});
return editors;
return instances;
}
window.AppRichEditor = {
init: initRichEditor,
initAll: initRichEditors
function bindFormSync() {
if (submitBound || !documentRef) return;
submitBound = true;
documentRef.addEventListener('submit', function (event) {
Object.keys(editors).forEach(function (id) {
var instance = editors[id];
if (instance.textarea.form === event.target) instance.sync();
});
});
}
function initAll() {
bindFormSync();
return initRichEditors(documentRef);
}
root.AppRichEditor = {
init: createRichEditor,
initAll: initRichEditors,
syncAll: function () {
Object.keys(editors).forEach(function (id) { editors[id].sync(); });
}
};
if (getJquery()) {
getJquery()(function () {
if (getJquery()('.js-rich-editor').length) {
initRichEditors(document);
}
});
}
})(window, window.jQuery || (window.layui && window.layui.$));
if (!documentRef) return;
if (documentRef.readyState === 'loading') documentRef.addEventListener('DOMContentLoaded', initAll);
else initAll();
})(window);
+13 -15
View File
@@ -15,15 +15,15 @@
'use strict';
var documentRef = root.document;
var CAPTCHA_SCENES = {
phone: 'PC_PHONE_CHANGE',
deactivate: 'PC_ACCOUNT_DEACTIVATE'
var CAPTCHA_OPERATIONS = {
phone: 'phone-change',
deactivate: 'account-deactivate'
};
var SMS_CODE_COOLDOWN_SECONDS = 60;
var boundPhone = '';
function getCaptchaScene(formType) {
return CAPTCHA_SCENES[formType] || '';
function getCaptchaOperation(formType) {
return CAPTCHA_OPERATIONS[formType] || '';
}
function hashPassword(value) {
@@ -100,18 +100,18 @@
}
async function ensureCaptcha(form, formType, subject) {
// 发送敏感业务短信前,先取得与当前场景绑定的验证码票据。
// 发送敏感业务短信前,先取得与当前认证动作绑定的验证码票据。
var captcha = root.CaptchaPages;
var sceneCode = getCaptchaScene(formType);
var operationCode = getCaptchaOperation(formType);
if (!sceneCode) return true;
if (!operationCode) return true;
if (!captcha || !captcha.ensureToken) {
showMessage('验证码组件未加载,请刷新后重试');
return false;
}
return Boolean(await captcha.ensureToken(form, {
sceneCode: sceneCode,
operationCode: operationCode,
subject: subject || ''
}));
}
@@ -295,7 +295,7 @@
}
async function sendPhoneCode(button) {
// 换绑手机号使用独立短信场景,避免复用登录或找回密码票据。
// 换绑手机号使用独立认证动作,避免复用登录或找回密码票据。
var api = getApi();
var form = query('[data-security-form="phone"]');
var values;
@@ -311,9 +311,8 @@
button.disabled = true;
try {
await api.sendSmsCode({
await api.sendSmsCode(getCaptchaOperation('phone'), {
phone: values.phone,
sceneCode: getCaptchaScene('phone'),
validToken: values.validToken || ''
});
clearCaptchaToken(form);
@@ -342,9 +341,8 @@
button.disabled = true;
try {
await api.sendSmsCode({
await api.sendSmsCode(getCaptchaOperation('deactivate'), {
phone: boundPhone,
sceneCode: getCaptchaScene('deactivate'),
validToken: values.validToken || ''
});
clearCaptchaToken(form);
@@ -460,7 +458,7 @@
buildPasswordChangeBody: buildPasswordChangeBody,
buildPhoneChangeBody: buildPhoneChangeBody,
buildDeactivateBody: buildDeactivateBody,
getCaptchaScene: getCaptchaScene,
getCaptchaOperation: getCaptchaOperation,
isDangerConfirmed: isDangerConfirmed,
isPhone: isPhone,
isSmsCode: isSmsCode,
+4 -4
View File
@@ -32,8 +32,8 @@
}
function getUploadMode() {
// 本期只保留头像单文件上传,不再根据文件大小切换分片流程。
return 'single';
// 当前 PC 目录只定义统一分片上传流程。
return 'resumable';
}
function getApi() {
@@ -72,9 +72,9 @@
}
function uploadFileForPage(api, file) {
// 上传结果仅用于回填资料表单中的 avatarOssId
// 当前初始化响应未展开 uploadId、instant 与 OSS 字段,不能猜测分片闭环
if (!api || !file) return Promise.reject(new Error('请选择文件'));
return api.uploadFile(file);
return Promise.reject(new Error('当前 PC 文件上传响应未定义头像回填字段,暂不能上传头像'));
}
async function uploadFromInput(input) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long