فهرست منبع

DatePicker: support literal (#15525)

Zhi Cun 6 سال پیش
والد
کامیت
a7d3f69f95

+ 1 - 0
examples/docs/en-US/date-picker.md

@@ -346,6 +346,7 @@ Pay attention to capitalization
 | `A`  | AM/PM | only for `format`, uppercased | AM |
 | `a`  | am/pm | only for `format`, lowercased | am |
 | `timestamp` | JS timestamp | only for `value-format`; binding value will be a `number` | 1483326245000 |
+| `[MM]` | No escape characters | To escape characters, wrap them in square brackets (e.g. [A] [MM]) | MM |
 
 :::demo
 ```html

+ 1 - 0
examples/docs/es/date-picker.md

@@ -346,6 +346,7 @@ Preste atención a la capitalización
 | `A`         | AM/PM        | solamente para `format`, mayusculas      | AM            |
 | `a`         | am/pm        | solamente para `format`, minúsculas      | am            |
 | `timestamp` | JS timestamp | solamente para `value-format`; valor vinculado debe ser un `number` | 1483326245000 |
+| `[MM]` | No escape characters | To escape characters, wrap them in square brackets (e.g. [A] [MM]) | MM |
 
 :::demo
 ```html

+ 1 - 0
examples/docs/fr-FR/date-picker.md

@@ -347,6 +347,7 @@ Attention à la capitalisation !
 | `A`  | AM/PM | uniquement pour `format`, majuscules | AM |
 | `a`  | am/pm | uniquement pour `format`, minuscules | am |
 | `timestamp` | timestamp JS | uniquement pour `value-format`; la variable stockée sera un `number` | 1483326245000 |
+| `[MM]` | No escape characters | To escape characters, wrap them in square brackets (e.g. [A] [MM]) | MM |
 
 :::demo
 ```html

+ 1 - 0
examples/docs/zh-CN/date-picker.md

@@ -299,6 +299,7 @@
 | `A`  | AM/PM | 仅 `format` 可用,大写 | AM |
 | `a`  | am/pm | 仅 `format` 可用,小写 | am |
 | `timestamp` | JS时间戳 | 仅 `value-format` 可用;组件绑定值为`number`类型 | 1483326245000 |
+| `[MM]` | 不需要格式化字符 | 使用方括号标识不需要格式化的字符 (如  [A] [MM])  | MM |
 
 :::demo
 ```html

+ 54 - 30
src/utils/date.js

@@ -34,13 +34,18 @@
    */
   var fecha = {};
   var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
-  var twoDigits = /\d\d?/;
-  var threeDigits = /\d{3}/;
-  var fourDigits = /\d{4}/;
-  var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+  var twoDigits = '\\d\\d?';
+  var threeDigits = '\\d{3}';
+  var fourDigits = '\\d{4}';
+  var word = '[^\\s]+';
+  var literal = /\[([^]*?)\]/gm;
   var noop = function () {
   };
 
+  function regexEscape(str) {
+    return str.replace( /[|\\{()[^$+*?.-]/g, '\\$&');
+  }
+
   function shorten(arr, sLen) {
     var newArr = [];
     for (var i = 0, len = arr.length; i < len; i++) {
@@ -117,10 +122,10 @@
       return i18n.monthNames[dateObj.getMonth()];
     },
     yy: function(dateObj) {
-      return String(dateObj.getFullYear()).substr(2);
+      return pad(String(dateObj.getFullYear()), 4).substr(2);
     },
     yyyy: function(dateObj) {
-      return dateObj.getFullYear();
+      return pad(dateObj.getFullYear(), 4);
     },
     h: function(dateObj) {
       return dateObj.getHours() % 12 || 12;
@@ -171,6 +176,9 @@
     d: [twoDigits, function (d, v) {
       d.day = v;
     }],
+    Do: [twoDigits + word, function (d, v) {
+      d.day = parseInt(v, 10);
+    }],
     M: [twoDigits, function (d, v) {
       d.month = v - 1;
     }],
@@ -190,10 +198,10 @@
     yyyy: [fourDigits, function (d, v) {
       d.year = v;
     }],
-    S: [/\d/, function (d, v) {
+    S: ['\\d', function (d, v) {
       d.millisecond = v * 100;
     }],
-    SS: [/\d{2}/, function (d, v) {
+    SS: ['\\d{2}', function (d, v) {
       d.millisecond = v * 10;
     }],
     SSS: [threeDigits, function (d, v) {
@@ -211,8 +219,8 @@
         d.isPm = true;
       }
     }],
-    ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) {
-      var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
+    ZZ: ['[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z', function (d, v) {
+      var parts = (v + '').match(/([+-]|\d\d)/gi), minutes;
 
       if (parts) {
         minutes = +(parts[1] * 60) + parseInt(parts[2], 10);
@@ -220,9 +228,9 @@
       }
     }]
   };
-  parseFlags.DD = parseFlags.D;
+  parseFlags.dd = parseFlags.d;
   parseFlags.dddd = parseFlags.ddd;
-  parseFlags.Do = parseFlags.dd = parseFlags.d;
+  parseFlags.DD = parseFlags.D;
   parseFlags.mm = parseFlags.m;
   parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
   parseFlags.MM = parseFlags.M;
@@ -232,7 +240,7 @@
 
   // Some common format strings
   fecha.masks = {
-    'default': 'ddd MMM dd yyyy HH:mm:ss',
+    default: 'ddd MMM dd yyyy HH:mm:ss',
     shortDate: 'M/D/yy',
     mediumDate: 'MMM d, yyyy',
     longDate: 'MMMM d, yyyy',
@@ -261,9 +269,21 @@
 
     mask = fecha.masks[mask] || mask || fecha.masks['default'];
 
-    return mask.replace(token, function ($0) {
+    var literals = [];
+
+    // Make literals inactive by replacing them with ??
+    mask = mask.replace(literal, function($0, $1) {
+      literals.push($1);
+      return '@@@';
+    });
+    // Apply formatting rules
+    mask = mask.replace(token, function ($0) {
       return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
     });
+    // Inline literal values back into the formatted value
+    return mask.replace(/@@@/g, function() {
+      return literals.shift();
+    });
   };
 
   /**
@@ -285,31 +305,35 @@
     // Avoid regular expression denial of service, fail early for really long strings
     // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
     if (dateStr.length > 1000) {
-      return false;
+      return null;
     }
 
-    var isValid = true;
     var dateInfo = {};
-    format.replace(token, function ($0) {
+    var parseInfo = [];
+    var literals = [];
+    format = format.replace(literal, function($0, $1) {
+      literals.push($1);
+      return '@@@';
+    });
+    var newFormat = regexEscape(format).replace(token, function ($0) {
       if (parseFlags[$0]) {
         var info = parseFlags[$0];
-        var index = dateStr.search(info[0]);
-        if (!~index) {
-          isValid = false;
-        } else {
-          dateStr.replace(info[0], function (result) {
-            info[1](dateInfo, result, i18n);
-            dateStr = dateStr.substr(index + result.length);
-            return result;
-          });
-        }
+        parseInfo.push(info[1]);
+        return '(' + info[0] + ')';
       }
 
-      return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
+      return $0;
     });
+    newFormat = newFormat.replace(/@@@/g, function() {
+      return literals.shift();
+    });
+    var matches = dateStr.match(new RegExp(newFormat, 'i'));
+    if (!matches) {
+      return null;
+    }
 
-    if (!isValid) {
-      return false;
+    for (var i = 1; i < matches.length; i++) {
+      parseInfo[i - 1](dateInfo, matches[i], i18n);
     }
 
     var today = new Date();

+ 59 - 0
test/unit/specs/date-picker.spec.js

@@ -489,6 +489,37 @@ describe('DatePicker', () => {
       }, DELAY);
     });
 
+    it('with literal string', done => {
+      vm = createVue({
+        template: `
+          <el-date-picker
+            ref="compo"
+            v-model="value"
+            type="date"
+            value-format="dd/MM yyyy [Element]" />`,
+        data() {
+          return {
+            value: ''
+          };
+        }
+      }, true);
+
+      vm.$refs.compo.$el.querySelector('input').focus();
+
+      setTimeout(_ => {
+        vm.$refs.compo.picker.$el.querySelector('.el-date-table td.available').click();
+        setTimeout(_ => {
+          const today = new Date();
+          const yyyy = today.getFullYear();
+          const MM = ('0' + (today.getMonth() + 1)).slice(-2);
+          const dd = '01'; // first available one should be first day of month
+          const expectValue = `${dd}/${MM} ${yyyy} Element`;
+          expect(vm.value).to.equal(expectValue);
+          done();
+        }, DELAY);
+      }, DELAY);
+    });
+
     it('accepts', done => {
       vm = createVue({
         template: `
@@ -549,6 +580,34 @@ describe('DatePicker', () => {
       }, DELAY);
     });
 
+    it('translates format to value-format with literal string', done => {
+      vm = createVue({
+        template: `
+          <el-date-picker
+            ref="compo"
+            v-model="value"
+            type="date"
+            format="[Element] yyyy-MM-dd"
+            value-format="dd/MM yyyy [UI]" />`,
+        data() {
+          return {
+            value: ''
+          };
+        }
+      }, true);
+      const input = vm.$refs.compo.$el.querySelector('input');
+      input.focus();
+      setTimeout(_ => {
+        input.value = 'Element 2000-10-01';
+        triggerEvent(input, 'input');
+        keyDown(input, ENTER);
+        setTimeout(_ => {
+          expect(vm.value).to.equal('01/10 2000 UI');
+          done();
+        }, DELAY);
+      }, DELAY);
+    });
+
     it('works for daterange', done => {
       vm = createVue({
         template: `