refactor: 完成Handlebars模板组件化

This commit is contained in:
Zuoling Rong
2025-05-08 01:32:14 +08:00
parent 6474fa3635
commit 9ea6cb1f09
24 changed files with 1440 additions and 598 deletions

File diff suppressed because it is too large Load Diff

173
src/helpers/conditions.js Normal file
View File

@@ -0,0 +1,173 @@
/**
* Handlebars条件判断助手函数
* 提供各种条件判断功能
*/
/**
* 比较两个值是否相等
* @param {any} v1 比较值1
* @param {any} v2 比较值2
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#ifEquals type "article"}}文章{{else}}页面{{/ifEquals}}
*/
function ifEquals(v1, v2, options) {
return v1 === v2 ? options.fn(this) : options.inverse(this);
}
/**
* 比较两个值是否不相等
* @param {any} v1 比较值1
* @param {any} v2 比较值2
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#ifNotEquals status "completed"}}进行中{{else}}已完成{{/ifNotEquals}}
*/
function ifNotEquals(v1, v2, options) {
return v1 !== v2 ? options.fn(this) : options.inverse(this);
}
/**
* 通用条件比较
* @param {any} v1 比较值1
* @param {string} operator 比较运算符 ('==', '===', '!=', '!==', '<', '<=', '>', '>=')
* @param {any} v2 比较值2
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#ifCond count '>' 0}}有内容{{else}}无内容{{/ifCond}}
*/
function ifCond(v1, operator, v2, options) {
switch (operator) {
case '==':
return (v1 == v2) ? options.fn(this) : options.inverse(this);
case '===':
return (v1 === v2) ? options.fn(this) : options.inverse(this);
case '!=':
return (v1 != v2) ? options.fn(this) : options.inverse(this);
case '!==':
return (v1 !== v2) ? options.fn(this) : options.inverse(this);
case '<':
return (v1 < v2) ? options.fn(this) : options.inverse(this);
case '<=':
return (v1 <= v2) ? options.fn(this) : options.inverse(this);
case '>':
return (v1 > v2) ? options.fn(this) : options.inverse(this);
case '>=':
return (v1 >= v2) ? options.fn(this) : options.inverse(this);
case '&&':
return (v1 && v2) ? options.fn(this) : options.inverse(this);
case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
}
/**
* 检查值是否为空null、undefined、空字符串、空数组或空对象
* @param {any} value 要检查的值
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#isEmpty items}}无内容{{else}}有内容{{/isEmpty}}
*/
function isEmpty(value, options) {
if (value === null || value === undefined) {
return options.fn(this);
}
if (typeof value === 'string' && value.trim() === '') {
return options.fn(this);
}
if (Array.isArray(value) && value.length === 0) {
return options.fn(this);
}
if (typeof value === 'object' && Object.keys(value).length === 0) {
return options.fn(this);
}
return options.inverse(this);
}
/**
* 检查值是否非空
* @param {any} value 要检查的值
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#isNotEmpty items}}有内容{{else}}无内容{{/isNotEmpty}}
*/
function isNotEmpty(value, options) {
return isEmpty(value, {
fn: options.inverse,
inverse: options.fn
});
}
/**
* 条件与操作
* @param {any} a 条件A
* @param {any} b 条件B
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#and isPremium isActive}}高级活跃用户{{else}}其他用户{{/and}}
*/
function and(a, b, options) {
return (a && b) ? options.fn(this) : options.inverse(this);
}
/**
* 条件或操作
* @param {any} a 条件A
* @param {any} b 条件B
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#or isPremium isAdmin}}有权限{{else}}无权限{{/or}}
*/
function or(a, b, options) {
return (a || b) ? options.fn(this) : options.inverse(this);
}
/**
* 多个条件的或操作
* 使用方式:{{#or (ifEquals a b) (ifEquals c d)}}满足条件{{else}}不满足条件{{/or}}
* @param {...any} args 多个条件值
* @returns {boolean} 条件结果
*/
function orHelper() {
// 最后一个参数是options对象
const options = arguments[arguments.length - 1];
// 检查是否至少有一个为true的参数
for (let i = 0; i < arguments.length - 1; i++) {
if (arguments[i]) {
return options.fn(this);
}
}
return options.inverse(this);
}
/**
* 条件非操作
* @param {any} value 条件值
* @param {object} options Handlebars选项
* @returns {string} 渲染结果
* @example {{#not isDisabled}}启用{{else}}禁用{{/not}}
*/
function not(value, options) {
return !value ? options.fn(this) : options.inverse(this);
}
// 导出所有条件判断助手函数
module.exports = {
ifEquals,
ifNotEquals,
ifCond,
isEmpty,
isNotEmpty,
and,
or,
orHelper,
not
};

101
src/helpers/formatters.js Normal file
View File

@@ -0,0 +1,101 @@
/**
* Handlebars格式化助手函数
* 提供日期、文本等格式化功能
*/
/**
* 格式化日期
* @param {Date|string} date 日期对象或日期字符串
* @param {string} format 格式化模式
* @returns {string} 格式化后的日期字符串
* @example {{formatDate date "YYYY-MM-DD"}}
*/
function formatDate(date, format) {
if (!date) return '';
// 将字符串转换为日期对象
const dateObj = typeof date === 'string' ? new Date(date) : date;
if (!(dateObj instanceof Date) || isNaN(dateObj)) {
return '';
}
// 获取日期组件
const year = dateObj.getFullYear();
const month = dateObj.getMonth() + 1;
const day = dateObj.getDate();
const hours = dateObj.getHours();
const minutes = dateObj.getMinutes();
const seconds = dateObj.getSeconds();
// 格式化日期字符串
if (!format) format = 'YYYY-MM-DD';
return format
.replace('YYYY', year)
.replace('MM', month.toString().padStart(2, '0'))
.replace('DD', day.toString().padStart(2, '0'))
.replace('HH', hours.toString().padStart(2, '0'))
.replace('mm', minutes.toString().padStart(2, '0'))
.replace('ss', seconds.toString().padStart(2, '0'));
}
/**
* 限制文本长度,超出部分显示省略号
* @param {string} text 输入文本
* @param {number} length 最大长度
* @returns {string} 处理后的文本
* @example {{limit description 100}}
*/
function limit(text, length) {
if (!text) return '';
text = String(text);
if (text.length <= length) {
return text;
}
return text.substring(0, length) + '...';
}
/**
* 转换文本为小写
* @param {string} text 输入文本
* @returns {string} 小写文本
* @example {{toLowerCase title}}
*/
function toLowerCase(text) {
if (!text) return '';
return String(text).toLowerCase();
}
/**
* 转换文本为大写
* @param {string} text 输入文本
* @returns {string} 大写文本
* @example {{toUpperCase code}}
*/
function toUpperCase(text) {
if (!text) return '';
return String(text).toUpperCase();
}
/**
* 将对象转换为JSON字符串用于调试
* @param {any} obj 要转换的对象
* @returns {string} JSON字符串
* @example {{json this}}
*/
function json(obj) {
return JSON.stringify(obj, null, 2);
}
// 导出所有格式化助手函数
module.exports = {
formatDate,
limit,
toLowerCase,
toUpperCase,
json
};

64
src/helpers/index.js Normal file
View File

@@ -0,0 +1,64 @@
/**
* Handlebars助手函数中心
*
* 导入并重导出所有助手函数方便在generator中统一注册
*/
const formatters = require('./formatters');
const conditions = require('./conditions');
const utils = require('./utils');
/**
* 注册所有助手函数到Handlebars实例
* @param {Handlebars} handlebars Handlebars实例
*/
function registerAllHelpers(handlebars) {
// 注册格式化助手函数
Object.entries(formatters).forEach(([name, helper]) => {
handlebars.registerHelper(name, helper);
console.log(`Registered formatter helper: ${name}`);
});
// 注册条件判断助手函数
Object.entries(conditions).forEach(([name, helper]) => {
handlebars.registerHelper(name, helper);
console.log(`Registered condition helper: ${name}`);
});
// 注册工具类助手函数
Object.entries(utils).forEach(([name, helper]) => {
handlebars.registerHelper(name, helper);
console.log(`Registered utility helper: ${name}`);
});
// 注册HTML转义函数作为助手函数方便在模板中调用
handlebars.registerHelper('escapeHtml', function(text) {
if (text === undefined || text === null) {
return '';
}
return String(text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
});
// 注册非转义助手函数安全输出HTML
handlebars.registerHelper('safeHtml', function(text) {
if (text === undefined || text === null) {
return '';
}
return new handlebars.SafeString(text);
});
console.log('All Handlebars helpers registered successfully.');
}
// 导出所有助手函数和注册函数
module.exports = {
formatters,
conditions,
utils,
registerAllHelpers
};

179
src/helpers/utils.js Normal file
View File

@@ -0,0 +1,179 @@
/**
* Handlebars通用工具类助手函数
* 提供数组处理、字符串处理等实用功能
*/
/**
* 数组或字符串切片操作
* @param {Array|string} array 要处理的数组或字符串
* @param {number} start 起始索引
* @param {number} [end] 结束索引(可选)
* @returns {Array|string} 切片结果
* @example {{slice array 0 5}}
*/
function slice(array, start, end) {
if (!array) return [];
if (typeof array === 'string') {
return end ? array.slice(start, end) : array.slice(start);
}
if (Array.isArray(array)) {
return end ? array.slice(start, end) : array.slice(start);
}
return [];
}
/**
* 合并数组
* @param {...Array} arrays 要合并的数组
* @returns {Array} 合并后的数组
* @example {{concat array1 array2 array3}}
*/
function concat() {
const args = Array.from(arguments);
const options = args.pop(); // 最后一个参数是Handlebars的options对象
// 过滤掉非数组参数
const validArrays = args.filter(arg => Array.isArray(arg));
if (validArrays.length === 0) {
return [];
}
return Array.prototype.concat.apply([], validArrays);
}
/**
* 获取数组或对象的长度/大小
* @param {Array|Object|string} value 要计算长度的值
* @returns {number} 长度或大小
* @example {{size array}}
*/
function size(value) {
if (!value) return 0;
if (Array.isArray(value) || typeof value === 'string') {
return value.length;
}
if (typeof value === 'object') {
return Object.keys(value).length;
}
return 0;
}
/**
* 获取数组的第一个元素
* @param {Array} array 数组
* @returns {any} 第一个元素
* @example {{first items}}
*/
function first(array) {
if (!array || !Array.isArray(array) || array.length === 0) {
return undefined;
}
return array[0];
}
/**
* 获取数组的最后一个元素
* @param {Array} array 数组
* @returns {any} 最后一个元素
* @example {{last items}}
*/
function last(array) {
if (!array || !Array.isArray(array) || array.length === 0) {
return undefined;
}
return array[array.length - 1];
}
/**
* 创建一个连续范围的数组(用于循环)
* @param {number} start 起始值
* @param {number} end 结束值
* @param {number} [step=1] 步长
* @returns {Array} 范围数组
* @example {{#each (range 1 5)}}{{this}}{{/each}}
*/
function range(start, end, step = 1) {
const result = [];
if (typeof start !== 'number' || typeof end !== 'number') {
return result;
}
if (step <= 0) {
step = 1;
}
for (let i = start; i <= end; i += step) {
result.push(i);
}
return result;
}
/**
* 从对象中选择指定的属性(创建新对象)
* @param {Object} object 源对象
* @param {...string} keys 要选择的属性键
* @returns {Object} 包含选定属性的新对象
* @example {{json (pick user "name" "email")}}
*/
function pick() {
const args = Array.from(arguments);
const options = args.pop(); // 最后一个参数是Handlebars的options对象
if (args.length < 1) {
return {};
}
const obj = args[0];
const keys = args.slice(1);
if (!obj || typeof obj !== 'object') {
return {};
}
const result = {};
keys.forEach(key => {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
});
return result;
}
/**
* 将对象的所有键转换为数组
* @param {Object} object 输入对象
* @returns {Array} 键数组
* @example {{#each (keys obj)}}{{this}}{{/each}}
*/
function keys(object) {
if (!object || typeof object !== 'object') {
return [];
}
return Object.keys(object);
}
// 导出所有工具类助手函数
module.exports = {
slice,
concat,
size,
first,
last,
range,
pick,
keys
};