Leon 1 year ago
parent
commit
7298c02bdd

+ 1 - 0
package.json

@@ -95,6 +95,7 @@
 		"intl": "^1.2.5",
 		"intl": "^1.2.5",
 		"lodash": "^4.17.21",
 		"lodash": "^4.17.21",
 		"mathjs": "^12.3.0",
 		"mathjs": "^12.3.0",
+		"miniprogram-recycle-view": "^0.1.5",
 		"moment": "^2.30.1",
 		"moment": "^2.30.1",
 		"moment-timezone": "^0.5.45",
 		"moment-timezone": "^0.5.45",
 		"react": "^18.1.0",
 		"react": "^18.1.0",

+ 45 - 0
src/components/miniprogram-recycle-view/index.d.ts

@@ -0,0 +1,45 @@
+declare namespace recycleContext {
+    interface itemSize {
+        width: number;
+        height: number;
+    }
+    
+    type Component = any;
+    type Page = any;
+    
+    type itemSizeFunc<T> = (item: T, index: number) => itemSize
+    
+    interface options<T> {
+        id: string;
+        dataKey: string;
+        page: Component | Page;
+        itemSize: itemSizeFunc<T> | itemSize;
+        useInPage?: boolean;
+        root?: Page;
+    }
+    
+    interface position {
+        left: number;
+        top: number;
+        width: number;
+        height: number;
+    }
+    
+    interface RecycleContext<T> {
+        append(list: T[], callback?: () => void): RecycleContext<T>
+        appendList(list: T[], callback?: () => void): RecycleContext<T>
+        splice(begin: number, deleteCount: number, appendList: T[], callback?: () => void): RecycleContext<T>;
+        updateList(beginIndex: number, list: T[], callback?: () => void): RecycleContext<T>
+        update(beginIndex: number, list: T[], callback?: () => void): RecycleContext<T>
+        destroy(): RecycleContext<T>
+        forceUpdate(callback: () => void, reinitSlot: boolean): RecycleContext<T>
+        getBoundingClientRect(index: number | undefined): position | position[]
+        getScrollTop(): number;
+        transformRpx(rpx: number, addPxSuffix?: string): number;
+        getViewportItems(inViewportPx: number): T[]
+        getList(): T[]
+    }
+}
+declare function createRecycleContext<T>(op: recycleContext.options<T>): recycleContext.RecycleContext<T>
+
+export = createRecycleContext;

+ 851 - 0
src/components/miniprogram-recycle-view/index.js

