admin.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /** 判断字段是否数组(以 [] 结尾)并返回标准名字 */
  2. function normalizeFieldName(name) {
  3. if (!name) return {isArray: false, base: name};
  4. if (name.endsWith('[]')) return {isArray: true, base: name.slice(0, -2)};
  5. return {isArray: false, base: name};
  6. }
  7. /** 格式化 Date 为 Y-m-d */
  8. function formatDateToYMD(date) {
  9. if (!date) return '';
  10. const y = date.getFullYear();
  11. const m = String(date.getMonth() + 1).padStart(2, '0');
  12. const d = String(date.getDate()).padStart(2, '0');
  13. return `${y}-${m}-${d}`;
  14. }
  15. /**
  16. * 从嵌套对象中获取值
  17. * @param {Object} obj - 源对象
  18. * @param {string} path - 属性路径,支持点号分隔
  19. * @returns {*} 属性值或 undefined
  20. */
  21. function getObjectValue(obj, path) {
  22. if (!obj || !path) return undefined;
  23. return path.split('.').reduce((cur, key) => (cur !== null && cur !== undefined) ? cur[key] : undefined, obj);
  24. }
  25. /**
  26. * 自动填充表单字段
  27. * @param {Object} data - 数据对象
  28. * @param {Object} options - 配置选项
  29. * @param {string} options.formSelector - 表单选择器,默认为 'form'
  30. * @param {Array} options.skipFields - 跳过的字段名
  31. */
  32. function autoPopulateForm(data, options = {}) {
  33. if (!data) return;
  34. const settings = {formSelector: 'form', skipFields: [], ...options};
  35. const $form = $(settings.formSelector);
  36. if (!$form.length) return;
  37. // 查询所有 input/select/textarea(包含可能是同名的多元素)
  38. $form.find('input, select, textarea').each(function () {
  39. const $el = $(this);
  40. const name = $el.attr('name') || $el.attr('id');
  41. if (!name || settings.skipFields.includes(name)) return;
  42. const {isArray, base} = normalizeFieldName(name);
  43. // 优先使用无 [] 的字段名去 data 中取值;若不存在且原名不是相同,则尝试原名
  44. let value = getObjectValue(data, base);
  45. if (value === undefined && base !== name) {
  46. value = getObjectValue(data, name);
  47. }
  48. if (value !== undefined) {
  49. const tag = $el.prop('tagName').toLowerCase();
  50. const type = $el.attr('type');
  51. const plugin = $el.attr('data-plugin');
  52. if (tag === 'input') {
  53. if (type === 'radio') {
  54. // $el 可能是选组:将匹配 value 的那项触发 click(保持原来用 click 的行为)
  55. $el.filter(`[value="${value}"]`).each(function () {
  56. const $this = $(this);
  57. if (!$this.is(':checked')) $this.click();
  58. });
  59. return;
  60. }
  61. if (type === 'checkbox') {
  62. if (Array.isArray(value)) {
  63. // 对应多个 checkbox(数组值)
  64. $el.each(function () {
  65. const $this = $(this);
  66. const should = value.includes($this.val());
  67. if ($this.is(':checked') !== should) $this.click();
  68. });
  69. } else {
  70. // 单一 checkbox(switchery 等插件映射 1/0)
  71. const shouldBeChecked = (value === true || value === 1 || value === '1' || value === 'true');
  72. $el.each(function () {
  73. const $this = $(this);
  74. if ($this.is(':checked') !== shouldBeChecked) $this.click();
  75. });
  76. }
  77. return;
  78. }
  79. // 非选择类 input
  80. if (plugin === 'datepicker') {
  81. // 设置日期,若 value 为空则传 null 以清除
  82. try {
  83. $el.datepicker('setDate', value ? new Date(value) : null);
  84. } catch (e) {
  85. // 忽略插件异常
  86. }
  87. return;
  88. }
  89. if (plugin === 'asColorPicker') {
  90. try {
  91. $el.asColorPicker('val', value);
  92. } catch (e) { }
  93. return;
  94. }
  95. $el.val(value);
  96. return;
  97. }
  98. if (tag === 'select') {
  99. if (plugin === 'multiSelect') {
  100. try { $el.multiSelect('select', value); } catch (e) { $el.val(value); }
  101. return;
  102. }
  103. if (plugin === 'selectpicker') {
  104. try {
  105. $el.selectpicker('val', value);
  106. $el.selectpicker('refresh');
  107. } catch (e) {
  108. $el.val(value);
  109. }
  110. return;
  111. }
  112. $el.val(value);
  113. return;
  114. }
  115. if (tag === 'textarea') {
  116. $el.val(value);
  117. return;
  118. }
  119. }
  120. });
  121. }
  122. /**
  123. * 收集表单数据
  124. * @param {string|Object} formSelector - 表单选择器或jQuery对象
  125. * @param {Object} options - 配置选项
  126. * @param {Array} options.excludeFields - 排除的字段
  127. * @param {Array} options.removeEmpty - 过滤掉空字符串
  128. * @returns {Object} 表单数据对象
  129. */
  130. function collectFormData(formSelector, options = {}) {
  131. const $form = (typeof formSelector === 'string') ? $(formSelector) : formSelector;
  132. if (!$form || !$form.length) return {};
  133. const settings = {excludeFields: [], removeEmpty: false, ...options};
  134. const formData = {};
  135. // 查找非 hidden 的 input/select/textarea(但还要跳过 data-hidden / [hidden] 或父元素 data-hidden)
  136. $form.find('input:not([hidden]), select:not([hidden]), textarea:not([hidden])').each(function () {
  137. const $el = $(this);
  138. const name = $el.attr('name');
  139. const type = $el.attr('type');
  140. const tag = $el.prop('tagName').toLowerCase();
  141. const {isArray, base} = normalizeFieldName(name);
  142. if (!name || settings.excludeFields.includes(base)) return;
  143. // 跳过通过 hide() / data-hidden 或父元素 data-hidden 隐藏的元素
  144. if ($el.is('[hidden], [data-hidden]') || $el.closest('[data-hidden]').length > 0) return;
  145. let value;
  146. const plugin = $el.attr('data-plugin');
  147. if (tag === 'input') {
  148. if (type === 'checkbox') {
  149. if (isArray) {
  150. // collect all checked ones by pushing to array
  151. if (!$el.is(':checked')) {
  152. // 不勾选时不推入
  153. } else {
  154. if (!formData[base]) formData[base] = [];
  155. formData[base].push($el.val());
  156. }
  157. return; // 已在数组处理,不继续后续赋值逻辑
  158. }
  159. // 非数组 checkbox:可能是 switchery (取 1/0),或普通单选取值/否则 null
  160. if (plugin === 'switchery') {
  161. value = $el.is(':checked') ? 1 : 0;
  162. } else {
  163. value = $el.is(':checked') ? $el.val() : null;
  164. }
  165. } else if (type === 'radio') {
  166. // 仅在 checked 时读取值;避免覆盖其他同名 radio
  167. if ($el.is(':checked')) value = $el.val();
  168. else return;
  169. } else {
  170. // 其他 input,特殊插件处理
  171. if (plugin === 'datepicker' || $el.parent().hasClass('input-daterange')) {
  172. value = formatDateToYMD($el.datepicker('getDate'));
  173. } else if (plugin === 'asColorPicker') {
  174. value = $el.asColorPicker('val');
  175. } else {
  176. value = $el.val();
  177. }
  178. }
  179. } else if (tag === 'select') {
  180. value = $el.prop('multiple') ? ($el.val() || []) : $el.val();
  181. } else if (tag === 'textarea') {
  182. value = $el.val();
  183. }
  184. // 将值写入 formData(注意 radio 与其他覆盖逻辑)
  185. if (isArray) {
  186. if (!formData[base]) formData[base] = [];
  187. if (value !== undefined && value !== null) {
  188. formData[base].push(...(Array.isArray(value) ? value : [value]));
  189. }
  190. } else if (value !== undefined) {
  191. // 保持原逻辑:避免 radio 被未选覆盖(radio 在未选时直接 return)
  192. if (formData[base] === undefined || type !== 'radio' || value !== null) {
  193. formData[base] = value;
  194. }
  195. }
  196. });
  197. // removeEmpty 过滤空字符串 / null / undefined / 空数组
  198. if (settings.removeEmpty) {
  199. return Object.fromEntries(Object.entries(formData).filter(([_, v]) => {
  200. if (Array.isArray(v)) return v.length > 0;
  201. return v !== "" && v !== null && v !== undefined;
  202. }));
  203. }
  204. return formData;
  205. }
  206. /* -----------------------
  207. jQuery hide/show/toggle 拦截(保留原有行为)
  208. ----------------------- */
  209. (function ($) {
  210. const origHide = $.fn.hide;
  211. const origShow = $.fn.show;
  212. const origToggle = $.fn.toggle;
  213. // 仅在实际状态变化时修改属性(减少 DOM 写入)
  214. function setDataHiddenIfChanged($els, hidden) {
  215. const attrVal = hidden ? 'true' : null;
  216. $els.each(function () {
  217. const cur = this.getAttribute('data-hidden');
  218. if (cur !== attrVal) {
  219. if (attrVal === null) this.removeAttribute('data-hidden');
  220. else this.setAttribute('data-hidden', 'true');
  221. }
  222. });
  223. }
  224. // hide -> 执行原 hide 后标记为隐藏
  225. $.fn.hide = function () {
  226. const res = origHide.apply(this, arguments);
  227. // 被隐藏 -> data-hidden = 'true'
  228. setDataHiddenIfChanged(this, true);
  229. return res;
  230. };
  231. // show -> 执行原 show 后移除标记
  232. $.fn.show = function () {
  233. const res = origShow.apply(this, arguments);
  234. // 被显示 -> 移除 data-hidden
  235. setDataHiddenIfChanged(this, false);
  236. return res;
  237. };
  238. // toggle 需要处理两种情况:传入布尔或不传
  239. $.fn.toggle = function (state) {
  240. if (typeof state === 'boolean') {
  241. // 如果有布尔参数,原生方法会按 state 显示/隐藏
  242. const res = origToggle.call(this, state);
  243. // state === true -> show -> hidden = false
  244. setDataHiddenIfChanged(this, !state);
  245. return res;
  246. }
  247. // 无参数:调用原始 toggle,然后依据最终可见性标记
  248. const res = origToggle.apply(this, arguments);
  249. // 逐项检查最终是否可见(避免计算样式多次:一次查询 .is(':visible'))
  250. this.each(function () {
  251. const $el = $(this);
  252. // :visible 计算开销可接受(仅在 toggle 时),并能反映 CSS/display/class 的最终结果
  253. const isVisible = $el.is(':visible');
  254. setDataHiddenIfChanged($el, !isVisible);
  255. });
  256. return res;
  257. };
  258. })(jQuery);