From aa6e83076c79091589a77013990cb0d2584a07b1 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 3 Mar 2025 18:07:54 +0800 Subject: [PATCH] Enhance date picker functionality to support date range selection and timezone normalization --- .../AbpDatePickerBaseTagHelperService.cs | 111 +++++++++++------- .../AbpDatePickerTagHelperService.cs | 24 +++- .../AbpDateRangePickerTagHelperService.cs | 34 ++++-- .../bootstrap/modal-manager.js | 2 +- .../date-range-picker-extensions.js | 27 +++-- npm/packs/core/src/abp.js | 46 ++++---- 6 files changed, 152 insertions(+), 92 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs index bd3a4abe48..786687696a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs @@ -16,63 +16,28 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.Json; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelperService where TTagHelper : AbpDatePickerBaseTagHelper { - protected readonly Dictionary> SupportedInputTypes = new() - { - { - typeof(string), o => - { - if(o is string s && DateTime.TryParse(s, out var dt)) - { - return dt.ToString("O"); - } - - return string.Empty; - } - }, - { - typeof(DateTime), o => - { - if(o is DateTime dt && dt != default) - { - return dt.ToString("O"); - } - - return string.Empty; - } - }, - {typeof(DateTime?), o => ((DateTime?) o)?.ToString("O")!}, - { - typeof(DateTimeOffset), o => - { - if(o is DateTimeOffset dto && dto != default) - { - return dto.ToString("O"); - } - - return string.Empty; - } - }, - {typeof(DateTimeOffset?), o => ((DateTimeOffset?) o)?.ToString("O")!} - }; + protected readonly Dictionary> SupportedInputTypes; protected readonly IJsonSerializer JsonSerializer; protected readonly IHtmlGenerator Generator; protected readonly HtmlEncoder Encoder; protected readonly IServiceProvider ServiceProvider; protected readonly IAbpTagHelperLocalizer TagHelperLocalizer; + protected readonly IClock Clock; protected virtual string TagName { get; set; } = "abp-date-picker"; protected IStringLocalizer L { get; } protected abstract TagHelperOutput TagHelperOutput { get; set; } protected AbpDatePickerBaseTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, - IAbpTagHelperLocalizer tagHelperLocalizer) + IAbpTagHelperLocalizer tagHelperLocalizer, IClock clock) { JsonSerializer = jsonSerializer; Generator = generator; @@ -80,6 +45,65 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp ServiceProvider = serviceProvider; L = l; TagHelperLocalizer = tagHelperLocalizer; + Clock = clock; + + SupportedInputTypes = new Dictionary> + { + { + typeof(string), x => + { + if(x is string s && DateTime.TryParse(s, out var dt)) + { + return Clock.Convert(dt).ToString("O"); + } + + return string.Empty; + } + }, + { + typeof(DateTime), x => + { + if(x is DateTime dt && dt != default) + { + return Clock.Convert(dt).ToString("O"); + } + + return string.Empty; + } + }, + { + typeof(DateTime?), x => + { + if(x is DateTime dt && dt != default) + { + return Clock.Convert(dt).ToString("O"); + } + return string.Empty; + } + }, + { + typeof(DateTimeOffset), x => + { + if(x is DateTimeOffset dto && dto != default) + { + return Clock.Convert(dto).ToString("O"); + } + + return string.Empty; + } + }, + { + typeof(DateTimeOffset?), x => + { + if(x is DateTimeOffset dto && dto != default) + { + return Clock.Convert(dto).ToString("O"); + } + + return string.Empty; + } + } + }; } protected virtual T? GetAttribute() where T : Attribute @@ -233,6 +257,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp } protected abstract int GetOrder(); + protected abstract void AddBaseTagAttributes(TagHelperAttributeList attributes); protected virtual string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output) @@ -375,7 +400,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp { attrList.Add("data-visible-date-format", options.VisibleDateFormat); } - + if(!options.InputDateFormat.IsNullOrEmpty()) { attrList.Add("data-input-date-format", options.InputDateFormat); @@ -754,7 +779,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp { return Task.FromResult(string.Empty); } - + return GetValidationAsHtmlByInputAsync(context, output, @for); } @@ -766,7 +791,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext }; var attributeList = new TagHelperAttributeList { { "class", "text-danger" } }; - + if(!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null) { if (nameAttribute != null) @@ -776,7 +801,7 @@ public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelp nameAttribute = new TagHelperAttribute("name", "date_" + Guid.NewGuid().ToString("N")); output.Attributes.Add(nameAttribute); } - + attributeList.Add("data-valmsg-for", nameAttribute.Value); return await validationMessageTagHelper.RenderAsync(attributeList, context, Encoder, "span", diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs index 3fc2861b59..9781a69b05 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs @@ -8,14 +8,22 @@ using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.Json; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService { - public AbpDatePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, IAbpTagHelperLocalizer tagHelperLocalizer) : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer) + public AbpDatePickerTagHelperService( + IJsonSerializer jsonSerializer, + IHtmlGenerator generator, + HtmlEncoder encoder, + IServiceProvider serviceProvider, + IStringLocalizer l, + IAbpTagHelperLocalizer tagHelperLocalizer, + IClock clock) + : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock) { - } protected override TagHelperOutput TagHelperOutput { get; set; } = default!; @@ -42,10 +50,18 @@ public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService { - public AbpDateRangePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, - HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, - IAbpTagHelperLocalizer tagHelperLocalizer) : - base(jsonSerializer, generator, encoder, serviceProvider, l, - tagHelperLocalizer) + public AbpDateRangePickerTagHelperService( + IJsonSerializer jsonSerializer, IHtmlGenerator generator, + HtmlEncoder encoder, + IServiceProvider serviceProvider, + IStringLocalizer l, + IAbpTagHelperLocalizer tagHelperLocalizer, + IClock clock) : + base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock) { } @@ -42,6 +46,13 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ InputTypeName = "hidden" }; + if (Clock.SupportsMultipleTimezone && TagHelper.AspForStart.Model is DateTime dateTime) + { + StartDateTagHelper.Format = "{0:O}"; + StartDateTagHelper.Value = Clock.Convert(dateTime).ToString("O"); + startDateAttributes.Add("value", StartDateTagHelper.Value); + } + StartDateTagHelperOutput = await StartDateTagHelper.ProcessAndGetOutputAsync(startDateAttributes, context, "input"); } @@ -55,6 +66,13 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ InputTypeName = "hidden" }; + if (Clock.SupportsMultipleTimezone && TagHelper.AspForEnd.Model is DateTime dateTime) + { + EndDateTagHelper.Format = "{0:O}"; + EndDateTagHelper.Value = Clock.Convert(dateTime).ToString("O"); + endDateAttributes.Add("value", EndDateTagHelper.Value); + } + EndDateTagHelperOutput = await EndDateTagHelper.ProcessAndGetOutputAsync(endDateAttributes, context, "input"); } @@ -78,7 +96,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ protected override int GetOrder() { - return TagHelper.Order; + return TagHelper.AspForStart?.Metadata.Order ?? 0; } protected override void AddBaseTagAttributes(TagHelperAttributeList attributes) @@ -92,7 +110,7 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ attributes.Add("data-start-date", convert); } } - + if (TagHelper.AspForEnd?.Model != null && SupportedInputTypes.TryGetValue(TagHelper.AspForEnd.Metadata.ModelType, out var convertFuncEnd)) { @@ -113,4 +131,4 @@ public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperServ { return TagHelper.AspForStart ?? TagHelper.AspForEnd; } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js index 9bba9ee9a9..e11a8f4d99 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js @@ -110,7 +110,7 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f _onOpenCallbacks.triggerAll(_publicApi); - if ($firstVisibleInput.data("datepicker")) { + if ($firstVisibleInput.data("datepicker") || $firstVisibleInput.data("daterangepicker")) { return; //don't pop-up date pickers... } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js index 08f3664c23..29facc3ac8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js @@ -754,16 +754,14 @@ }); $.fn.handleDatepicker = function (datepickerSelector) { - var $this = $(this); + var $this = $(this); var datepickers = $this.find(datepickerSelector); - $this.find('input[class="hidden-datepicker"]').remove(); - + $this.find('input[class~="hidden-datepicker"]').remove(); datepickers.each(function () { - var $this = $(this); - if($this.val() === ''){ + var $this = $(this); + if ($this.val() === '') { return; } - var name = $this.attr('name') || $this.data('name'); $this.data('name', name).removeAttr('name'); var datepicker = $this.data('daterangepicker'); @@ -772,13 +770,16 @@ var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); $this.after(startDateInput); } else { - var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); - var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); - $this.after(startDateInput); - - var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate()); - var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker'); - $this.after(endDateInput); + if ($this.data('start-date')) { + var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); + var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); + $this.after(startDateInput); + } + if ($this.data('end-date')) { + var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate()); + var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker'); + $this.after(endDateInput); + } } }); diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index aca0196c15..c06e58631c 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -81,7 +81,7 @@ var abp = abp || {}; if (resource) { return resource; } - + var legacySource = abp.localization.values[resourceName]; if (legacySource) { return { @@ -89,11 +89,11 @@ var abp = abp || {}; baseResources: [] }; } - - abp.log.warn('Could not find localization source: ' + resourceName); + + abp.log.warn('Could not find localization source: ' + resourceName); return null; }; - + abp.localization.internal.localize = function (key, sourceName) { var resource = abp.localization.internal.getResource(sourceName); if (!resource){ @@ -104,7 +104,7 @@ var abp = abp || {}; } var value = resource.texts[key]; - if (value === undefined) { + if (value === undefined) { for (var i = 0; i < resource.baseResources.length; i++){ var basedArguments = Array.prototype.slice.call(arguments, 0); basedArguments[1] = resource.baseResources[i]; @@ -114,7 +114,7 @@ var abp = abp || {}; return result; } } - + return { value: key, found: false @@ -135,7 +135,7 @@ var abp = abp || {}; if (sourceName === '_') { //A convention to suppress the localization return key; } - + if (sourceName) { return abp.localization.internal.localize.apply(this, arguments).value; } @@ -767,28 +767,28 @@ var abp = abp || {}; return date; } - if (abp.clock.supportsMultipleTimezone()) { - return dateObj.toISOString(); - } - function padZero(num) { return num < 10 ? '0' + num : num; } - function padMilliseconds(num) { - if (num < 10) return '00' + num; - if (num < 100) return '0' + num; - return num; + var addZulu = false; + if (abp.clock.supportsMultipleTimezone()) { + var timeZone = abp.clock.timeZone(); + var now = new Date(); + var formattedDate = now.toLocaleString('en-US', { timeZone: timeZone, timeZoneName: 'short' }); + var match = formattedDate.match(/GMT([+-]\d+)/); + var targetOffsetHours = match ? parseInt(match[1], 10) : 0; + var dateObj = new Date(dateObj.getTime() - (targetOffsetHours * 60 * 60 * 1000)); + addZulu = true; } - - // yyyy-MM-ddTHH:mm:ss.SSS + + // yyyy-MM-DDTHH:mm:ss return dateObj.getFullYear() + '-' + - padZero(dateObj.getMonth() + 1) + '-' + - padZero(dateObj.getDate()) + 'T' + - padZero(dateObj.getHours()) + ':' + - padZero(dateObj.getMinutes()) + ':' + - padZero(dateObj.getSeconds()) + '.' + - padMilliseconds(dateObj.getMilliseconds()); + padZero(dateObj.getMonth() + 1) + '-' + + padZero(dateObj.getDate()) + 'T' + + padZero(dateObj.getHours()) + ':' + + padZero(dateObj.getMinutes()) + ':' + + padZero(dateObj.getSeconds()) + (addZulu ? 'Z' : ''); }; // Default options for toLocaleString