@@ -0,0 +1,851 @@
+module.exports =
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
+/******/
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 2);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var isIPhone = false;
+var deviceWidth = void 0;
+var deviceDPR = void 0;
+var BASE_DEVICE_WIDTH = 750;
+var checkDeviceWidth = function checkDeviceWidth() {
+  var info = wx.getSystemInfoSync();
+  // console.log('info', info)
+  isIPhone = info.platform === 'ios';
+  var newDeviceWidth = info.screenWidth || 375;
+  var newDeviceDPR = info.pixelRatio || 2;
+
+  if (!isIPhone) {
+    // HACK switch width and height when landscape
+    // const newDeviceHeight = info.screenHeight || 375
+    // 暂时不处理转屏的情况
+  }
+
+  if (newDeviceWidth !== deviceWidth || newDeviceDPR !== deviceDPR) {
+    deviceWidth = newDeviceWidth;
+    deviceDPR = newDeviceDPR;
+    // console.info('Updated device width: ' + newDeviceWidth + 'px DPR ' + newDeviceDPR)
+  }
+};
+checkDeviceWidth();
+
+var eps = 1e-4;
+var transformByDPR = function transformByDPR(number) {
+  if (number === 0) {
+    return 0;
+  }
+  number = number / BASE_DEVICE_WIDTH * deviceWidth;
+  number = Math.floor(number + eps);
+  if (number === 0) {
+    if (deviceDPR === 1 || !isIPhone) {
+      return 1;
+    }
+    return 0.5;
+  }
+  return number;
+};
+
+var rpxRE = /([+-]?\d+(?:\.\d+)?)rpx/gi;
+// const inlineRpxRE = /(?::|\s|\(|\/)([+-]?\d+(?:\.\d+)?)rpx/g
+
+var transformRpx = function transformRpx(style, inline) {
+  if (typeof style !== 'string') {
+    return style;
+  }
+  var re = rpxRE;
+  return style.replace(re, function (match, num) {
+    return transformByDPR(Number(num)) + (inline ? 'px' : '');
+  });
+};
+
+module.exports = {
+  transformRpx: transformRpx
+};
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+module.exports = {};
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/**
+ * recycle-view组件的api使用
+ * 提供wx.createRecycleContext进行管理功能
+ */
+var RecycleContext = __webpack_require__(3);
+
+/**
+ * @params options参数是object对象,展开的结构如下
+      id: recycle-view的id
+      dataKey: recycle-item的wx:for绑定的数据变量
+      page: recycle-view所在的页面或组件的实例
+      itemSize: 函数或者是Object对象,生成每个recycle-item的宽和高
+ * @return RecycleContext对象
+ */
+module.exports = function (options) {
+  return new RecycleContext(options);
+};
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+/* eslint complexity: ["error", {"max": 50}] */
+var recycleData = __webpack_require__(1);
+var recycleViewportChangeFunc = __webpack_require__(4);
+var transformRpx = __webpack_require__(0);
+
+var RECT_SIZE = 200;
+
+// eslint-disable-next-line no-complexity
+function RecycleContext(_ref) {
+  var _this = this;
+
+  var id = _ref.id,
+      dataKey = _ref.dataKey,
+      page = _ref.page,
+      itemSize = _ref.itemSize,
+      useInPage = _ref.useInPage,
+      placeholderClass = _ref.placeholderClass,
+      root = _ref.root;
+
+  if (!id || !dataKey || !page || !itemSize) {
+    throw new Error('parameter id, dataKey, page, itemSize is required');
+  }
+  if (typeof itemSize !== 'function' && (typeof itemSize === 'undefined' ? 'undefined' : _typeof(itemSize)) !== 'object') {
+    throw new Error('parameter itemSize must be function or object with key width and height');
+  }
+  if ((typeof itemSize === 'undefined' ? 'undefined' : _typeof(itemSize)) === 'object' && (!itemSize.width || !itemSize.height) && (!itemSize.props || !itemSize.queryClass || !itemSize.dataKey)) {
+    throw new Error('parameter itemSize must be function or object with key width and height');
+  }
+  this.id = id;
+  this.dataKey = dataKey;
+  this.page = page;
+  // 加root参数给useInPage单独使用
+  this.root = root;
+  this.placeholderClass = placeholderClass;
+  page._recycleViewportChange = recycleViewportChangeFunc;
+  this.comp = page.selectComponent('#' + id);
+  this.itemSize = itemSize;
+  this.itemSizeOpt = itemSize;
+  // if (!this.comp) {
+  // throw `<recycle-view> with id ${id} not found`
+  // }
+  this.useInPage = useInPage || false;
+  if (this.comp) {
+    this.comp.context = this;
+    this.comp.setPage(page);
+    this.comp.setUseInPage(this.useInPage);
+  }
+  if (this.useInPage && !this.root) {
+    throw new Error('parameter root is required when useInPage is true');
+  }
+  if (this.useInPage) {
+    this.oldPageScroll = this.root.onPageScroll;
+    // 重写onPageScroll事件
+    this.root.onPageScroll = function (e) {
+      // this.checkComp();
+      if (_this.comp) {
+        _this.comp._scrollViewDidScroll({
+          detail: {
+            scrollLeft: 0,
+            scrollTop: e.scrollTop
+          }
+        });
+      }
+      _this.oldPageScroll.apply(_this.root, [e]);
+    };
+    this.oldReachBottom = this.root.onReachBottom;
+    this.root.onReachBottom = function (e) {
+      if (_this.comp) {
+        _this.comp.triggerEvent('scrolltolower', {});
+      }
+      _this.oldReachBottom.apply(_this.root, [e]);
+    };
+    this.oldPullDownRefresh = this.root.onPullDownRefresh;
+    this.root.onPullDownRefresh = function (e) {
+      if (_this.comp) {
+        _this.comp.triggerEvent('scrolltoupper', {});
+      }
+      _this.oldPullDownRefresh.apply(_this.root, [e]);
+    };
+  }
+}
+RecycleContext.prototype.checkComp = function () {
+  if (!this.comp) {
+    this.comp = this.page.selectComponent('#' + this.id);
+    if (this.comp) {
+      this.comp.setUseInPage(this.useInPage);
+      this.comp.context = this;
+      this.comp.setPage(this.page);
+    } else {
+      throw new Error('the recycle-view correspond to this context is detached, pls create another RecycleContext');
+    }
+  }
+};
+RecycleContext.prototype.appendList = function (list, cb) {
+  this.checkComp();
+  var id = this.id;
+  var dataKey = this.dataKey;
+  if (!recycleData[id]) {
+    recycleData[id] = {
+      key: dataKey,
+      id: id,
+      list: list,
+      sizeMap: {},
+      sizeArray: []
+    };
+  } else {
+    recycleData[id].dataKey = dataKey;
+    recycleData[id].list = recycleData[id].list.concat(list);
+  }
+  this._forceRerender(id, cb);
+  return this;
+};
+RecycleContext.prototype._forceRerender = function (id, cb) {
+  this.isDataReady = true; // 首次调用说明数据已经ready了
+  // 动态计算高度并缓存
+  var that = this;
+  var allrect = null;
+  var parentRect = null;
+  var count = 0;
+
+  function setPlaceholderImage() {
+    if (!allrect || !parentRect) return;
+    var svgRects = [];
+    for (var i = 0; i < count; i++) {
+      svgRects.push({
+        left: allrect[i].left - parentRect.left,
+        top: allrect[i].top - parentRect.top,
+        width: allrect[i].width,
+        height: allrect[i].height
+      });
+    }
+    that.comp.setPlaceholderImage(svgRects, {
+      width: parentRect.width,
+      height: parentRect.height
+    });
+  }
+  function newcb() {
+    if (cb) {
+      cb();
+    }
+    // 计算placeholder, 只有在动态计算高度的时候才支持
+    if (that.autoCalculateSize && that.placeholderClass) {
+      var newQueryClass = [];
+      that.placeholderClass.forEach(function (item) {
+        newQueryClass.push('.' + that.itemSizeOpt.queryClass + ' .' + item);
+      });
+      // newQueryClass.push(`.` + that.itemSizeOpt.queryClass)
+      count = newQueryClass.length;
+      wx.createSelectorQuery().selectAll(newQueryClass.join(',')).boundingClientRect(function (rect) {
+        if (rect.length < count) return;
+        allrect = rect;
+        setPlaceholderImage();
+      }).exec();
+      wx.createSelectorQuery().select('.' + that.itemSizeOpt.queryClass).boundingClientRect(function (rect) {
+        parentRect = rect;
+        setPlaceholderImage();
+      }).exec();
+    }
+  }
+  if (Object.prototype.toString.call(this.itemSizeOpt) === '[object Object]' && this.itemSizeOpt && !this.itemSizeOpt.width) {
+    this._recalculateSizeByProp(recycleData[id].list, function (sizeData) {
+      recycleData[id].sizeMap = sizeData.map;
+      recycleData[id].sizeArray = sizeData.array;
+      // 触发强制渲染
+      that.comp.forceUpdate(newcb);
+    });
+    return;
+  }
+  var sizeData = this._recalculateSize(recycleData[id].list);
+  recycleData[id].sizeMap = sizeData.map;
+  // console.log('size is', sizeData.array, sizeData.map, 'totalHeight', sizeData.totalHeight)
+  // console.log('sizeArray', sizeData.array)
+  recycleData[id].sizeArray = sizeData.array;
+  // 触发强制渲染
+  this.comp.forceUpdate(cb);
+};
+function getValue(item, key) {
+  if (!key) return item;
+  if (typeof item[key] !== 'undefined') return item[key];
+  var keyItems = key.split('.');
+  for (var i = 0; i < keyItems.length; i++) {
+    item = item[keyItems[i]];
+    if (typeof item === 'undefined' || (typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object' && !item) {
+      return undefined;
+    }
+  }
+  return item;
+}
+function getValues(item, keys) {
+  if (Object.prototype.toString.call(keys) !== '[object Array]') {
+    keys = [keys];
+  }
+  var vals = {};
+  for (var i = 0; i < keys.length; i++) {
+    vals[keys[i]] = getValue(item, keys[i]);
+  }
+  return vals;
+}
+function isArray(arr) {
+  return Object.prototype.toString.call(arr) === '[object Array]';
+}
+function isSamePureValue(item1, item2) {
+  if ((typeof item1 === 'undefined' ? 'undefined' : _typeof(item1)) !== (typeof item2 === 'undefined' ? 'undefined' : _typeof(item2))) return false;
+  if (isArray(item1) && isArray(item2)) {
+    if (item1.length !== item2.length) return false;
+    for (var i = 0; i < item1.length; i++) {
+      if (item1[i] !== item2[i]) return false;
+    }
+    return true;
+  }
+  return item1 === item2;
+}
+function isSameValue(item1, item2, keys) {
+  if (!isArray(keys)) {
+    keys = [keys];
+  }
+  for (var i = 0; i < keys.length; i++) {
+    if (!isSamePureValue(getValue(item1, keys[i]), getValue(item2, keys[i]))) return false;
+  }
+  return true;
+}
+RecycleContext.prototype._recalculateSizeByProp = function (list, cb) {
+  var itemSize = this.itemSizeOpt;
+  var propValueMap = this.propValueMap || [];
+  var calcNewItems = [];
+  var needCalcPropIndex = [];
+  if (itemSize.cacheKey) {
+    propValueMap = wx.getStorageSync(itemSize.cacheKey) || [];
+    // eslint-disable-next-line no-console
+    // console.log('[recycle-view] get itemSize from cache', propValueMap)
+  }
+  this.autoCalculateSize = true;
+  var item2PropValueMap = [];
+  for (var i = 0; i < list.length; i++) {
+    var item2PropValueIndex = propValueMap.length;
+    if (!propValueMap.length) {
+      var val = getValues(list[i], itemSize.props);
+      val.__index__ = i;
+      propValueMap.push(val);
+      calcNewItems.push(list[i]);
+      needCalcPropIndex.push(item2PropValueIndex);
+      item2PropValueMap.push({
+        index: i,
+        sizeIndex: item2PropValueIndex
+      });
+      continue;
+    }
+    var found = false;
+    for (var j = 0; j < propValueMap.length; j++) {
+      if (isSameValue(propValueMap[j], list[i], itemSize.props)) {
+        item2PropValueIndex = j;
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      var _val = getValues(list[i], itemSize.props);
+      _val.__index__ = i;
+      propValueMap.push(_val);
+      calcNewItems.push(list[i]);
+      needCalcPropIndex.push(item2PropValueIndex);
+    }
+    item2PropValueMap.push({
+      index: i,
+      sizeIndex: item2PropValueIndex
+    });
+  }
+  // this.item2PropValueMap = item2PropValueMap
+  this.propValueMap = propValueMap;
+  if (propValueMap.length > 10) {
+    // eslint-disable-next-line no-console
+    console.warn('[recycle-view] get itemSize count exceed maximum of 10, now got', propValueMap);
+  }
+  // console.log('itemsize', propValueMap, item2PropValueMap)
+  // 预先渲染
+  var that = this;
+  function newItemSize(item, index) {
+    var sizeIndex = item2PropValueMap[index];
+    if (!sizeIndex) {
+      // eslint-disable-next-line no-console
+      console.error('[recycle-view] auto calculate size array error, no map size found', item, index, item2PropValueMap);
+      throw new Error('[recycle-view] auto calculate size array error, no map size found');
+    }
+    var size = propValueMap[sizeIndex.sizeIndex];
+    if (!size) {
+      // eslint-disable-next-line no-console
+      console.log('[recycle-view] auto calculate size array error, no size found', item, index, sizeIndex, propValueMap);
+      throw new Error('[recycle-view] auto calculate size array error, no size found');
+    }
+    return {
+      width: size.width,
+      height: size.height
+    };
+  }
+  function sizeReady(rects) {
+    rects.forEach(function (rect, index) {
+      var propValueIndex = needCalcPropIndex[index];
+      propValueMap[propValueIndex].width = rect.width;
+      propValueMap[propValueIndex].height = rect.height;
+    });
+    that.itemSize = newItemSize;
+    var sizeData = that._recalculateSize(list);
+    if (itemSize.cacheKey) {
+      wx.setStorageSync(itemSize.cacheKey, propValueMap); // 把数据缓存起来
+    }
+    if (cb) {
+      cb(sizeData);
+    }
+  }
+  if (calcNewItems.length) {
+    var obj = {};
+    obj[itemSize.dataKey] = calcNewItems;
+    this.page.setData(obj, function () {
+      // wx.createSelectorQuery().select(itemSize.componentClass).boundingClientRect(rects => {
+      //   compSize = rects;
+      //   if (compSize && allItemSize) {
+      //     sizeReady();
+      //   }
+      // }).exec();
+      wx.createSelectorQuery().selectAll('.' + itemSize.queryClass).boundingClientRect(function (rects) {
+        sizeReady(rects);
+      }).exec();
+    });
+  } else {
+    that.itemSize = newItemSize;
+    var sizeData = that._recalculateSize(list);
+    if (cb) {
+      cb(sizeData);
+    }
+  }
+};
+// 当before和after这2个slot发生变化的时候调用一下此接口
+RecycleContext.prototype._recalculateSize = function (list) {
+  // 遍历所有的数据
+  // 应该最多就千量级的, 遍历没有问题
+  var sizeMap = {};
+  var func = this.itemSize;
+  var funcExist = typeof func === 'function';
+  var comp = this.comp;
+  var compData = comp.data;
+  var offsetLeft = 0;
+  var offsetTop = 0;
+  var line = 0;
+  var column = 0;
+  var sizeArray = [];
+  var listLen = list.length;
+  // 把整个页面拆分成200*200的很多个方格, 判断每个数据落在哪个方格上
+  for (var i = 0; i < listLen; i++) {
+    list[i].__index__ = i;
+    var itemSize = {};
+    // 获取到每一项的宽和高
+    if (funcExist) {
+      // 必须保证返回的每一行的高度一样
+      itemSize = func && func.call(this, list[i], i);
+    } else {
+      itemSize = {
+        width: func.width,
+        height: func.height
+      };
+    }
+    itemSize = Object.assign({}, itemSize);
+    sizeArray.push(itemSize);
+    // 判断数据落到哪个方格上
+    // 超过了宽度, 移动到下一行, 再根据高度判断是否需要移动到下一个方格
+    if (offsetLeft + itemSize.width > compData.width) {
+      column = 0;
+      offsetLeft = itemSize.width;
+      // Fixed issue #22
+      if (sizeArray.length >= 2) {
+        offsetTop += sizeArray[sizeArray.length - 2].height || 0; // 加上最后一个数据的高度
+      } else {
+        offsetTop += itemSize.height;
+      }
+      // offsetTop += sizeArray[sizeArray.length - 2].height // 加上最后一个数据的高度
+      // 根据高度判断是否需要移动到下一个方格
+      if (offsetTop >= RECT_SIZE * (line + 1)) {
+        // fix: 当区块比较大时,会缺失块区域信息
+        var lastIdx = i - 1;
+        var lastLine = line;
+
+        line += parseInt((offsetTop - RECT_SIZE * line) / RECT_SIZE, 10);
+
+        for (var idx = lastLine; idx < line; idx++) {
+          var _key = idx + '.' + column;
+          if (!sizeMap[_key]) {
+            sizeMap[_key] = [];
+          }
+          sizeMap[_key].push(lastIdx);
+        }
+      }
+
+      // 新起一行的元素, beforeHeight是前一个元素的beforeHeight和height相加
+      if (i === 0) {
+        itemSize.beforeHeight = 0;
+      } else {
+        var prevItemSize = sizeArray[sizeArray.length - 2];
+        itemSize.beforeHeight = prevItemSize.beforeHeight + prevItemSize.height;
+      }
+    } else {
+      if (offsetLeft >= RECT_SIZE * (column + 1)) {
+        column++;
+      }
+      offsetLeft += itemSize.width;
+      if (i === 0) {
+        itemSize.beforeHeight = 0;
+      } else {
+        // 同一行的元素, beforeHeight和前面一个元素的beforeHeight一样
+        itemSize.beforeHeight = sizeArray[sizeArray.length - 2].beforeHeight;
+      }
+    }
+    var key = line + '.' + column;
+    if (!sizeMap[key]) {
+      sizeMap[key] = [];
+    }
+    sizeMap[key].push(i);
+
+    // fix: 当区块比较大时,会缺失块区域信息
+    if (listLen - 1 === i && itemSize.height > RECT_SIZE) {
+      var _lastIdx = line;
+      offsetTop += itemSize.height;
+      line += parseInt((offsetTop - RECT_SIZE * line) / RECT_SIZE, 10);
+      for (var _idx = _lastIdx; _idx <= line; _idx++) {
+        var _key2 = _idx + '.' + column;
+        if (!sizeMap[_key2]) {
+          sizeMap[_key2] = [];
+        }
+        sizeMap[_key2].push(i);
+      }
+    }
+  }
+  // console.log('sizeMap', sizeMap)
+  var obj = {
+    array: sizeArray,
+    map: sizeMap,
+    totalHeight: sizeArray.length ? sizeArray[sizeArray.length - 1].beforeHeight + sizeArray[sizeArray.length - 1].height : 0
+  };
+  comp.setItemSize(obj);
+  return obj;
+};
+RecycleContext.prototype.deleteList = function (beginIndex, count, cb) {
+  this.checkComp();
+  var id = this.id;
+  if (!recycleData[id]) {
+    return this;
+  }
+  recycleData[id].list.splice(beginIndex, count);
+  this._forceRerender(id, cb);
+  return this;
+};
+RecycleContext.prototype.updateList = function (beginIndex, list, cb) {
+  this.checkComp();
+  var id = this.id;
+  if (!recycleData[id]) {
+    return this;
+  }
+  var len = recycleData[id].list.length;
+  for (var i = 0; i < list.length && beginIndex < len; i++) {
+    recycleData[id].list[beginIndex++] = list[i];
+  }
+  this._forceRerender(id, cb);
+  return this;
+};
+RecycleContext.prototype.update = RecycleContext.prototype.updateList;
+RecycleContext.prototype.splice = function (begin, deleteCount, appendList, cb) {
+  this.checkComp();
+  var id = this.id;
+  var dataKey = this.dataKey;
+  // begin是数组
+  if ((typeof begin === 'undefined' ? 'undefined' : _typeof(begin)) === 'object' && begin.length) {
+    cb = deleteCount;
+    appendList = begin;
+  }
+  if (typeof appendList === 'function') {
+    cb = appendList;
+    appendList = [];
+  }
+  if (!recycleData[id]) {
+    recycleData[id] = {
+      key: dataKey,
+      id: id,
+      list: appendList || [],
+      sizeMap: {},
+      sizeArray: []
+    };
+  } else {
+    recycleData[id].dataKey = dataKey;
+    var list = recycleData[id].list;
+    if (appendList && appendList.length) {
+      list.splice.apply(list, [begin, deleteCount].concat(appendList));
+    } else {
+      list.splice(begin, deleteCount);
+    }
+  }
+  this._forceRerender(id, cb);
+  return this;
+};
+
+RecycleContext.prototype.append = RecycleContext.prototype.appendList;
+
+RecycleContext.prototype.destroy = function () {
+  if (this.useInPage) {
+    this.page.onPullDownRefresh = this.oldPullDownRefresh;
+    this.page.onReachBottom = this.oldReachBottom;
+    this.page.onPageScroll = this.oldPageScroll;
+    this.oldPageScroll = this.oldReachBottom = this.oldPullDownRefresh = null;
+  }
+  this.page = null;
+  this.comp = null;
+  if (recycleData[this.id]) {
+    delete recycleData[this.id];
+  }
+  return this;
+};
+// 重新更新下页面的数据
+RecycleContext.prototype.forceUpdate = function (cb, reinitSlot) {
+  var _this2 = this;
+
+  this.checkComp();
+  if (reinitSlot) {
+    this.comp.reRender(function () {
+      _this2._forceRerender(_this2.id, cb);
+    });
+  } else {
+    this._forceRerender(this.id, cb);
+  }
+  return this;
+};
+RecycleContext.prototype.getBoundingClientRect = function (index) {
+  this.checkComp();
+  if (!recycleData[this.id]) {
+    return null;
+  }
+  var sizeArray = recycleData[this.id].sizeArray;
+  if (!sizeArray || !sizeArray.length) {
+    return null;
+  }
+  if (typeof index === 'undefined') {
+    var list = [];
+    for (var i = 0; i < sizeArray.length; i++) {
+      list.push({
+        left: 0,
+        top: sizeArray[i].beforeHeight,
+        width: sizeArray[i].width,
+        height: sizeArray[i].height
+      });
+    }
+    return list;
+  }
+  index = parseInt(index, 10);
+  if (index >= sizeArray.length || index < 0) return null;
+  return {
+    left: 0,
+    top: sizeArray[index].beforeHeight,
+    width: sizeArray[index].width,
+    height: sizeArray[index].height
+  };
+};
+RecycleContext.prototype.getScrollTop = function () {
+  this.checkComp();
+  return this.comp.currentScrollTop || 0;
+};
+// 将px转化为rpx
+RecycleContext.prototype.transformRpx = RecycleContext.transformRpx = function (str, addPxSuffix) {
+  if (typeof str === 'number') str += 'rpx';
+  return parseFloat(transformRpx.transformRpx(str, addPxSuffix));
+};
+RecycleContext.prototype.getViewportItems = function (inViewportPx) {
+  this.checkComp();
+  var indexes = this.comp.getIndexesInViewport(inViewportPx);
+  if (indexes.length <= 0) return [];
+  var viewportItems = [];
+  var list = recycleData[this.id].list;
+  for (var i = 0; i < indexes.length; i++) {
+    viewportItems.push(list[indexes[i]]);
+  }
+  return viewportItems;
+};
+RecycleContext.prototype.getTotalHeight = function () {
+  this.checkComp();
+  return this.comp.getTotalHeight();
+};
+// 返回完整的列表数据
+RecycleContext.prototype.getList = function () {
+  if (!recycleData[this.id]) {
+    return [];
+  }
+  return recycleData[this.id].list;
+};
+module.exports = RecycleContext;
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* eslint complexity: ["error", {"max": 50}] */
+var recycleData = __webpack_require__(1);
+
+module.exports = function (e, cb) {
+  var detail = e.detail;
+  // console.log('data change transfer use time', Date.now() - e.detail.timeStamp)
+  var newList = [];
+  var item = recycleData[detail.id];
+  // 边界值判断, 避免造成异常, 假设先调用了createRecycleContext, 然后再延迟2s调用append插入数据的情况
+  if (!item || !item.list) return;
+  var dataList = item.list;
+  var pos = detail.data;
+  var beginIndex = pos.beginIndex;
+  var endIndex = pos.endIndex;
+  item.pos = pos;
+  // 加ignoreBeginIndex和ignoreEndIndex
+  if (typeof beginIndex === 'undefined' || beginIndex === -1 || typeof endIndex === 'undefined' || endIndex === -1) {
+    newList = [];
+  } else {
+    var i = -1;
+    for (i = beginIndex; i < dataList.length && i <= endIndex; i++) {
+      if (i >= pos.ignoreBeginIndex && i <= pos.ignoreEndIndex) continue;
+      newList.push(dataList[i]);
+    }
+  }
+  var obj = {
+    // batchSetRecycleData: !this.data.batchSetRecycleData
+  };
+  obj[item.key] = newList;
+  var comp = this.selectComponent('#' + detail.id);
+  obj[comp.data.batchKey] = !this.data.batchSetRecycleData;
+  comp._setInnerBeforeAndAfterHeight({
+    beforeHeight: pos.minTop,
+    afterHeight: pos.afterHeight
+  });
+  this.setData(obj, function () {
+    if (typeof cb === 'function') {
+      cb();
+    }
+  });
+  // Fix #1
+  // 去掉了batchSetDataKey,支持一个页面内显示2个recycle-view
+  // const groupSetData = () => {
+  //   this.setData(obj)
+  //   comp._recycleInnerBatchDataChanged(() => {
+  //     if (typeof cb === 'function') {
+  //       cb()
+  //     }
+  //   })
+  // }
+  // if (typeof this.groupSetData === 'function') {
+  //   this.groupSetData(groupSetData)
+  // } else {
+  //   groupSetData()
+  // }
+};
+
+/***/ })
+/******/ ]);

