| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- /** 判断字段是否数组(以 [] 结尾)并返回标准名字 */
- function normalizeFieldName(name) {
- if (!name) return {isArray: false, base: name};
- if (name.endsWith('[]')) return {isArray: true, base: name.slice(0, -2)};
- return {isArray: false, base: name};
- }
- /** 格式化 Date 为 Y-m-d */
- function formatDateToYMD(date) {
- if (!date) return '';
- const y = date.getFullYear();
- const m = String(date.getMonth() + 1).padStart(2, '0');
- const d = String(date.getDate()).padStart(2, '0');
- return `${y}-${m}-${d}`;
- }
- /**
- * 从嵌套对象中获取值
- * @param {Object} obj - 源对象
- * @param {string} path - 属性路径,支持点号分隔
- * @returns {*} 属性值或 undefined
- */
- function getObjectValue(obj, path) {
- if (!obj || !path) return undefined;
- return path.split('.').reduce((cur, key) => (cur !== null && cur !== undefined) ? cur[key] : undefined, obj);
- }
- /**
- * 自动填充表单字段
- * @param {Object} data - 数据对象
- * @param {Object} options - 配置选项
- * @param {string} options.formSelector - 表单选择器,默认为 'form'
- * @param {Array} options.skipFields - 跳过的字段名
- */
- function autoPopulateForm(data, options = {}) {
- if (!data) return;
- const settings = {formSelector: 'form', skipFields: [], ...options};
- const $form = $(settings.formSelector);
- if (!$form.length) return;
- // 查询所有 input/select/textarea(包含可能是同名的多元素)
- $form.find('input, select, textarea').each(function () {
- const $el = $(this);
- const name = $el.attr('name') || $el.attr('id');
- if (!name || settings.skipFields.includes(name)) return;
- const {isArray, base} = normalizeFieldName(name);
- // 优先使用无 [] 的字段名去 data 中取值;若不存在且原名不是相同,则尝试原名
- let value = getObjectValue(data, base);
- if (value === undefined && base !== name) {
- value = getObjectValue(data, name);
- }
- if (value !== undefined) {
- const tag = $el.prop('tagName').toLowerCase();
- const type = $el.attr('type');
- const plugin = $el.attr('data-plugin');
- if (tag === 'input') {
- if (type === 'radio') {
- // $el 可能是选组:将匹配 value 的那项触发 click(保持原来用 click 的行为)
- $el.filter(`[value="${value}"]`).each(function () {
- const $this = $(this);
- if (!$this.is(':checked')) $this.click();
- });
- return;
- }
- if (type === 'checkbox') {
- if (Array.isArray(value)) {
- // 对应多个 checkbox(数组值)
- $el.each(function () {
- const $this = $(this);
- const should = value.includes($this.val());
- if ($this.is(':checked') !== should) $this.click();
- });
- } else {
- // 单一 checkbox(switchery 等插件映射 1/0)
- const shouldBeChecked = (value === true || value === 1 || value === '1' || value === 'true');
- $el.each(function () {
- const $this = $(this);
- if ($this.is(':checked') !== shouldBeChecked) $this.click();
- });
- }
- return;
- }
- // 非选择类 input
- if (plugin === 'datepicker') {
- // 设置日期,若 value 为空则传 null 以清除
- try {
- $el.datepicker('setDate', value ? new Date(value) : null);
- } catch (e) {
- // 忽略插件异常
- }
- return;
- }
- if (plugin === 'asColorPicker') {
- try {
- $el.asColorPicker('val', value);
- } catch (e) { }
- return;
- }
- $el.val(value);
- return;
- }
- if (tag === 'select') {
- if (plugin === 'multiSelect') {
- try { $el.multiSelect('select', value); } catch (e) { $el.val(value); }
- return;
- }
- if (plugin === 'selectpicker') {
- try {
- $el.selectpicker('val', value);
- $el.selectpicker('refresh');
- } catch (e) {
- $el.val(value);
- }
- return;
- }
- $el.val(value);
- return;
- }
- if (tag === 'textarea') {
- $el.val(value);
- return;
- }
- }
- });
- }
- /**
- * 收集表单数据
- * @param {string|Object} formSelector - 表单选择器或jQuery对象
- * @param {Object} options - 配置选项
- * @param {Array} options.excludeFields - 排除的字段
- * @param {Array} options.removeEmpty - 过滤掉空字符串
- * @returns {Object} 表单数据对象
- */
- function collectFormData(formSelector, options = {}) {
- const $form = (typeof formSelector === 'string') ? $(formSelector) : formSelector;
- if (!$form || !$form.length) return {};
- const settings = {excludeFields: [], removeEmpty: false, ...options};
- const formData = {};
- // 查找非 hidden 的 input/select/textarea(但还要跳过 data-hidden / [hidden] 或父元素 data-hidden)
- $form.find('input:not([hidden]), select:not([hidden]), textarea:not([hidden])').each(function () {
- const $el = $(this);
- const name = $el.attr('name');
- const type = $el.attr('type');
- const tag = $el.prop('tagName').toLowerCase();
- const {isArray, base} = normalizeFieldName(name);
- if (!name || settings.excludeFields.includes(base)) return;
- // 跳过通过 hide() / data-hidden 或父元素 data-hidden 隐藏的元素
- if ($el.is('[hidden], [data-hidden]') || $el.closest('[data-hidden]').length > 0) return;
- let value;
- const plugin = $el.attr('data-plugin');
- if (tag === 'input') {
- if (type === 'checkbox') {
- if (isArray) {
- // collect all checked ones by pushing to array
- if (!$el.is(':checked')) {
- // 不勾选时不推入
- } else {
- if (!formData[base]) formData[base] = [];
- formData[base].push($el.val());
- }
- return; // 已在数组处理,不继续后续赋值逻辑
- }
- // 非数组 checkbox:可能是 switchery (取 1/0),或普通单选取值/否则 null
- if (plugin === 'switchery') {
- value = $el.is(':checked') ? 1 : 0;
- } else {
- value = $el.is(':checked') ? $el.val() : null;
- }
- } else if (type === 'radio') {
- // 仅在 checked 时读取值;避免覆盖其他同名 radio
- if ($el.is(':checked')) value = $el.val();
- else return;
- } else {
- // 其他 input,特殊插件处理
- if (plugin === 'datepicker' || $el.parent().hasClass('input-daterange')) {
- value = formatDateToYMD($el.datepicker('getDate'));
- } else if (plugin === 'asColorPicker') {
- value = $el.asColorPicker('val');
- } else {
- value = $el.val();
- }
- }
- } else if (tag === 'select') {
- value = $el.prop('multiple') ? ($el.val() || []) : $el.val();
- } else if (tag === 'textarea') {
- value = $el.val();
- }
- // 将值写入 formData(注意 radio 与其他覆盖逻辑)
- if (isArray) {
- if (!formData[base]) formData[base] = [];
- if (value !== undefined && value !== null) {
- formData[base].push(...(Array.isArray(value) ? value : [value]));
- }
- } else if (value !== undefined) {
- // 保持原逻辑:避免 radio 被未选覆盖(radio 在未选时直接 return)
- if (formData[base] === undefined || type !== 'radio' || value !== null) {
- formData[base] = value;
- }
- }
- });
- // removeEmpty 过滤空字符串 / null / undefined / 空数组
- if (settings.removeEmpty) {
- return Object.fromEntries(Object.entries(formData).filter(([_, v]) => {
- if (Array.isArray(v)) return v.length > 0;
- return v !== "" && v !== null && v !== undefined;
- }));
- }
- return formData;
- }
- /* -----------------------
- jQuery hide/show/toggle 拦截(保留原有行为)
- ----------------------- */
- (function ($) {
- const origHide = $.fn.hide;
- const origShow = $.fn.show;
- const origToggle = $.fn.toggle;
- // 仅在实际状态变化时修改属性(减少 DOM 写入)
- function setDataHiddenIfChanged($els, hidden) {
- const attrVal = hidden ? 'true' : null;
- $els.each(function () {
- const cur = this.getAttribute('data-hidden');
- if (cur !== attrVal) {
- if (attrVal === null) this.removeAttribute('data-hidden');
- else this.setAttribute('data-hidden', 'true');
- }
- });
- }
- // hide -> 执行原 hide 后标记为隐藏
- $.fn.hide = function () {
- const res = origHide.apply(this, arguments);
- // 被隐藏 -> data-hidden = 'true'
- setDataHiddenIfChanged(this, true);
- return res;
- };
- // show -> 执行原 show 后移除标记
- $.fn.show = function () {
- const res = origShow.apply(this, arguments);
- // 被显示 -> 移除 data-hidden
- setDataHiddenIfChanged(this, false);
- return res;
- };
- // toggle 需要处理两种情况:传入布尔或不传
- $.fn.toggle = function (state) {
- if (typeof state === 'boolean') {
- // 如果有布尔参数,原生方法会按 state 显示/隐藏
- const res = origToggle.call(this, state);
- // state === true -> show -> hidden = false
- setDataHiddenIfChanged(this, !state);
- return res;
- }
- // 无参数:调用原始 toggle,然后依据最终可见性标记
- const res = origToggle.apply(this, arguments);
- // 逐项检查最终是否可见(避免计算样式多次:一次查询 .is(':visible'))
- this.each(function () {
- const $el = $(this);
- // :visible 计算开销可接受(仅在 toggle 时),并能反映 CSS/display/class 的最终结果
- const isVisible = $el.is(':visible');
- setDataHiddenIfChanged($el, !isVisible);
- });
- return res;
- };
- })(jQuery);
|