121 lines
9.1 KiB
JavaScript
121 lines
9.1 KiB
JavaScript
/* Presentation only: original scores and recommendation rules remain in SkinScore. */
|
||
(function(root, factory) {
|
||
if (typeof module === 'object' && module.exports) module.exports = factory(require('./report-copy.js'));
|
||
else root.ReportUI = factory(root.ReportCopy);
|
||
})(typeof window !== 'undefined' ? window : this, function(Copy) {
|
||
'use strict';
|
||
var defs = [
|
||
['contour', '轮廓', 'firm', 'contour'], ['wrinkles', '皱纹', 'firm', 'wrinkles'],
|
||
['color', '肤色', 'vita', 'tone'], ['smooth', '平滑度', 'vita', 'smooth'], ['water', '水分', 'vita', 'tone'],
|
||
['sens', '泛红', 'stab', 'sens'], ['acne', '痘痘', 'stab', 'acne']
|
||
];
|
||
var products = [
|
||
{ tier: 1, match: '25+束紧精华', title: "1'me 主场精华 I", name: '以我本然有光蓄活', image: 'assets/img/ui-20261009/product-25.png' },
|
||
{ tier: 2, match: '35+支撑精华', title: "1'me 主场精华 II", name: '以我自在有势挺括', image: 'assets/img/ui-20261009/product-35.png' },
|
||
{ tier: 3, match: '50+重塑精华', title: "1'me 主场精华 III", name: '以我从容有态构塑', image: 'assets/img/ui-20261009/product-50.png' }
|
||
];
|
||
var levelMap = { '预警': '改善', '改善': '良好', '良好': '优秀' };
|
||
var dimNames = { firm: '紧塑力', vita: '透亮感', stab: '稳韧力' };
|
||
function numeric(value) { return typeof value === 'number' && isFinite(value); }
|
||
function scalar(value) { return numeric(value) ? value : value && numeric(value.value) ? value.value : null; }
|
||
var wrinkleDefs = [
|
||
['forehead', '抬头纹(额头纹)', ['forehead_wrinkle_info'], 'forehead_count'],
|
||
['left_nasolabial', '左法令纹', ['left_nasolabial_wrinkle_info', 'left_nasolabial_fold_info'], 'left_nasolabial_count'],
|
||
['right_nasolabial', '右法令纹', ['right_nasolabial_wrinkle_info', 'right_nasolabial_fold_info'], 'right_nasolabial_count'],
|
||
['left_crowsfeet', '左鱼尾纹', ['left_crowsfeet_wrinkle_info'], 'left_crowsfeet_count'],
|
||
['right_crowsfeet', '右鱼尾纹', ['right_crowsfeet_wrinkle_info'], 'right_crowsfeet_count'],
|
||
['left_eye', '左眼部细纹', ['left_eye_wrinkle_info'], 'left_undereye_count'],
|
||
['right_eye', '右眼部细纹', ['right_eye_wrinkle_info'], 'right_undereye_count'],
|
||
['left_mouth', '左嘴角纹', ['left_mouth_wrinkle_info'], 'left_mouth_count'],
|
||
['right_mouth', '右嘴角纹', ['right_mouth_wrinkle_info'], 'right_mouth_count'],
|
||
['glabella', '眉间纹', ['glabella_wrinkle_info'], 'glabella_count'],
|
||
['left_cheek', '左脸颊纹', ['left_cheek_wrinkle_info'], 'left_cheek_count'],
|
||
['right_cheek', '右脸颊纹', ['right_cheek_wrinkle_info'], 'right_cheek_count']
|
||
];
|
||
var wrinkleMetrics = [
|
||
['wrinkle_score', '皱纹分'], ['wrinkle_severity_level', '严重等级'],
|
||
['wrinkle_deep_num', '深纹条数'], ['wrinkle_shallow_num', '浅纹条数'],
|
||
['wrinkle_norm_length', '标准化长度'], ['wrinkle_norm_depth', '标准化深度'],
|
||
['wrinkle_pixel_density', '像素密度'], ['wrinkle_area_ratio', '面积比例'], ['wrinkle_deep_ratio', '深纹比例']
|
||
];
|
||
function wrinkleDetails(result) {
|
||
result = result || {};
|
||
return wrinkleDefs.map(function (def) {
|
||
var node = null;
|
||
def[2].some(function (key) { if (result[key] && typeof result[key] === 'object') { node = result[key]; return true; } return false; });
|
||
var values = {};
|
||
wrinkleMetrics.forEach(function (metric) { values[metric[0]] = scalar(node && node[metric[0]]); });
|
||
// Region counts are a valid fallback; a missing count is never zero.
|
||
if (values.wrinkle_deep_num == null) values.wrinkle_deep_num = scalar(result.wrinkle_count && result.wrinkle_count[def[3]]);
|
||
var fine = result.fine_line || result.fine_line_count;
|
||
if (values.wrinkle_shallow_num == null) values.wrinkle_shallow_num = scalar(fine && fine[def[3]]);
|
||
return { key: def[0], name: def[1], metrics: values };
|
||
});
|
||
}
|
||
function metricText(value) { return value == null ? '未获取' : String(Math.round(value * 10000) / 10000); }
|
||
function wrinkleText(rows) {
|
||
return '\n分区明细(左右以本人为准;抬头纹与额头纹为同一分区):\n' + rows.map(function (row) {
|
||
return row.name + ':' + wrinkleMetrics.map(function (metric) { return metric[1] + ' ' + metricText(row.metrics[metric[0]]); }).join(',') + '。';
|
||
}).join('\n');
|
||
}
|
||
function fill(template, vars) {
|
||
return template.replace(/\{([a-z_]+)\}/g, function(_, key) {
|
||
var v = vars[key];
|
||
return v == null ? '未获取' : numeric(v) ? String(Math.round(v * 100) / 100) : String(v);
|
||
}).replace(/\s+/g, ' ').trim();
|
||
}
|
||
function count(result, key) { var v = result[key]; return v && numeric(v.count) ? v.count : null; }
|
||
function build(vm, result) {
|
||
result = result || {};
|
||
var views = defs.map(function(d) {
|
||
var cat = vm.cats[d[3]], vars = Object.assign({}, cat.vars || {});
|
||
var level = levelMap[cat.level], valid = cat.P != null;
|
||
if (d[0] === 'color' && vars.melanin == null) valid = false;
|
||
if (d[0] === 'water' && vars.water_score == null) valid = false;
|
||
var template = valid && level ? Copy.conclusion[d[0]][level] : '';
|
||
if (d[0] === 'contour') {
|
||
var severity = vars.dark_circle_sev;
|
||
var description = numeric(severity) ? ['无明显黑眼圈', '黑眼圈轻度', '黑眼圈中度', '黑眼圈重度'][severity] : null;
|
||
template = template.replace(/黑眼圈轻\/中\/重度(根据分数给出判断)/g, description || '黑眼圈数据未获取');
|
||
if (vars.la == null || vars.ra == null) template = template.replace(/左右下颌线角度(?:分别为左|左) \{la\}°\/ 右 \{ra\}°[;;]/g, '侧脸角度数据未完整获取;');
|
||
}
|
||
if (d[0] === 'acne') {
|
||
var pustules = count(result, 'acne_pustule'), nodules = count(result, 'acne_nodule');
|
||
var heavy = [pustules > 0 ? '脓包' : '', nodules > 0 ? '结节' : ''].filter(Boolean);
|
||
vars.heavy_note = heavy.length ? ',含' + heavy.join('、') + '等中重度皮损,炎症反应明显' : '';
|
||
if (heavy.length) { level = '改善'; template = Copy.conclusion.acne[level]; }
|
||
else if (vars.acne_cnt > 0 && level === '优秀') { level = '良好'; template = Copy.conclusion.acne[level]; }
|
||
if (vars.acne_cnt == null) template = '本次未获取完整皮损计数,请结合检测图片查看。';
|
||
else if (vars.acne_cnt === 0) template = '本次未检出明显皮损,请结合检测图片查看。';
|
||
}
|
||
// Missing counts are unknown, not zero: remove only the unavailable metric.
|
||
if (vars.fl_cnt == null) template = template.replace(/、细纹 \{fl_cnt\} 条/g, '(细纹计数未获取)');
|
||
if (vars.wr_cnt == null) template = template.replace(/皱纹 \{wr_cnt\} 条/g, '皱纹计数未获取');
|
||
if (vars.water_area == null) template = template.replace(/缺水面积 \{water_area\}%[、,]/g, '缺水面积未获取;');
|
||
if (vars.water_sev == null) template = template.replace(/肌肤缺水比较严重 \{water_sev\},/g, '');
|
||
if (vars.sens_area == null) template = template.replace(/泛红面积(?:达 | )\{sens_area\}%[,;]/g, '泛红面积未获取;');
|
||
if (vars.pore_cnt == null) template = template.replace(/粗大毛孔 \{pore_cnt\} 颗/g, '毛孔计数未获取');
|
||
if (vars.blackhead_cnt == null) template = template.replace(/黑头 \{blackhead_cnt\} 颗/g, '黑头计数未获取');
|
||
if (d[0] === 'smooth' && valid && level === '优秀') {
|
||
// A high aggregate score does not mean every localized pore is absent.
|
||
if (vars.pore_cnt == null || vars.blackhead_cnt == null) template = '肌肤质感整体较好,但毛孔或黑头计数未完整获取,请结合检测图查看。';
|
||
else if (vars.pore_cnt > 0 || vars.blackhead_cnt > 0) template = '肌肤质感整体较好;本次检测到粗大毛孔 {pore_cnt} 颗、黑头 {blackhead_cnt} 颗,局部仍有可改善之处。';
|
||
}
|
||
var details = d[0] === 'wrinkles' ? wrinkleDetails(result) : null;
|
||
var advice = level ? Copy.care[d[0]][level] : null;
|
||
return { key: d[0], name: d[1], dim: d[2], source: d[3], P: cat.P, level: level || null,
|
||
text: (valid ? fill(template, vars) : '本次未检出有效数据,请重新拍摄后查看。') + (details ? wrinkleText(details) : ''),
|
||
valid: valid, advice: valid ? advice : null };
|
||
});
|
||
// Preserve selection of the two weakest original categories. Tone expands into
|
||
// color+water advice, with thresholds inherited from the same original category.
|
||
var ranked = Object.keys(vm.cats).filter(function(k) { return vm.cats[k].P != null; })
|
||
.sort(function(a,b) { return vm.cats[a].P - vm.cats[b].P; }).slice(0,2);
|
||
var chosen = views.filter(function(v) { return ranked.indexOf(v.source) >= 0 && v.advice; });
|
||
var care = chosen.map(function(v) { return { name: v.name, text: v.advice.care + v.advice.lifestyle }; });
|
||
var product = products.filter(function(p) { return p.match === vm.essence.name; })[0] || null;
|
||
return { version: Copy.version + '+feedback-2026-10-12', dims: dimNames, views: views, care: care, product: product };
|
||
}
|
||
return { build: build, products: products, levelMap: levelMap, dimNames: dimNames, wrinkleDetails: wrinkleDetails };
|
||
});
|