+ 126 - 0
src/components/miniprogram-recycle-view/recycle-item.js

@@ -0,0 +1,126 @@
+module.exports =
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
+/******/
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 5);
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ 5:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+// components/recycle-item/recycle-item.js
+Component({
+  relations: {
+    './recycle-view': {
+      type: 'parent', // 关联的目标节点应为子节点
+      linked: function linked() {}
+    }
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {},
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    // height: 100
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    heightChange: function heightChange() {}
+  }
+});
+
+/***/ })
+
+/******/ });

+ 4 - 0
src/components/miniprogram-recycle-view/recycle-item.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 4 - 0
src/components/miniprogram-recycle-view/recycle-item.wxml

@@ -0,0 +1,4 @@
+<!--components/recycle-item/recycle-item.wxml-->
+<view class="wx-recycle-item">
+  <slot></slot>
+</view>

+ 7 - 0
src/components/miniprogram-recycle-view/recycle-item.wxss

@@ -0,0 +1,7 @@
+/* components/recycle-item/recycle-item.wxss */
+:host {
+  display: inline-block;
+}
+.wx-recycle-item {
+  height: 100%;
+}

+ 925 - 0
src/components/miniprogram-recycle-view/recycle-view.js

@@ -0,0 +1,925 @@
+module.exports =
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
+/******/
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 6);
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ 0:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var isIPhone = false;
+var deviceWidth = void 0;
+var deviceDPR = void 0;
+var BASE_DEVICE_WIDTH = 750;
+var checkDeviceWidth = function checkDeviceWidth() {
+  var info = wx.getSystemInfoSync();
+  // console.log('info', info)
+  isIPhone = info.platform === 'ios';
+  var newDeviceWidth = info.screenWidth || 375;
+  var newDeviceDPR = info.pixelRatio || 2;
+
+  if (!isIPhone) {
+    // HACK switch width and height when landscape
+    // const newDeviceHeight = info.screenHeight || 375
+    // 暂时不处理转屏的情况
+  }
+
+  if (newDeviceWidth !== deviceWidth || newDeviceDPR !== deviceDPR) {
+    deviceWidth = newDeviceWidth;
+    deviceDPR = newDeviceDPR;
+    // console.info('Updated device width: ' + newDeviceWidth + 'px DPR ' + newDeviceDPR)
+  }
+};
+checkDeviceWidth();
+
+var eps = 1e-4;
+var transformByDPR = function transformByDPR(number) {
+  if (number === 0) {
+    return 0;
+  }
+  number = number / BASE_DEVICE_WIDTH * deviceWidth;
+  number = Math.floor(number + eps);
+  if (number === 0) {
+    if (deviceDPR === 1 || !isIPhone) {
+      return 1;
+    }
+    return 0.5;
+  }
+  return number;
+};
+
+var rpxRE = /([+-]?\d+(?:\.\d+)?)rpx/gi;
+// const inlineRpxRE = /(?::|\s|\(|\/)([+-]?\d+(?:\.\d+)?)rpx/g
+
+var transformRpx = function transformRpx(style, inline) {
+  if (typeof style !== 'string') {
+    return style;
+  }
+  var re = rpxRE;
+  return style.replace(re, function (match, num) {
+    return transformByDPR(Number(num)) + (inline ? 'px' : '');
+  });
+};
+
+module.exports = {
+  transformRpx: transformRpx
+};
+
+/***/ }),
+
+/***/ 6:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* eslint complexity: ["error", {"max": 50}] */
+/* eslint-disable indent */
+var DEFAULT_SHOW_SCREENS = 4;
+var RECT_SIZE = 200;
+var systemInfo = wx.getSystemInfoSync();
+var DEBUG = false;
+var transformRpx = __webpack_require__(0).transformRpx;
+
+Component({
+  options: {
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
+  },
+  relations: {
+    '../recycle-item/recycle-item': {
+      type: 'child', // 关联的目标节点应为子节点
+      linked: function linked(target) {
+        // 检查第一个的尺寸就好了吧
+        if (!this._hasCheckSize) {
+          this._hasCheckSize = true;
+          var size = this.boundingClientRect(this._pos.beginIndex);
+          if (!size) {
+            return;
+          }
+          setTimeout(function () {
+            try {
+              target.createSelectorQuery().select('.wx-recycle-item').boundingClientRect(function (rect) {
+                if (rect && (rect.width !== size.width || rect.height !== size.height)) {
+                  // eslint-disable-next-line no-console
+                  console.warn('[recycle-view] the size in <recycle-item> is not the same with param ' + ('itemSize, expect {width: ' + rect.width + ', height: ' + rect.height + '} but got ') + ('{width: ' + size.width + ', height: ' + size.height + '}'));
+                }
+              }).exec();
+            } catch (e) {
+              // do nothing
+            }
+          }, 10);
+        }
+      }
+    }
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    debug: {
+      type: Boolean,
+      value: false
+    },
+    scrollY: {
+      type: Boolean,
+      value: true
+    },
+    batch: {
+      type: Boolean,
+      value: false,
+      public: true,
+      observer: '_recycleInnerBatchDataChanged'
+    },
+    batchKey: {
+      type: String,
+      value: 'batchSetRecycleData',
+      public: true
+    },
+    scrollTop: {
+      type: Number,
+      value: 0,
+      public: true,
+      observer: '_scrollTopChanged',
+      observeAssignments: true
+    },
+    height: {
+      type: Number,
+      value: systemInfo.windowHeight,
+      public: true,
+      observer: '_heightChanged'
+    },
+    width: {
+      type: Number,
+      value: systemInfo.windowWidth,
+      public: true,
+      observer: '_widthChanged'
+    },
+    // 距顶部/左边多远时,触发bindscrolltoupper
+    upperThreshold: {
+      type: Number,
+      value: 50,
+      public: true
+    },
+    // 距底部/右边多远时,触发bindscrolltolower
+    lowerThreshold: {
+      type: Number,
+      value: 50,
+      public: true
+    },
+    scrollToIndex: {
+      type: Number,
+      public: true,
+      value: 0,
+      observer: '_scrollToIndexChanged',
+      observeAssignments: true
+    },
+    scrollWithAnimation: {
+      type: Boolean,
+      public: true,
+      value: false
+    },
+    enableBackToTop: {
+      type: Boolean,
+      public: true,
+      value: false
+    },
+    // 是否节流,默认是
+    throttle: {
+      type: Boolean,
+      public: true,
+      value: true
+    },
+    placeholderImage: {
+      type: String,
+      public: true,
+      value: ''
+    },
+    screen: { // 默认渲染多少屏的数据
+      type: Number,
+      public: true,
+      value: DEFAULT_SHOW_SCREENS
+    }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    innerBeforeHeight: 0,
+    innerAfterHeight: 0,
+    innerScrollTop: 0,
+    innerScrollIntoView: '',
+    placeholderImageStr: '',
+    totalHeight: 0,
+    useInPage: false
+  },
+  attached: function attached() {
+    if (this.data.placeholderImage) {
+      this.setData({
+        placeholderImageStr: transformRpx(this.data.placeholderImage, true)
+      });
+    }
+    this.setItemSize({
+      array: [],
+      map: {},
+      totalHeight: 0
+    });
+  },
+  ready: function ready() {
+    var _this = this;
+
+    this._initPosition(function () {
+      _this._isReady = true; // DOM结构ready了
+      // 有一个更新的timer在了
+      if (_this._updateTimerId) return;
+
+      _this._scrollViewDidScroll({
+        detail: {
+          scrollLeft: _this._pos.left,
+          scrollTop: _this._pos.top,
+          ignoreScroll: true
+        }
+      }, true);
+    });
+  },
+  detached: function detached() {
+    this.page = null;
+    // 销毁对应的RecycleContext
+    if (this.context) {
+      this.context.destroy();
+      this.context = null;
+    }
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    _log: function _log() {
+      var _console;
+
+      if (!DEBUG && !this.data.debug) return;
+      var h = new Date();
+      var str = h.getHours() + ':' + h.getMinutes() + ':' + h.getSeconds() + '.' + h.getMilliseconds();
+
+      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+        args[_key] = arguments[_key];
+      }
+
+      Array.prototype.splice.call(args, 0, 0, str);
+      // eslint-disable-next-line no-console
+      (_console = console).log.apply(_console, args);
+    },
+    _scrollToUpper: function _scrollToUpper(e) {
+      this.triggerEvent('scrolltoupper', e.detail);
+    },
+    _scrollToLower: function _scrollToLower(e) {
+      this.triggerEvent('scrolltolower', e.detail);
+    },
+    _beginToScroll: function _beginToScroll() {
+      if (!this._lastScrollTop) {
+        this._lastScrollTop = this._pos && (this._pos.top || 0);
+      }
+    },
+    _clearList: function _clearList(cb) {
+      this.currentScrollTop = 0;
+      this._lastScrollTop = 0;
+      var pos = this._pos;
+      pos.beginIndex = this._pos.endIndex = -1;
+      pos.afterHeight = pos.minTop = pos.maxTop = 0;
+      this.page._recycleViewportChange({
+        detail: {
+          data: pos,
+          id: this.id
+        }
+      }, cb);
+    },
+
+    // 判断RecycleContext是否Ready
+    _isValid: function _isValid() {
+      return this.page && this.context && this.context.isDataReady;
+    },
+
+    // eslint-disable-next-line no-complexity
+    _scrollViewDidScroll: function _scrollViewDidScroll(e, force) {
+      // 如果RecycleContext还没有初始化, 不做任何事情
+      if (!this._isValid()) {
+        return;
+      }
+      // 监测白屏时间
+      if (!e.detail.ignoreScroll) {
+        this.triggerEvent('scroll', e.detail);
+      }
+      this.currentScrollTop = e.detail.scrollTop;
+      // 高度为0的情况, 不做任何渲染逻辑
+      if (!this._pos.height || !this.sizeArray.length) {
+        // 没有任何数据的情况下, 直接清理所有的状态
+        this._clearList(e.detail.cb);
+        return;
+      }
+
+      // 在scrollWithAnimation动画最后会触发一次scroll事件, 这次scroll事件必须要被忽略
+      if (this._isScrollingWithAnimation) {
+        this._isScrollingWithAnimation = false;
+        return;
+      }
+      var pos = this._pos;
+      var that = this;
+      var scrollLeft = e.detail.scrollLeft;
+      var scrollTop = e.detail.scrollTop;
+      var scrollDistance = Math.abs(scrollTop - this._lastScrollTop);
+      if (!force && Math.abs(scrollTop - pos.top) < pos.height * 1.5) {
+        this._log('【not exceed height');
+        return;
+      }
+      this._lastScrollTop = scrollTop;
+      var SHOW_SCREENS = this.data.screen; // 固定4屏幕
+      this._log('SHOW_SCREENS', SHOW_SCREENS, scrollTop);
+      this._calcViewportIndexes(scrollLeft, scrollTop, function (beginIndex, endIndex, minTop, afterHeight, maxTop) {
+        that._log('scrollDistance', scrollDistance, 'indexes', beginIndex, endIndex);
+        // 渲染的数据不变
+        if (!force && pos.beginIndex === beginIndex && pos.endIndex === endIndex && pos.minTop === minTop && pos.afterHeight === afterHeight) {
+          that._log('------------is the same beginIndex and endIndex');
+          return;
+        }
+        // 如果这次渲染的范围比上一次的范围小,则忽略
+        that._log('【check】before setData, old pos is', pos.minTop, pos.maxTop, minTop, maxTop);
+        that._throttle = false;
+        pos.left = scrollLeft;
+        pos.top = scrollTop;
+        pos.beginIndex = beginIndex;
+        pos.endIndex = endIndex;
+        // console.log('render indexes', endIndex - beginIndex + 1, endIndex, beginIndex)
+        pos.minTop = minTop;
+        pos.maxTop = maxTop;
+        pos.afterHeight = afterHeight;
+        pos.ignoreBeginIndex = pos.ignoreEndIndex = -1;
+        that.page._recycleViewportChange({
+          detail: {
+            data: that._pos,
+            id: that.id
+          }
+        }, function () {
+          if (e.detail.cb) {
+            e.detail.cb();
+          }
+        });
+      });
+    },
+
+    // 计算在视窗内渲染的数据
+    _calcViewportIndexes: function _calcViewportIndexes(left, top, cb) {
+      var that = this;
+      // const st = +new Date
+      this._getBeforeSlotHeight(function () {
+        var _that$__calcViewportI = that.__calcViewportIndexes(left, top),
+            beginIndex = _that$__calcViewportI.beginIndex,
+            endIndex = _that$__calcViewportI.endIndex,
+            minTop = _that$__calcViewportI.minTop,
+            afterHeight = _that$__calcViewportI.afterHeight,
+            maxTop = _that$__calcViewportI.maxTop;
+
+        if (cb) {
+          cb(beginIndex, endIndex, minTop, afterHeight, maxTop);
+        }
+      });
+    },
+    _getBeforeSlotHeight: function _getBeforeSlotHeight(cb) {
+      if (typeof this.data.beforeSlotHeight !== 'undefined') {
+        if (cb) {
+          cb(this.data.beforeSlotHeight);
+        }
+      } else {
+        this.reRender(cb);
+      }
+    },
+    _getAfterSlotHeight: function _getAfterSlotHeight(cb) {
+      if (typeof this.data.afterSlotHeight !== 'undefined') {
+        if (cb) {
+          cb(this.data.afterSlotHeight);
+        }
+        // cb && cb(this.data.afterSlotHeight)
+      } else {
+        this.reRender(cb);
+      }
+    },
+    _getIndexes: function _getIndexes(minTop, maxTop) {
+      if (minTop === maxTop && maxTop === 0) {
+        return {
+          beginIndex: -1,
+          endIndex: -1
+        };
+      }
+      var startLine = Math.floor(minTop / RECT_SIZE);
+      var endLine = Math.ceil(maxTop / RECT_SIZE);
+      var rectEachLine = Math.floor(this.data.width / RECT_SIZE);
+      var beginIndex = void 0;
+      var endIndex = void 0;
+      var sizeMap = this.sizeMap;
+      for (var i = startLine; i <= endLine; i++) {
+        for (var col = 0; col < rectEachLine; col++) {
+          var key = i + '.' + col;
+          // 找到sizeMap里面的最小值和最大值即可
+          if (!sizeMap[key]) continue;
+          for (var j = 0; j < sizeMap[key].length; j++) {
+            if (typeof beginIndex === 'undefined') {
+              beginIndex = endIndex = sizeMap[key][j];
+              continue;
+            }
+            if (beginIndex > sizeMap[key][j]) {
+              beginIndex = sizeMap[key][j];
+            } else if (endIndex < sizeMap[key][j]) {
+              endIndex = sizeMap[key][j];
+            }
+          }
+        }
+      }
+      return {
+        beginIndex: beginIndex,
+        endIndex: endIndex
+      };
+    },
+    _isIndexValid: function _isIndexValid(beginIndex, endIndex) {
+      if (typeof beginIndex === 'undefined' || beginIndex === -1 || typeof endIndex === 'undefined' || endIndex === -1 || endIndex >= this.sizeArray.length) {
+        return false;
+      }
+      return true;
+    },
+    __calcViewportIndexes: function __calcViewportIndexes(left, top) {
+      if (!this.sizeArray.length) return {};
+      var pos = this._pos;
+      if (typeof left === 'undefined') {
+        left = pos.left;
+      }
+      if (typeof top === 'undefined') {
+        top = pos.top;
+      }
+      // top = Math.max(top, this.data.beforeSlotHeight)
+      var beforeSlotHeight = this.data.beforeSlotHeight || 0;
+      // 和direction无关了
+      var SHOW_SCREENS = this.data.screen;
+      var minTop = top - pos.height * SHOW_SCREENS - beforeSlotHeight;
+      var maxTop = top + pos.height * SHOW_SCREENS - beforeSlotHeight;
+      // maxTop或者是minTop超出了范围
+      if (maxTop > this.totalHeight) {
+        minTop -= maxTop - this.totalHeight;
+        maxTop = this.totalHeight;
+      }
+      if (minTop < beforeSlotHeight) {
+        maxTop += Math.min(beforeSlotHeight - minTop, this.totalHeight);
+        minTop = 0;
+      }
+      // 计算落在minTop和maxTop之间的方格有哪些
+      var indexObj = this._getIndexes(minTop, maxTop);
+      var beginIndex = indexObj.beginIndex;
+      var endIndex = indexObj.endIndex;
+      if (endIndex >= this.sizeArray.length) {
+        endIndex = this.sizeArray.length - 1;
+      }
+      // 校验一下beginIndex和endIndex的有效性,
+      if (!this._isIndexValid(beginIndex, endIndex)) {
+        return {
+          beginIndex: -1,
+          endIndex: -1,
+          minTop: 0,
+          afterHeight: 0,
+          maxTop: 0
+        };
+      }
+      // 计算白屏的默认占位的区域
+      var maxTopFull = this.sizeArray[endIndex].beforeHeight + this.sizeArray[endIndex].height;
+      var minTopFull = this.sizeArray[beginIndex].beforeHeight;
+
+      // console.log('render indexes', beginIndex, endIndex)
+      var afterHeight = this.totalHeight - maxTopFull;
+      return {
+        beginIndex: beginIndex,
+        endIndex: endIndex,
+        minTop: minTopFull, // 取整, beforeHeight的距离
+        afterHeight: afterHeight,
+        maxTop: maxTop
+      };
+    },
+    setItemSize: function setItemSize(size) {
+      this.sizeArray = size.array;
+      this.sizeMap = size.map;
+      if (size.totalHeight !== this.totalHeight) {
+        // console.log('---totalHeight is', size.totalHeight);
+        this.setData({
+          totalHeight: size.totalHeight,
+          useInPage: this.useInPage || false
+        });
+      }
+      this.totalHeight = size.totalHeight;
+    },
+    setList: function setList(key, newList) {
+      this._currentSetDataKey = key;
+      this._currentSetDataList = newList;
+    },
+    setPage: function setPage(page) {
+      this.page = page;
+    },
+    forceUpdate: function forceUpdate(cb, reInit) {
+      var _this2 = this;
+
+      if (!this._isReady) {
+        if (this._updateTimerId) {
+          // 合并多次的forceUpdate
+          clearTimeout(this._updateTimerId);
+        }
+        this._updateTimerId = setTimeout(function () {
+          _this2.forceUpdate(cb, reInit);
+        }, 10);
+        return;
+      }
+      this._updateTimerId = null;
+      var that = this;
+      if (reInit) {
+        this.reRender(function () {
+          that._scrollViewDidScroll({
+            detail: {
+              scrollLeft: that._pos.left,
+              scrollTop: that.currentScrollTop || that.data.scrollTop || 0,
+              ignoreScroll: true,
+              cb: cb
+            }
+          }, true);
+        });
+      } else {
+        this._scrollViewDidScroll({
+          detail: {
+            scrollLeft: that._pos.left,
+            scrollTop: that.currentScrollTop || that.data.scrollTop || 0,
+            ignoreScroll: true,
+            cb: cb
+          }
+        }, true);
+      }
+    },
+    _initPosition: function _initPosition(cb) {
+      var that = this;
+      that._pos = {
+        left: that.data.scrollLeft || 0,
+        top: that.data.scrollTop || 0,
+        width: this.data.width,
+        height: Math.max(500, this.data.height), // 一个屏幕的高度
+        direction: 0
+      };
+      this.reRender(cb);
+    },
+    _widthChanged: function _widthChanged(newVal) {
+      if (!this._isReady) return newVal;
+      this._pos.width = newVal;
+      this.forceUpdate();
+      return newVal;
+    },
+    _heightChanged: function _heightChanged(newVal) {
+      if (!this._isReady) return newVal;
+      this._pos.height = Math.max(500, newVal);
+      this.forceUpdate();
+      return newVal;
+    },
+    reRender: function reRender(cb) {
+      var _this3 = this;
+
+      var beforeSlotHeight = void 0;
+      var afterSlotHeight = void 0;
+      var that = this;
+      // const reRenderStart = Date.now()
+      function newCb() {
+        if (that._lastBeforeSlotHeight !== beforeSlotHeight || that._lastAfterSlotHeight !== afterSlotHeight) {
+          that.setData({
+            hasBeforeSlotHeight: true,
+            hasAfterSlotHeight: true,
+            beforeSlotHeight: beforeSlotHeight,
+            afterSlotHeight: afterSlotHeight
+          });
+        }
+        that._lastBeforeSlotHeight = beforeSlotHeight;
+        that._lastAfterSlotHeight = afterSlotHeight;
+        // console.log('_getBeforeSlotHeight use time', Date.now() - reRenderStart)
+        if (cb) {
+          cb();
+        }
+      }
+      // 重新渲染事件发生
+      var beforeReady = false;
+      var afterReady = false;
+      // fix:#16 确保获取slot节点实际高度
+      this.setData({
+        hasBeforeSlotHeight: false,
+        hasAfterSlotHeight: false
+      }, function () {
+        _this3.createSelectorQuery().select('.slot-before').boundingClientRect(function (rect) {
+          beforeSlotHeight = rect.height;
+          beforeReady = true;
+          if (afterReady) {
+            if (newCb) {
+              newCb();
+            }
+          }
+        }).exec();
+        _this3.createSelectorQuery().select('.slot-after').boundingClientRect(function (rect) {
+          afterSlotHeight = rect.height;
+          afterReady = true;
+          if (beforeReady) {
+            if (newCb) {
+              newCb();
+            }
+          }
+        }).exec();
+      });
+    },
+    _setInnerBeforeAndAfterHeight: function _setInnerBeforeAndAfterHeight(obj) {
+      if (typeof obj.beforeHeight !== 'undefined') {
+        this._tmpBeforeHeight = obj.beforeHeight;
+      }
+      if (obj.afterHeight) {
+        this._tmpAfterHeight = obj.afterHeight;
+      }
+    },
+    _recycleInnerBatchDataChanged: function _recycleInnerBatchDataChanged(cb) {
+      var _this4 = this;
+
+      if (typeof this._tmpBeforeHeight !== 'undefined') {
+        var setObj = {
+          innerBeforeHeight: this._tmpBeforeHeight || 0,
+          innerAfterHeight: this._tmpAfterHeight || 0
+        };
+        if (typeof this._tmpInnerScrollTop !== 'undefined') {
+          setObj.innerScrollTop = this._tmpInnerScrollTop;
+        }
+        var pageObj = {};
+        var hasPageData = false;
+        if (typeof this._currentSetDataKey !== 'undefined') {
+          pageObj[this._currentSetDataKey] = this._currentSetDataList;
+          hasPageData = true;
+        }
+        var saveScrollWithAnimation = this.data.scrollWithAnimation;
+        var groupSetData = function groupSetData() {
+          // 如果有分页数据的话
+          if (hasPageData) {
+            _this4.page.setData(pageObj);
+          }
+          _this4.setData(setObj, function () {
+            _this4.setData({
+              scrollWithAnimation: saveScrollWithAnimation
+            });
+            if (typeof cb === 'function') {
+              cb();
+            }
+          });
+        };
+        groupSetData();
+        delete this._currentSetDataKey;
+        delete this._currentSetDataList;
+        this._tmpBeforeHeight = undefined;
+        this._tmpAfterHeight = undefined;
+        this._tmpInnerScrollTop = undefined;
+      }
+    },
+    _renderByScrollTop: function _renderByScrollTop(scrollTop) {
+      // 先setData把目标位置的数据补齐
+      this._scrollViewDidScroll({
+        detail: {
+          scrollLeft: this._pos.scrollLeft,
+          scrollTop: scrollTop,
+          ignoreScroll: true
+        }
+      }, true);
+      if (this.data.scrollWithAnimation) {
+        this._isScrollingWithAnimation = true;
+      }
+      this.setData({
+        innerScrollTop: scrollTop
+      });
+    },
+    _scrollTopChanged: function _scrollTopChanged(newVal, oldVal) {
+      var _this5 = this;
+
+      // if (newVal === oldVal && newVal === 0) return
+      if (!this._isInitScrollTop && newVal === 0) {
+        this._isInitScrollTop = true;
+        return newVal;
+      }
+      this.currentScrollTop = newVal;
+      if (!this._isReady) {
+        if (this._scrollTopTimerId) {
+          clearTimeout(this._scrollTopTimerId);
+        }
+        this._scrollTopTimerId = setTimeout(function () {
+          _this5._scrollTopChanged(newVal, oldVal);
+        }, 10);
+        return newVal;
+      }
+      this._isInitScrollTop = true;
+      this._scrollTopTimerId = null;
+      // this._lastScrollTop = oldVal
+      if (typeof this._lastScrollTop === 'undefined') {
+        this._lastScrollTop = this.data.scrollTop;
+      }
+      // 滑动距离小于一个屏幕的高度, 直接setData
+      if (Math.abs(newVal - this._lastScrollTop) < this._pos.height) {
+        this.setData({
+          innerScrollTop: newVal
+        });
+        return newVal;
+      }
+      if (!this._isScrollTopChanged) {
+        // 首次的值需要延后一点执行才能生效
+        setTimeout(function () {
+          _this5._isScrollTopChanged = true;
+          _this5._renderByScrollTop(newVal);
+        }, 10);
+      } else {
+        this._renderByScrollTop(newVal);
+      }
+      return newVal;
+    },
+    _scrollToIndexChanged: function _scrollToIndexChanged(newVal, oldVal) {
+      var _this6 = this;
+
+      // if (newVal === oldVal && newVal === 0) return
+      // 首次滚动到0的不执行
+      if (!this._isInitScrollToIndex && newVal === 0) {
+        this._isInitScrollToIndex = true;
+        return newVal;
+      }
+      if (!this._isReady) {
+        if (this._scrollToIndexTimerId) {
+          clearTimeout(this._scrollToIndexTimerId);
+        }
+        this._scrollToIndexTimerId = setTimeout(function () {
+          _this6._scrollToIndexChanged(newVal, oldVal);
+        }, 10);
+        return newVal;
+      }
+      this._isInitScrollToIndex = true;
+      this._scrollToIndexTimerId = null;
+      if (typeof this._lastScrollTop === 'undefined') {
+        this._lastScrollTop = this.data.scrollTop;
+      }
+      var rect = this.boundingClientRect(newVal);
+      if (!rect) return newVal;
+      // console.log('rect top', rect, this.data.beforeSlotHeight)
+      var calScrollTop = rect.top + (this.data.beforeSlotHeight || 0);
+      this.currentScrollTop = calScrollTop;
+      if (Math.abs(calScrollTop - this._lastScrollTop) < this._pos.height) {
+        this.setData({
+          innerScrollTop: calScrollTop
+        });
+        return newVal;
+      }
+      if (!this._isScrollToIndexChanged) {
+        setTimeout(function () {
+          _this6._isScrollToIndexChanged = true;
+          _this6._renderByScrollTop(calScrollTop);
+        }, 10);
+      } else {
+        this._renderByScrollTop(calScrollTop);
+      }
+      return newVal;
+    },
+
+    // 提供给开发者使用的接口
+    boundingClientRect: function boundingClientRect(idx) {
+      if (idx < 0 || idx >= this.sizeArray.length) {
+        return null;
+      }
+      return {
+        left: 0,
+        top: this.sizeArray[idx].beforeHeight,
+        width: this.sizeArray[idx].width,
+        height: this.sizeArray[idx].height
+      };
+    },
+
+    // 获取当前出现在屏幕内数据项, 返回数据项组成的数组
+    // 参数inViewportPx表示当数据项至少有多少像素出现在屏幕内才算是出现在屏幕内,默认是1
+    getIndexesInViewport: function getIndexesInViewport(inViewportPx) {
+      if (!inViewportPx) {
+        inViewportPx = 1;
+      }
+      var scrollTop = this.currentScrollTop;
+      var minTop = scrollTop + inViewportPx;
+      if (minTop < 0) minTop = 0;
+      var maxTop = scrollTop + this.data.height - inViewportPx;
+      if (maxTop > this.totalHeight) maxTop = this.totalHeight;
+      var indexes = [];
+      for (var i = 0; i < this.sizeArray.length; i++) {
+        if (this.sizeArray[i].beforeHeight + this.sizeArray[i].height >= minTop && this.sizeArray[i].beforeHeight <= maxTop) {
+          indexes.push(i);
+        }
+        if (this.sizeArray[i].beforeHeight > maxTop) break;
+      }
+      return indexes;
+    },
+    getTotalHeight: function getTotalHeight() {
+      return this.totalHeight;
+    },
+    setUseInPage: function setUseInPage(useInPage) {
+      this.useInPage = useInPage;
+    },
+    setPlaceholderImage: function setPlaceholderImage(svgs, size) {
+      var fill = 'style=\'fill:rgb(204,204,204);\'';
+      var placeholderImages = ['data:image/svg+xml,%3Csvg height=\'' + size.height + '\' width=\'' + size.width + '\' xmlns=\'http://www.w3.org/2000/svg\'%3E'];
+      svgs.forEach(function (svg) {
+        placeholderImages.push('%3Crect width=\'' + svg.width + '\' x=\'' + svg.left + '\' height=\'' + svg.height + '\' y=\'' + svg.top + '\' ' + fill + ' /%3E');
+      });
+      placeholderImages.push('%3C/svg%3E');
+      this.setData({
+        placeholderImageStr: placeholderImages.join('')
+      });
+    }
+  }
+});
+
+/***/ })
+
+/******/ });

+ 4 - 0
src/components/miniprogram-recycle-view/recycle-view.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 21 - 0
src/components/miniprogram-recycle-view/recycle-view.wxml

@@ -0,0 +1,21 @@
+<!--components/recycle-view/recycle-view.wxml-->
+<view bindtouchstart='_beginToScroll' style="height:{{useInPage ? totalHeight + (hasBeforeSlotHeight ? beforeSlotHeight : 0) + (hasAfterSlotHeight ? afterSlotHeight : 0) : height}}px;width:{{width}}px;transform:translateZ(0);-webkit-transform:translateZ(0);" id="content" class="wrap">
+  <scroll-view bindscroll="_scrollViewDidScroll" class="content" style='height:100%;position: relative;' scroll-y="{{useInPage ? false : scrollY}}" scroll-x="{{false}}" upper-threshold="{{upperThreshold}}" lower-threshold="{{lowerThreshold}}" scroll-top="{{innerScrollTop}}" scroll-into-view="{{innerScrollIntoView}}" scroll-with-animation="{{scrollWithAnimation}}" bindscrolltoupper="_scrollToUpper" bindscrolltolower="_scrollToLower" scroll-anchoring enable-back-to-top="{{enableBackToTop}}" throttle="{{throttle}}">
+    <view style="position: absolute;z-index:1;width:100%;left: 0;top: 0;opacity: 0;visibility: hidden;">
+      <slot name="itemsize"></slot>
+    </view>
+    <view style="height:{{hasBeforeSlotHeight ? beforeSlotHeight + 'px' : 'auto'}}" class="slot-before">
+      <slot name="before"></slot>
+    </view>
+    <view style='position:relative;width:100%;z-index:10;background: url("{{placeholderImageStr}}") repeat;height:{{totalHeight}}px;'>
+      <!-- <view class='before' style="height:{{innerBeforeHeight}}px"></view> -->
+      <view style="position: absolute;left:0;width:100%;top:{{innerBeforeHeight}}px;">
+        <slot></slot>
+      </view>
+      <!-- <view class='after' style="height:{{innerAfterHeight}}px"></view> -->
+    </view>
+    <view style="height:{{hasAfterSlotHeight ? afterSlotHeight + 'px' : 'auto'}}" class="slot-after">
+      <slot name="after"></slot>
+    </view>
+  </scroll-view>
+</view>

+ 5 - 0
src/components/miniprogram-recycle-view/recycle-view.wxss

@@ -0,0 +1,5 @@
+/* components/recycle-view/recycle-view.wxss */
+:host {
+  display: block;
+  width: 100%;
+}

+ 28 - 7
src/features/health/MainHistory.tsx

@@ -24,6 +24,7 @@ let lastMode = ''
 let myScrollTop = 0
 let myScrollTop = 0
 export default forwardRef((props: { type?: string, fast_type?: string, updateDate?: any, refreshSuccess?: any }, ref) => {
 export default forwardRef((props: { type?: string, fast_type?: string, updateDate?: any, refreshSuccess?: any }, ref) => {
     const [itemLayouts, setItemLayouts] = useState<any>([])
     const [itemLayouts, setItemLayouts] = useState<any>([])
+    const [itemHeights, setItemHeights] = useState<any>([])
     const [list, setList] = useState<any>([])
     const [list, setList] = useState<any>([])
     const [page, setPage] = useState(1)
     const [page, setPage] = useState(1)
     const [total, setTotal] = useState(0)
     const [total, setTotal] = useState(0)
@@ -44,6 +45,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
     const [hideActiveArchiveTip, setHideActiveArchiveTip] = useState(false)
     const [hideActiveArchiveTip, setHideActiveArchiveTip] = useState(false)
     const [hideFastTip, setHideFastTip] = useState(false)
     const [hideFastTip, setHideFastTip] = useState(false)
     const [hideSleepTip, setHideSleepTip] = useState(false)
     const [hideSleepTip, setHideSleepTip] = useState(false)
+    const [pageTop, setPageTop] = useState(0)
 
 
     const dispatch = useDispatch()
     const dispatch = useDispatch()
     const { t } = useTranslation()
     const { t } = useTranslation()
@@ -142,18 +144,25 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
     }, [health.mode])
     }, [health.mode])
 
 
     function measureItemLayouts() {
     function measureItemLayouts() {
+        console.log(new Date().getTime())
         const query = Taro.createSelectorQuery()
         const query = Taro.createSelectorQuery()
         list.forEach((item, index) => {
         list.forEach((item, index) => {
             query.select(`#history-${index}`).boundingClientRect()
             query.select(`#history-${index}`).boundingClientRect()
         });
         });
         query.exec((res) => {
         query.exec((res) => {
             var layouts: any = []
             var layouts: any = []
+            var heights: any = []
             res.forEach((rect, index) => {
             res.forEach((rect, index) => {
                 if (rect) {
                 if (rect) {
                     layouts[index] = rect.top + myScrollTop
                     layouts[index] = rect.top + myScrollTop
+                    heights[index] = rect.height
                 }
                 }
             });
             });
             setItemLayouts(layouts)
             setItemLayouts(layouts)
+            setItemHeights(heights)
+            debugger
+
+            console.log(new Date().getTime())
         })
         })
     }
     }
 
 
@@ -163,6 +172,8 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
         var top = e.detail.scrollTop - e.detail.deltaY
         var top = e.detail.scrollTop - e.detail.deltaY
         myScrollTop = e.detail.scrollTop
         myScrollTop = e.detail.scrollTop
 
 
+        setPageTop(top)
+
         if (itemLayouts.length > 0) {
         if (itemLayouts.length > 0) {
             var i = -1
             var i = -1
             var date = ''
             var date = ''
@@ -507,7 +518,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
             }
             }
         }
         }
         if (showDate) {
         if (showDate) {
-            return <View className="history_year_month h42 bold" style={{marginBottom:rpxToPx(60)}}>{dateStr}</View>
+            return <View className="history_year_month h42 bold" style={{ marginBottom: rpxToPx(60) }}>{dateStr}</View>
         }
         }
         return <View />
         return <View />
     }
     }
@@ -637,8 +648,8 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
             var time = dayjs(scenario.archive_timestamp).format('HH:mm')
             var time = dayjs(scenario.archive_timestamp).format('HH:mm')
             if (today == date) {
             if (today == date) {
                 strTime = t('health.today_at', { time: time })
                 strTime = t('health.today_at', { time: time })
-                if (global.language == 'en'){
-                    if (time == '23:59'){
+                if (global.language == 'en') {
+                    if (time == '23:59') {
                         strTime = 'at midnight'
                         strTime = 'at midnight'
                     }
                     }
                     else {
                     else {
@@ -648,7 +659,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
             }
             }
             else {
             else {
                 strTime = t('health.tomorrow_at', { time: time })
                 strTime = t('health.tomorrow_at', { time: time })
-                if (global.language == 'en'){
+                if (global.language == 'en') {
                     strTime = `tomorrow at ${time}`
                     strTime = `tomorrow at ${time}`
                 }
                 }
             }
             }
@@ -683,7 +694,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
                 }
                 }
             </View>
             </View>
             <NewButton type={NewButtonType.img} btnStyle={{
             <NewButton type={NewButtonType.img} btnStyle={{
-                paddingLeft:20,
+                paddingLeft: 20,
                 height: rpxToPx(32),
                 height: rpxToPx(32),
                 width: rpxToPx(32)
                 width: rpxToPx(32)
             }} onClick={() => {
             }} onClick={() => {
@@ -741,6 +752,16 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
             list.length > 0 && <View style={{ minHeight: rpxToPx(464), backgroundColor: '#fff', paddingTop: rpxToPx(60) }}>
             list.length > 0 && <View style={{ minHeight: rpxToPx(464), backgroundColor: '#fff', paddingTop: rpxToPx(60) }}>
                 {
                 {
                     list.map((item, index) => {
                     list.map((item, index) => {
+                        if (itemLayouts.length >= index + 1 && index>5) {
+                            if (Math.abs(itemLayouts[index] - pageTop) > 2500) {
+                                return <View style={{ height: itemHeights[index] }} id={`history-${index}`} key={index}>
+                                    {
+                                        historyMonth(index)
+                                    }
+                                </View>
+                            }
+                        }
+
                         return <View id={`history-${index}`} key={index}>
                         return <View id={`history-${index}`} key={index}>
                             {
                             {
                                 historyMonth(index)
                                 historyMonth(index)
@@ -764,7 +785,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
         }
         }
 
 
 
 
-        <View style={{ height: rpxToPx(40), flexShrink: 0, backgroundColor: '#fff' }} />
-        <ListFooter noMore={(list.length > 0) && (total == list.length)} loading={loading}/>
+        {/* <View style={{ height: rpxToPx(40), flexShrink: 0, backgroundColor: '#fff' }} /> */}
+        <ListFooter noMore={(list.length > 0) && (total == list.length)} loading={loading} />
     </View>
     </View>
 })
 })

+ 17 - 1
src/features/health/MainHistory2.tsx

@@ -23,6 +23,7 @@ let lastMode = ''
 let myScrollTop = 0
 let myScrollTop = 0
 export default forwardRef((props: { type?: string, fast_type?: string, updateDate?: any, refreshSuccess?: any }, ref) => {
 export default forwardRef((props: { type?: string, fast_type?: string, updateDate?: any, refreshSuccess?: any }, ref) => {
     const [itemLayouts, setItemLayouts] = useState<any>([])
     const [itemLayouts, setItemLayouts] = useState<any>([])
+    const [itemHeights, setItemHeights] = useState<any>([])
     const [list, setList] = useState<any>([])
     const [list, setList] = useState<any>([])
     const [page, setPage] = useState(1)
     const [page, setPage] = useState(1)
     const [total, setTotal] = useState(0)
     const [total, setTotal] = useState(0)
@@ -38,6 +39,8 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
     const [activeList, setActiveList] = useState<any>([])
     const [activeList, setActiveList] = useState<any>([])
     const [sleepList, setSleepList] = useState<any>([])
     const [sleepList, setSleepList] = useState<any>([])
 
 
+    const [pageTop, setPageTop] = useState(0)
+
     const dispatch = useDispatch()
     const dispatch = useDispatch()
     const { t } = useTranslation()
     const { t } = useTranslation()
 
 
@@ -140,12 +143,15 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
         });
         });
         query.exec((res) => {
         query.exec((res) => {
             var layouts: any = []
             var layouts: any = []
+            var heights: any = []
             res.forEach((rect, index) => {
             res.forEach((rect, index) => {
                 if (rect) {
                 if (rect) {
                     layouts[index] = rect.top + myScrollTop
                     layouts[index] = rect.top + myScrollTop
+                    heights[index] = rect.height
                 }
                 }
             });
             });
             setItemLayouts(layouts)
             setItemLayouts(layouts)
+            setItemHeights(heights)
         })
         })
     }
     }
 
 
@@ -154,6 +160,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
         // myScrollTop = top
         // myScrollTop = top
         var top = e.detail.scrollTop - e.detail.deltaY
         var top = e.detail.scrollTop - e.detail.deltaY
         myScrollTop = e.detail.scrollTop
         myScrollTop = e.detail.scrollTop
+        setPageTop(top)
         if (itemLayouts.length > 0) {
         if (itemLayouts.length > 0) {
             var i = -1
             var i = -1
             var date = ''
             var date = ''
@@ -706,6 +713,15 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
             list.length > 0 && <View style={{ minHeight: rpxToPx(464), backgroundColor: '#fff',paddingTop:rpxToPx(60) }}>
             list.length > 0 && <View style={{ minHeight: rpxToPx(464), backgroundColor: '#fff',paddingTop:rpxToPx(60) }}>
                 {
                 {
                     list.map((item, index) => {
                     list.map((item, index) => {
+                        if (itemLayouts.length >= index + 1 && index>5) {
+                            if (Math.abs(itemLayouts[index] - pageTop) > 2500) {
+                                return <View style={{ height: itemHeights[index] }} id={`history-${index}`} key={index}>
+                                    {
+                                        historyMonth(index)
+                                    }
+                                </View>
+                            }
+                        }
                         return <View id={`history-${index}`} key={index}>
                         return <View id={`history-${index}`} key={index}>
                             {
                             {
                                 historyMonth(index)
                                 historyMonth(index)
@@ -729,7 +745,7 @@ export default forwardRef((props: { type?: string, fast_type?: string, updateDat
         }
         }
 
 
 
 
-        <View style={{ height: rpxToPx(40), flexShrink: 0, backgroundColor: '#fff' }} />
+        {/* <View style={{ height: rpxToPx(40), flexShrink: 0, backgroundColor: '#fff' }} /> */}
         <ListFooter noMore={(list.length > 0) && (total == list.length)} loading={loading}/>
         <ListFooter noMore={(list.length > 0) && (total == list.length)} loading={loading}/>
     </View>
     </View>
 })
 })

+ 12 - 8
src/pages/account/Journal.config.ts

@@ -1,9 +1,13 @@
 export default definePageConfig({
 export default definePageConfig({
-    usingComponents:{
-      // 'ec-canvas': '../../lib/ec-canvas/ec-canvas',
-      // 'demo':'../../components/demo'
-    },
-    "disableScroll":true,
-    "navigationBarTitleText":"",
-    "navigationBarBackgroundColor":"#f5f5f5"
-  })
+  // usingComponents:{
+  //   // 'ec-canvas': '../../lib/ec-canvas/ec-canvas',
+  //   // 'demo':'../../components/demo'
+  // },
+  "usingComponents": {
+    "recycle-view": "../../components/miniprogram-recycle-view/recycle-view",
+    "recycle-item": "../../components/miniprogram-recycle-view/recycle-item"
+  },
+  "disableScroll": true,
+  "navigationBarTitleText": "",
+  "navigationBarBackgroundColor": "#f5f5f5"
+})

+ 91 - 69
src/pages/account/Journal.tsx

@@ -35,6 +35,7 @@ export default function Journal() {
     const [journals, setJournals] = useState<any>([])
     const [journals, setJournals] = useState<any>([])
     const [isPulling, setIsPulling] = useState(false)
     const [isPulling, setIsPulling] = useState(false)
     const [itemLayouts, setItemLayouts] = useState<any>([])
     const [itemLayouts, setItemLayouts] = useState<any>([])
+    const [itemHeights, setItemHeights] = useState<any>([])
     const [showDate, setShowDate] = useState(false)
     const [showDate, setShowDate] = useState(false)
     const [date, setDate] = useState('')
     const [date, setDate] = useState('')
     const [loaded, setLoaded] = useState(false)
     const [loaded, setLoaded] = useState(false)
@@ -42,6 +43,7 @@ export default function Journal() {
     const [total, setTotal] = useState(0)
     const [total, setTotal] = useState(0)
     const [loading, setLoading] = useState(false)
     const [loading, setLoading] = useState(false)
     const { t } = useTranslation()
     const { t } = useTranslation()
+    const [pageTop, setPageTop] = useState(0)
 
 
     let router
     let router
     let navigation;
     let navigation;
@@ -139,19 +141,24 @@ export default function Journal() {
             query.select(`#history-${index}`).boundingClientRect()
             query.select(`#history-${index}`).boundingClientRect()
         });
         });
         query.exec((res) => {
         query.exec((res) => {
+
             var layouts: any = []
             var layouts: any = []
+            var heights: any = []
             res.forEach((rect, index) => {
             res.forEach((rect, index) => {
                 if (rect) {
                 if (rect) {
                     layouts[index] = rect.top + myScrollTop
                     layouts[index] = rect.top + myScrollTop
+                    heights[index] = rect.height
                 }
                 }
             });
             });
             setItemLayouts(layouts)
             setItemLayouts(layouts)
+            setItemHeights(heights)
         })
         })
     }
     }
 
 
     function onScroll(e) {
     function onScroll(e) {
         var top = e.detail.scrollTop - e.detail.deltaY
         var top = e.detail.scrollTop - e.detail.deltaY
         myScrollTop = e.detail.scrollTop
         myScrollTop = e.detail.scrollTop
+        setPageTop(top)
 
 
         if (top > 60) {
         if (top > 60) {
 
 
@@ -279,7 +286,7 @@ export default function Journal() {
             }
             }
         }
         }
         if (showDate) {
         if (showDate) {
-            return <View className="history_year_month h42 bold" style={{marginBottom:rpxToPx(60)}}>{dateStr2}</View>
+            return <View className="history_year_month h42 bold" style={{ marginBottom: rpxToPx(60) }}>{dateStr2}</View>
         }
         }
         return <View />
         return <View />
     }
     }
@@ -512,6 +519,82 @@ export default function Journal() {
 
 
     if (!loaded) return <View />
     if (!loaded) return <View />
 
 
+    // return <recycle-view
+    //     batch={true}
+    //     id="recycleId"
+    //     enableBackToTop
+    //     refresherEnabled
+    //     upperThreshold={70}
+    //     lowerThreshold={80}
+    //     refresherBackground={MainColorType.g05}
+    //     onRefresherRefresh={() => {
+    //         setIsPulling(true)
+    //         getJounalsData(1)
+    //     }}
+    //     refresherTriggered={isPulling}
+    //     onScroll={onScroll}
+    //     onScrollToLower={() => {
+    //         more()
+    //     }}
+    // >
+    //     <View>
+    //         <NewHeader type={NewHeaderType.left} title={pageTitle()} />
+    //         {
+    //             markDoneTip()
+    //         }
+    //     </View>
+
+    //     {
+    //         journals.map((item, index) => {
+    //             return <recycle-item key={index}><View key={index}>
+    //                 {
+    //                     historyYear(index)
+    //                 }
+    //                 <View className="history_item2" id={`history-${index}`} onClick={() => {
+    //                     jumpPage('/pages/account/JournalDetail?date=' + item.date + '&window=' + window) //JSON.stringify(item))
+    //                 }}>
+
+    //                     <View className="cell_date" >
+    //                         <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item, index)}</View>
+    //                         <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item, index)}</View>
+    //                     </View>
+    //                     <View style={{ display: 'flex', flexDirection: 'column', flex: 1, width: rpxToPx(552), }}>
+    //                         {
+    //                             journalCell(item, index)
+    //                         }
+    //                     </View>
+    //                 </View></View>
+    //             </recycle-item>
+    //         })
+    //     }
+    //     <View>长列表后面的内容</View>
+    // </recycle-view>
+
+    function itemData(index,item) {
+        if (itemLayouts.length >= index + 1 && index > 5) {
+            if (Math.abs(itemLayouts[index] - pageTop) > 2500) {
+                return <View style={{ height: itemHeights[index] }} id={`history-${index}`}>
+                    {/* {index} */}
+                </View>
+            }
+        }
+        return <View className="history_item2" id={`history-${index}`} onClick={() => {
+            jumpPage('/pages/account/JournalDetail?date=' + item.date + '&window=' + window) //JSON.stringify(item))
+        }}>
+
+            <View className="cell_date" >
+                <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item, index)}</View>
+                <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item, index)}</View>
+            </View>
+            <View style={{ display: 'flex', flexDirection: 'column', flex: 1, width: rpxToPx(552), }}>
+                {
+                    journalCell(item, index)
+                }
+            </View>
+        </View>
+    }
+
+
     return <StickyDateList onRefresherRefresh={() => {
     return <StickyDateList onRefresherRefresh={() => {
         setIsPulling(true)
         setIsPulling(true)
         getJounalsData(1)
         getJounalsData(1)
@@ -528,81 +611,20 @@ export default function Journal() {
             {
             {
                 markDoneTip()
                 markDoneTip()
             }
             }
-            {/* {
-                !router.params.type && <ScrollView style={{ width: rpxToPx(750), flexDirection: 'row', display: 'flex', height: rpxToPx(72), marginBottom: rpxToPx(20) }} scrollX enableFlex showScrollbar={false}>
-                    <View style={{ width: rpxToPx(40), flexShrink: 0 }} />
-                    <NewButton type={NewButtonType.img} onClick={() => {
-                        setWindow('')
-                    }}>
-                        <View className="streak_toolbar_btn"
-                            style={{ backgroundColor: window == '' ? '#B2B2B21A' : 'transparent' }}
-                        >
-                            <Text className={window == '' ? 'bold h30' : 'h30'}
-                                style={{ color: window == '' ? '#000' : MainColorType.g01, marginRight: 5 }}>全部</Text>
-                        </View>
-                    </NewButton>
-
-                    {
-                        items.map((item, index) => {
-                            return <View key={index}>
-                                <NewButton type={NewButtonType.img} onClick={() => {
-                                    setWindow(item)
-                                }}>
-                                    <View className="streak_toolbar_btn"
-                                        style={{ backgroundColor: window == item ? getThemeColor(item) + '1A' : 'transparent' }}
-                                    >
-                                        <Text className={window == item ? 'bold h30' : 'h30'}
-                                            style={{ color: window == item ? getThemeColor(item) : MainColorType.g01, marginRight: 5 }}>{item}</Text>
-                                    </View>
-                                </NewButton>
-                            </View>
-                        })
-                    }
-                    <View style={{ width: rpxToPx(40), flexShrink: 0 }} />
-                </ScrollView>
-            } */}
             <View style={{ minHeight: '100vh', backgroundColor: '#fff', paddingTop: rpxToPx(60) }}>
             <View style={{ minHeight: '100vh', backgroundColor: '#fff', paddingTop: rpxToPx(60) }}>
                 {
                 {
                     journals.map((item, index) => {
                     journals.map((item, index) => {
-                        // if (index>0) return <View key={index}/>
+
+
                         return <View key={index}>
                         return <View key={index}>
                             {
                             {
                                 historyYear(index)
                                 historyYear(index)
                             }
                             }
-                            <View className="history_item2" id={`history-${index}`} onClick={() => {
-                                // if (index == 0) {
-                                //     setTimeout(() => { setShowBadge(false) }, 1000)
-                                // }
-                                jumpPage('/pages/account/JournalDetail?date=' + item.date + '&window=' + window) //JSON.stringify(item))
-                            }}>
-
-                                <View className="cell_date" >
-                                    <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item, index)}</View>
-                                    <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item, index)}</View>
-                                    {/* {
-                                        index == 0 && showBadge && <View style={{
-                                            position: 'absolute',
-                                            backgroundColor: MainColorType.error,
-                                            width: rpxToPx(12),
-                                            height: rpxToPx(12),
-                                            borderRadius: rpxToPx(6),
-                                            right: 10,
-                                            top: 0
-                                        }} />
-                                    } */}
-                                </View>
-                                <View style={{ display: 'flex', flexDirection: 'column', flex: 1, width: rpxToPx(552), }}>
-                                    {/* {
-                                    item.windows.map((window, i) => {
-                                        return journalItem(window, i)
-                                    })
-                                } */}
-                                    {
-                                        journalCell(item, index)
-                                    }
-                                </View>
-                                {/* <View className="border_footer_line" /> */}
-                            </View></View>
+                            {
+                                itemData(index,item)
+                            }
+                            </View>
+                            
                     })
                     })
                 }
                 }
                 {
                 {

+ 614 - 0
src/pages/account/Journal_副本.tsx

@@ -0,0 +1,614 @@
+import JournalCover from "@/features/journal/components/journal_cover";
+import { getJournals } from "@/services/health";
+import { View, Text, Image, ScrollView } from "@tarojs/components";
+import { useEffect, useState } from "react";
+import './Journal.scss'
+import dayjs from "dayjs";
+import { rpxToPx } from "@/utils/tools";
+import { jumpPage } from "@/features/trackTimeDuration/hooks/Common";
+import NewHeader, { NewHeaderType } from "@/_health/components/new_header";
+import Taro, { useRouter } from "@tarojs/taro";
+import StickyDateList from "@/_health/components/sticky_date_list";
+import { MainColorType } from "@/context/themes/color";
+import ListFooter from "@/_health/components/list_footer";
+import TimeTitleDesc from "@/_health/components/time_title_desc";
+import { TimeFormatter } from "@/utils/time_format";
+import { useTranslation } from "react-i18next";
+import NewButton, { NewButtonType } from "@/_health/base/new_button";
+import { getScenario, getThemeColor } from "@/features/health/hooks/health_hooks";
+import { useSelector } from "react-redux";
+import { IconClose } from "@/components/basic/Icons";
+import TargetProgress from "@/_health/components/target_progress";
+import NoRecord from "@/_health/components/no_record";
+
+let myScrollTop = 0
+let useRoute;
+let useNavigation;
+let scenario = '';
+if (process.env.TARO_ENV == 'rn') {
+    useRoute = require("@react-navigation/native").useRoute
+    useNavigation = require("@react-navigation/native").useNavigation
+}
+
+export default function Journal() {
+    const health = useSelector((state: any) => state.health);
+    const [journals, setJournals] = useState<any>([])
+    const [isPulling, setIsPulling] = useState(false)
+    const [itemLayouts, setItemLayouts] = useState<any>([])
+    const [showDate, setShowDate] = useState(false)
+    const [date, setDate] = useState('')
+    const [loaded, setLoaded] = useState(false)
+    const [page, setPage] = useState(1)
+    const [total, setTotal] = useState(0)
+    const [loading, setLoading] = useState(false)
+    const { t } = useTranslation()
+
+    let router
+    let navigation;
+    if (useNavigation) {
+        navigation = useNavigation()
+    }
+
+    if (process.env.TARO_ENV == 'rn') {
+        router = useRoute()
+    }
+    else {
+        router = useRouter()
+    }
+
+    const [window, setWindow] = useState(router.params.type ?? '')
+    const items = ['FAST', 'SLEEP', 'EAT', 'ACTIVE']
+
+    const [showTip, setShowTip] = useState(router.params.show_tip == '1')
+    const [showBadge, setShowBadge] = useState(router.params.show_badge == '1')
+    // useEffect(() => {
+
+    // }, [])
+
+    useEffect(() => {
+        getJounalsData(1)
+    }, [window])
+
+    useEffect(() => {
+        console.log('last show status', showDate)
+    }, [showDate])
+
+    useEffect(() => {
+        if (journals.length > 0) {
+            setTimeout(() => {
+                measureItemLayouts()
+            }, 500)
+        }
+    }, [journals])
+
+    function more() {
+        if (loading) return;
+        if (total == journals.length) return;
+        var index = page;
+        index++;
+        setPage(index)
+        getJounalsData(index)
+    }
+
+    function getJounalsData(index = 1) {
+        setLoading(true)
+        setPage(index)
+        var params: any = {
+            page: index,
+            limit: 10,
+        }
+        params.window = window
+        getJournals(params).then(res => {
+            setLoaded(true)
+            let list = (res as any).data
+            // list.forEach(element => {
+            //     let array: any = []
+            //     element.windows.map(item => {
+            //         item.events.map(event => {
+            //             event.moments && event.moments.map(moment => {
+            //                 if (moment.media && moment.media.length > 0) {
+            //                     moment.media.map(media => {
+            //                         array.push(media.url)
+            //                     })
+            //                 }
+            //             })
+
+            //         })
+            //     })
+            //     element.imgs = array
+            // });
+            setLoading(false)
+            if (index == 1) {
+
+
+                setTotal((res as any).total)
+                setJournals(list)
+                setIsPulling(false)
+            }
+            else {
+                setJournals([...journals, ...list])
+            }
+        }).catch(e => {
+            setLoading(false)
+        })
+    }
+
+    function measureItemLayouts() {
+        const query = Taro.createSelectorQuery()
+        journals.forEach((item, index) => {
+            query.select(`#history-${index}`).boundingClientRect()
+        });
+        query.exec((res) => {
+            var layouts: any = []
+            res.forEach((rect, index) => {
+                if (rect) {
+                    layouts[index] = rect.top + myScrollTop
+                }
+            });
+            setItemLayouts(layouts)
+        })
+    }
+
+    function onScroll(e) {
+        var top = e.detail.scrollTop - e.detail.deltaY
+        myScrollTop = e.detail.scrollTop
+
+        if (top > 60) {
+
+            Taro.setNavigationBarTitle({
+                title: pageTitle()
+            })
+        }
+        else {
+            Taro.setNavigationBarTitle({
+                title: ''
+            })
+        }
+
+
+        if (itemLayouts.length > 0) {
+            var i = -1
+            var dt = ''
+            journals.forEach((item, index) => {
+                if (top >= itemLayouts[index] - 50) {
+                    i = index
+                    var currentDate = (journals[index].date + '').substring(0, 6)
+                    dt = currentDate.substring(0, 4) + '年' + currentDate.substring(4, 6) + '月'
+                }
+            })
+
+            setShowDate(i != -1)
+            setDate(dt)
+        }
+        else {
+            setShowDate(false)
+            setDate('')
+        }
+
+        // if (itemLayouts.length > 0 && itemLayouts[0] > top) {
+        //     setShowDate(false)
+        //     setDate('')
+        //     console.log(itemLayouts[0], showDate, date, top, e.detail.deltaY)
+        // }
+        // else {
+        //     console.log(itemLayouts[0], showDate, date, top, e.detail.deltaY)
+        // }
+
+    }
+
+    function getTitle(item) {
+        if (item.title) {
+            return item.title;
+        }
+        if (item.moments) {
+            return item.moments[0].title
+        }
+        return ''
+    }
+
+    function journalItem(window, index) {
+        var array: any = [];
+        window.events.map(event => {
+            event.moments && event.moments.map(moment => {
+                if (moment.media && moment.media.length > 0) {
+                    moment.media.map(media => {
+                        array.push(media.url)
+                    })
+                }
+            })
+        })
+        return <View style={{ display: 'flex', flexDirection: 'row', marginBottom: rpxToPx(12), overflow: 'hidden', flexShrink: 0 }} key={index}>
+            {
+                array.length > 0 && <JournalCover imgs={array} />
+            }
+            <View style={{
+                display: 'flex',
+                flexDirection: 'column',
+                // flex: 1,
+                overflow: 'hidden',
+                backgroundColor: array.length == 0 ? '#fafafa' : 'transparent',
+                paddingLeft: array.length == 0 ? rpxToPx(20) : 0,
+                paddingRight: array.length == 0 ? rpxToPx(20) : 0,
+                paddingTop: array.length == 0 ? rpxToPx(12) : 0,
+                paddingBottom: array.length == 0 ? rpxToPx(12) : 0,
+                marginRight: rpxToPx(40),
+                flexShrink: 0
+            }}>
+                {
+
+                    window.events.map((item2, index2) => {
+                        if (item2.moments && item2.moments[0].type == 'PIC') return <View key={index2 * 1000} />
+                        return <TimeTitleDesc
+                            key={index2 * 1000}
+                            className="line1"
+                            style={{ width: array.length > 0 ? rpxToPx(350) : rpxToPx(512) }}
+                            timeObj={item2.time}
+                            time={dayjs(item2.time.timestamp).format('HH:mm')}
+                            title={getTitle(item2)}
+                            desc={item2.moments && item2.moments.length > 0 ? item2.moments[0].description : ''}
+                        />
+                    })
+                }
+
+
+
+            </View>
+        </View>
+    }
+
+    function historyYear(index) {
+        var showDate = false;
+        var dateStr2: any = ''
+        if (index == 0) {
+            var currentDate = global.language == 'en' ? dayjs(journals[index].timestamp).format('YYYY') : dayjs(journals[index].timestamp).format('YYYY年')
+            var now = global.language == 'en' ? dayjs().format('YYYY') : dayjs().format('YYYY年')
+
+            if (currentDate != now) {
+                showDate = true
+                dateStr2 = currentDate
+            }
+
+
+        }
+        else {
+            var currentDate = global.language == 'en' ? dayjs(journals[index].timestamp).format('YYYY') : dayjs(journals[index].timestamp).format('YYYY年')
+            var now = global.language == 'en' ? dayjs(journals[index - 1].timestamp).format('YYYY') : dayjs(journals[index - 1].timestamp).format('YYYY年')
+            if (currentDate != now) {
+                showDate = true
+                dateStr2 = currentDate
+            }
+        }
+        if (showDate) {
+            return <View className="history_year_month h42 bold" style={{marginBottom:rpxToPx(60)}}>{dateStr2}</View>
+        }
+        return <View />
+    }
+
+    function historyDate(item, index) {
+        if (index == 0) {
+            if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
+                return '今天'
+            }
+            if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
+                return '昨天'
+            }
+            return dayjs(item.timestamp).format('DD')
+        }
+        if (dayjs(item.timestamp).format('MM-DD') ==
+            dayjs(journals[index - 1].timestamp).format('MM-DD')) {
+            return ''
+        }
+        if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
+            return '今天'
+        }
+        if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
+            return '昨天'
+        }
+        return dayjs(item.timestamp).format('DD')
+    }
+
+    function historyMonth(item, index) {
+        if (index == 0) {
+            if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
+                return ''
+            }
+            if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
+                return ''
+            }
+            return dayjs(item.timestamp).format('MMM')
+        }
+        if (dayjs(item.timestamp).format('MM-DD') ==
+            dayjs(journals[index - 1].timestamp).format('MM-DD')) {
+            return ''
+        }
+        if (global.language == 'zh' && TimeFormatter.isToday(item.timestamp)) {
+            return ''
+        }
+        if (global.language == 'zh' && TimeFormatter.isYesterday(item.timestamp)) {
+            return ''
+        }
+        return dayjs(item.timestamp).format('MMM')
+    }
+
+    function journalCellText(index) {
+        var windows = journals[index].windows
+        var array: any = []
+        windows.map(window => {
+            window.events.map(event => {
+                event.moments && event.moments.map(moment => {
+                    array.push({
+                        title: moment.title,
+                        description: moment.description
+                    })
+                })
+            })
+
+        })
+        return array
+        if (array.length > 0) {
+            return <View style={{ display: 'flex', flexDirection: 'column' }}>
+                {
+                    array.map((item, index) => {
+                        if (index >= 3) return <View key={index * 10000} />
+                        return <TimeTitleDesc key={index * 10000}
+                            time=''
+                            title={item.title}
+                            desc={item.description}
+                        />
+                    })
+                }
+            </View>
+        }
+        return <View />
+    }
+
+    function journalCell(item, index) {
+        // if (item.show_ring) {
+        //     if (item.windows) {
+        //         return <View key={index}>{
+        //             item.windows.map((temp, i) => {
+        //                 return <TargetProgress key={i * 1000} showLine={i < item.windows.length - 1 ? true : false}
+        //                     color={getThemeColor(temp.window)}
+        //                     showRing={true}
+        //                     desc={temp.description}
+        //                     icon={
+        //                         null
+        //                     }
+        //                     canvasId={`${temp.window}${temp.window_range.start_timestamp}${index}`}
+        //                     startTimestamp={temp.window_range.start_timestamp}
+        //                     endTimerstamp={temp.window_range.end_timestamp}
+        //                 />
+        //             })
+        //         }</View>
+
+        //     }
+        //     return <View key={index} />
+        // }
+        return <View style={{ display: 'flex', flexDirection: 'column' }}>
+            {
+                (item.pics.length > 0 || item.texts.length > 0) && <View style={{ display: 'flex', flexDirection: 'row', marginBottom: rpxToPx(12), overflow: 'hidden', flexShrink: 0 }} key={index}>
+                    {
+                        item.pics.length > 0 && <View onClick={(e) => {
+                            if (process.env.TARO_ENV == 'weapp') {
+                                e.stopPropagation()
+                            }
+                            Taro.previewImage({
+                                current: item.pics[0],
+                                urls: item.pics
+                            })
+                        }}>
+                            <JournalCover imgs={item.pics} />
+                        </View>
+                    }
+                    <View style={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        // flex: 1,
+                        overflow: 'hidden',
+                        backgroundColor: item.pics.length == 0 ? '#fafafa' : 'transparent',
+                        paddingLeft: item.pics.length == 0 ? rpxToPx(20) : 0,
+                        paddingRight: item.pics.length == 0 ? rpxToPx(20) : 0,
+                        paddingTop: item.pics.length == 0 ? rpxToPx(12) : 0,
+                        paddingBottom: item.pics.length == 0 ? rpxToPx(12) : 0,
+                        marginRight: rpxToPx(40),
+                        flexShrink: 0
+                    }}>
+                        {
+
+                            item.texts.map((item2, index2) => {
+                                return <TimeTitleDesc
+                                    key={index2 * 1000}
+                                    className='line1'//{item.pics.length == 0 ? 'line2' : 'line3'}
+                                    style={{ width: item.pics.length > 0 ? rpxToPx(350) : rpxToPx(512) }}
+                                    time=''
+                                    title={item2.title}
+                                    desc={item2.description}
+                                />
+                            })
+                        }
+
+
+
+
+                    </View>
+
+
+                </View>
+            }
+
+            {
+                item.show_ring && item.windows && <View key={index}>{
+                    item.windows.map((temp, i) => {
+                        return <TargetProgress key={i * 1000} showLine={i < item.windows.length - 1 ? true : false}
+                            color={getThemeColor(temp.window)}
+                            showRing={true}
+                            desc={temp.description}
+                            icon={
+                                null
+                            }
+                            canvasId={`${temp.window}${temp.window_range.start_timestamp}${index}`}
+                            startTimestamp={temp.window_range.start_timestamp}
+                            endTimerstamp={temp.window_range.end_timestamp}
+                        />
+                    })
+                }</View>
+
+
+            }
+        </View>
+    }
+
+    function pageTitle() {
+        if (router.params.type == 'EAT') {
+            return t('health.title_food_journal')
+        }
+        else if (router.params.type == 'ACTIVE') {
+            return t('health.title_active_journal')
+        }
+        return t('health.title_journal')
+    }
+
+    function markDoneTip() {
+        if (health.mode != 'EAT' && health.mode != 'ACTIVE') return
+        var scenario = getScenario(health.windows, health.mode)
+        if (scenario.status != 'OG') return
+        if (!showTip) return
+
+        var strTitle = ''
+        if (scenario.archive_timestamp) {
+            var today = new Date().getDate()
+            var date = new Date(scenario.archive_timestamp).getDate()
+            if (today == date) {
+                strTitle = t('health.tonight_at', { time: dayjs(scenario.archive_timestamp).format('HH:mm') })
+            }
+            else {
+                strTitle = t('health.tomorrow_at', { time: dayjs(scenario.archive_timestamp).format('HH:mm') })
+            }
+        }
+
+        return <View className="mark_done_tip" style={{
+            backgroundColor: getThemeColor(health.mode) + '1A'
+        }}>
+            <View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
+                <Text className="h28 bold" style={{ color: getThemeColor(health.mode) }}>{strTitle}</Text>
+                <Text className="h24 bold"><Text style={{ fontWeight: 'normal' }}>{t('health.detail_complete_tip_head')}</Text>{t('health.detail_complete_tip_end')}</Text>
+            </View>
+            <NewButton type={NewButtonType.img} btnStyle={{
+                height: rpxToPx(32),
+                width: rpxToPx(32)
+            }} onClick={() => {
+                setShowTip(false)
+                if (health.mode == 'EAT') {
+                    global.hideEatArchiveTip = true
+                }
+                else {
+                    global.hideActiveArchiveTip = true
+                }
+            }}>
+                <IconClose color={MainColorType.g03} width={rpxToPx(32)} height={rpxToPx(32)} />
+            </NewButton>
+        </View>
+    }
+
+    if (!loaded) return <View />
+
+    return <StickyDateList onRefresherRefresh={() => {
+        setIsPulling(true)
+        getJounalsData(1)
+    }} isPulling={isPulling}
+        onScroll={onScroll}
+        showDate={showDate}
+        date={date}
+        loadMore={() => {
+            more()
+        }}
+    ><View style={{ display: 'flex', flexDirection: 'column', minHeight: rpxToPx(464), backgroundColor: '#f5f5f5' }}>
+
+            <NewHeader type={NewHeaderType.left} title={pageTitle()} />
+            {
+                markDoneTip()
+            }
+            {/* {
+                !router.params.type && <ScrollView style={{ width: rpxToPx(750), flexDirection: 'row', display: 'flex', height: rpxToPx(72), marginBottom: rpxToPx(20) }} scrollX enableFlex showScrollbar={false}>
+                    <View style={{ width: rpxToPx(40), flexShrink: 0 }} />
+                    <NewButton type={NewButtonType.img} onClick={() => {
+                        setWindow('')
+                    }}>
+                        <View className="streak_toolbar_btn"
+                            style={{ backgroundColor: window == '' ? '#B2B2B21A' : 'transparent' }}
+                        >
+                            <Text className={window == '' ? 'bold h30' : 'h30'}
+                                style={{ color: window == '' ? '#000' : MainColorType.g01, marginRight: 5 }}>全部</Text>
+                        </View>
+                    </NewButton>
+
+                    {
+                        items.map((item, index) => {
+                            return <View key={index}>
+                                <NewButton type={NewButtonType.img} onClick={() => {
+                                    setWindow(item)
+                                }}>
+                                    <View className="streak_toolbar_btn"
+                                        style={{ backgroundColor: window == item ? getThemeColor(item) + '1A' : 'transparent' }}
+                                    >
+                                        <Text className={window == item ? 'bold h30' : 'h30'}
+                                            style={{ color: window == item ? getThemeColor(item) : MainColorType.g01, marginRight: 5 }}>{item}</Text>
+                                    </View>
+                                </NewButton>
+                            </View>
+                        })
+                    }
+                    <View style={{ width: rpxToPx(40), flexShrink: 0 }} />
+                </ScrollView>
+            } */}
+            <View style={{ minHeight: '100vh', backgroundColor: '#fff', paddingTop: rpxToPx(60) }}>
+                {
+                    journals.map((item, index) => {
+                        // if (index>0) return <View key={index}/>
+                        return <View key={index}>
+                            {
+                                historyYear(index)
+                            }
+                            <View className="history_item2" id={`history-${index}`} onClick={() => {
+                                // if (index == 0) {
+                                //     setTimeout(() => { setShowBadge(false) }, 1000)
+                                // }
+                                jumpPage('/pages/account/JournalDetail?date=' + item.date + '&window=' + window) //JSON.stringify(item))
+                            }}>
+
+                                <View className="cell_date" >
+                                    <View className="h42 bold" style={{ lineHeight: rpxToPx(60) + 'px' }}>{historyDate(item, index)}</View>
+                                    <View className="h24 bold" style={{ marginLeft: rpxToPx(6), marginTop: rpxToPx(13), lineHeight: rpxToPx(47) + 'px' }}>{historyMonth(item, index)}</View>
+                                    {/* {
+                                        index == 0 && showBadge && <View style={{
+                                            position: 'absolute',
+                                            backgroundColor: MainColorType.error,
+                                            width: rpxToPx(12),
+                                            height: rpxToPx(12),
+                                            borderRadius: rpxToPx(6),
+                                            right: 10,
+                                            top: 0
+                                        }} />
+                                    } */}
+                                </View>
+                                <View style={{ display: 'flex', flexDirection: 'column', flex: 1, width: rpxToPx(552), }}>
+                                    {/* {
+                                    item.windows.map((window, i) => {
+                                        return journalItem(window, i)
+                                    })
+                                } */}
+                                    {
+                                        journalCell(item, index)
+                                    }
+                                </View>
+                                {/* <View className="border_footer_line" /> */}
+                            </View></View>
+                    })
+                }
+                {
+                    loaded && journals.length == 0 && <NoRecord />
+                }
+            </View>
+            <ListFooter noMore={(journals.length > 0) && (total == journals.length)} loading={loading} />
+        </View></StickyDateList>
+}

+ 5 - 1
src/pages/clock/Clock.config.ts

@@ -8,6 +8,10 @@ export default definePageConfig({
     //         "defaultDisplayBlock": true
     //         "defaultDisplayBlock": true
     //     }
     //     }
     // },
     // },
+    "usingComponents": {
+        "recycle-view": "../../components/miniprogram-recycle-view/recycle-view",
+        "recycle-item": "../../components/miniprogram-recycle-view/recycle-item"
+    },
     "disableScroll": true,
     "disableScroll": true,
-    "navigationStyle":"custom",
+    "navigationStyle": "custom",
 })
 })

+ 2 - 0
src/pages/clock/ClockNew.tsx

@@ -283,6 +283,8 @@ export default function ClockNew(props: { children: any, onScroll: any }) {
         if (!user.isLogin) {
         if (!user.isLogin) {
             return
             return
         }
         }
+        console.log('zzzzz',user.isLogin)
+        debugger
 
 
 
 
         const showAlert1 = await getStorage('148alert') || false;
         const showAlert1 = await getStorage('148alert') || false;

+ 8 - 28
yarn.lock

@@ -14752,6 +14752,11 @@ miniprogram-exparser@latest:
   resolved "https://registry.npmmirror.com/miniprogram-exparser/-/miniprogram-exparser-2.29.1.tgz#c8404b7182e7b1e7fb68e2d9d37abdae90db26b5"
   resolved "https://registry.npmmirror.com/miniprogram-exparser/-/miniprogram-exparser-2.29.1.tgz#c8404b7182e7b1e7fb68e2d9d37abdae90db26b5"
   integrity sha512-f2LUVYcQ5O664nOHhrEbtR//hlqln88dRY0mIwuRncJfuXMCdK9FBk0vzNDG6EgaaeTt3iGLeFQLRHlhYktkXw==
   integrity sha512-f2LUVYcQ5O664nOHhrEbtR//hlqln88dRY0mIwuRncJfuXMCdK9FBk0vzNDG6EgaaeTt3iGLeFQLRHlhYktkXw==
 
 
+miniprogram-recycle-view@^0.1.5:
+  version "0.1.5"
+  resolved "https://registry.npmjs.org/miniprogram-recycle-view/-/miniprogram-recycle-view-0.1.5.tgz#854c2fe0df63c2165f739c8e0446aa35aa6c7d78"
+  integrity sha512-Pfm/u+jfg+b5zDhjXqm7QbR9IuViqX8JyzcHDgr6Zw9wSK0wmbXNzscA1WoSnGP5Z2nG2TIqtiNXvEbvsyrxXA==
+
 miniprogram-simulate@^1.1.5:
 miniprogram-simulate@^1.1.5:
   version "1.6.1"
   version "1.6.1"
   resolved "https://registry.npmmirror.com/miniprogram-simulate/-/miniprogram-simulate-1.6.1.tgz#48b5e0e79c3a53525d38487a264a659ac5ca948d"
   resolved "https://registry.npmmirror.com/miniprogram-simulate/-/miniprogram-simulate-1.6.1.tgz#48b5e0e79c3a53525d38487a264a659ac5ca948d"
@@ -18562,7 +18567,7 @@ string-hash-64@1.0.3:
   resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
   resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
   integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==
   integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==
 
 
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
   version "4.2.3"
   version "4.2.3"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -18579,15 +18584,6 @@ string-width@^2.1.0, string-width@^2.1.1:
     is-fullwidth-code-point "^2.0.0"
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
     strip-ansi "^4.0.0"
 
 
-string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
-  version "4.2.3"
-  resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
 string-width@^5.0.1, string-width@^5.1.2:
 string-width@^5.0.1, string-width@^5.1.2:
   version "5.1.2"
   version "5.1.2"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -18668,7 +18664,7 @@ stringify-entities@^1.0.1:
     is-alphanumerical "^1.0.0"
     is-alphanumerical "^1.0.0"
     is-hexadecimal "^1.0.0"
     is-hexadecimal "^1.0.0"
 
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   version "6.0.1"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -18696,13 +18692,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
   dependencies:
   dependencies:
     ansi-regex "^4.1.0"
     ansi-regex "^4.1.0"
 
 
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
 strip-ansi@^7.0.1:
 strip-ansi@^7.0.1:
   version "7.1.0"
   version "7.1.0"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -20591,7 +20580,7 @@ wonka@^6.3.2:
   resolved "https://registry.npmmirror.com/wonka/-/wonka-6.3.4.tgz#76eb9316e3d67d7febf4945202b5bdb2db534594"
   resolved "https://registry.npmmirror.com/wonka/-/wonka-6.3.4.tgz#76eb9316e3d67d7febf4945202b5bdb2db534594"
   integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==
   integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==
 
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   version "7.0.0"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -20609,15 +20598,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
     strip-ansi "^6.0.0"
 
 
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
 wrap-ansi@^8.1.0:
 wrap-ansi@^8.1.0:
   version "8.1.0"
   version "8.1.0"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"