@mpxjs/webpack-plugin 2.10.17-beta.6 → 2.10.17-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +1 -0
- package/lib/json-compiler/index.js +18 -1
- package/lib/platform/style/wx/index.js +1 -17
- package/lib/react/processJSON.js +20 -1
- package/lib/react/processScript.js +1 -0
- package/lib/react/script-helper.js +0 -1
- package/lib/runtime/components/ali/mpx-recycle-view.mpx +518 -0
- package/lib/runtime/components/ali/mpx-sticky-header.mpx +212 -0
- package/lib/runtime/components/ali/mpx-sticky-section.mpx +17 -0
- package/lib/runtime/components/react/animationHooks/useTransitionHooks.ts +34 -30
- package/lib/runtime/components/react/animationHooks/utils.ts +3 -2
- package/lib/runtime/components/react/dist/animationHooks/useTransitionHooks.js +38 -33
- package/lib/runtime/components/react/dist/animationHooks/utils.js +3 -2
- package/lib/runtime/components/react/dist/mpx-image.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-recycle-view.d.ts +45 -0
- package/lib/runtime/components/react/dist/mpx-recycle-view.jsx +272 -0
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +6 -5
- package/lib/runtime/components/react/dist/utils.d.ts +2 -1
- package/lib/runtime/components/react/dist/utils.jsx +15 -21
- package/lib/runtime/components/react/mpx-image.tsx +2 -2
- package/lib/runtime/components/react/mpx-recycle-view.tsx +398 -0
- package/lib/runtime/components/react/mpx-scroll-view.tsx +1 -0
- package/lib/runtime/components/react/mpx-sticky-section.tsx +1 -1
- package/lib/runtime/components/react/mpx-swiper.tsx +6 -5
- package/lib/runtime/components/react/utils.tsx +18 -23
- package/lib/runtime/components/web/mpx-recycle-view.vue +508 -0
- package/lib/runtime/components/wx/mpx-list-header-default.mpx +21 -0
- package/lib/runtime/components/wx/mpx-recycle-item-default.mpx +21 -0
- package/lib/runtime/components/wx/mpx-recycle-view.mpx +193 -0
- package/lib/runtime/components/wx/mpx-section-header-default.mpx +21 -0
- package/lib/template-compiler/compiler.js +8 -3
- package/lib/utils/const.js +17 -0
- package/lib/utils/process-extend-components.js +43 -0
- package/lib/web/processJSON.js +20 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -805,6 +805,7 @@ class MpxWebpackPlugin {
|
|
|
805
805
|
// 若配置disableRequireAsync=true, 则全平台构建不支持异步分包
|
|
806
806
|
supportRequireAsync: !this.options.disableRequireAsync && (this.options.mode === 'wx' || this.options.mode === 'ali' || this.options.mode === 'tt' || isWeb(this.options.mode) || (isReact(this.options.mode) && this.options.rnConfig.supportSubpackage)),
|
|
807
807
|
partialCompileRules: this.options.partialCompileRules,
|
|
808
|
+
useExtendComponents: this.options.useExtendComponents,
|
|
808
809
|
collectDynamicEntryInfo: ({ resource, packageName, filename, entryType, hasAsync }) => {
|
|
809
810
|
const curInfo = mpx.dynamicEntryInfo[packageName] = mpx.dynamicEntryInfo[packageName] || {
|
|
810
811
|
hasPage: false,
|
|
@@ -13,6 +13,7 @@ const createJSONHelper = require('./helper')
|
|
|
13
13
|
const RecordIndependentDependency = require('../dependencies/RecordIndependentDependency')
|
|
14
14
|
const RecordRuntimeInfoDependency = require('../dependencies/RecordRuntimeInfoDependency')
|
|
15
15
|
const { MPX_DISABLE_EXTRACTOR_CACHE, RESOLVE_IGNORED_ERR, JSON_JS_EXT } = require('../utils/const')
|
|
16
|
+
const { processExtendComponents } = require('../utils/process-extend-components')
|
|
16
17
|
const resolve = require('../utils/resolve')
|
|
17
18
|
const resolveTabBarPath = require('../utils/resolve-tab-bar-path')
|
|
18
19
|
const resolveMpxCustomElementPath = require('../utils/resolve-mpx-custom-element-path')
|
|
@@ -75,13 +76,17 @@ module.exports = function (content) {
|
|
|
75
76
|
const { getRequestString } = createHelpers(this)
|
|
76
77
|
|
|
77
78
|
let currentName
|
|
78
|
-
|
|
79
|
+
let hasApp = true
|
|
79
80
|
if (isApp) {
|
|
80
81
|
currentName = appInfo.name
|
|
81
82
|
} else {
|
|
82
83
|
currentName = componentsMap[resourcePath] || pagesMap[resourcePath]
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
if (!appInfo.name) {
|
|
87
|
+
hasApp = false
|
|
88
|
+
}
|
|
89
|
+
|
|
85
90
|
const relativePath = useRelativePath ? publicPath + path.dirname(currentName) : ''
|
|
86
91
|
|
|
87
92
|
const copydir = (dir, context, callback) => {
|
|
@@ -160,6 +165,18 @@ module.exports = function (content) {
|
|
|
160
165
|
json.usingComponents = json.usingComponents || {}
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
if (mode === 'wx' || mode === 'ali') {
|
|
169
|
+
const { useExtendComponents = {} } = mpx
|
|
170
|
+
if ((isApp || !hasApp) && useExtendComponents[mode]) {
|
|
171
|
+
const extendComponents = processExtendComponents({
|
|
172
|
+
useExtendComponents,
|
|
173
|
+
mode,
|
|
174
|
+
emitWarning
|
|
175
|
+
})
|
|
176
|
+
json.usingComponents = Object.assign({}, extendComponents, json.usingComponents)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
// 快应用补全json配置,必填项
|
|
164
181
|
if (mode === 'qa' && isApp) {
|
|
165
182
|
const defaultConf = {
|
|
@@ -139,11 +139,10 @@ module.exports = function getSpec ({ warn, error }) {
|
|
|
139
139
|
const type = getValueType(prop)
|
|
140
140
|
const tipsType = (type) => {
|
|
141
141
|
const info = {
|
|
142
|
-
[ValueType.number]: '2rpx,10%,30rpx',
|
|
143
142
|
[ValueType.color]: 'rgb,rgba,hsl,hsla,hwb,named color,#000000',
|
|
144
143
|
[ValueType.enum]: `${SUPPORTED_PROP_VAL_ARR[prop]?.join(',')}`
|
|
145
144
|
}
|
|
146
|
-
tips(`Value of ${prop} in ${selector} should be ${type}
|
|
145
|
+
tips(`Value of ${prop} in ${selector} should be ${type}${info[type] ? `, eg ${info[type]}` : ''}, received [${value}], please check again!`)
|
|
147
146
|
}
|
|
148
147
|
switch (type) {
|
|
149
148
|
case ValueType.number: {
|
|
@@ -549,15 +548,6 @@ module.exports = function getSpec ({ warn, error }) {
|
|
|
549
548
|
return { prop, value: values[0].trim() }
|
|
550
549
|
}
|
|
551
550
|
|
|
552
|
-
const formatZIndex = ({ prop, value, selector }, { mode }) => {
|
|
553
|
-
// z-index auto 报错
|
|
554
|
-
if (value === 'auto') {
|
|
555
|
-
error(`Property [${prop}] does not supported [${value}] on ${selector} in ${mode} environment, please check again!`)
|
|
556
|
-
return { prop, value: 0 }
|
|
557
|
-
}
|
|
558
|
-
return { prop, value: value }
|
|
559
|
-
}
|
|
560
|
-
|
|
561
551
|
// const formatBoxShadow = ({ prop, value, selector }, { mode }) => {
|
|
562
552
|
// value = value.trim()
|
|
563
553
|
// if (value === 'none') {
|
|
@@ -623,12 +613,6 @@ module.exports = function getSpec ({ warn, error }) {
|
|
|
623
613
|
android: formatFontFamily,
|
|
624
614
|
harmony: formatFontFamily
|
|
625
615
|
},
|
|
626
|
-
{
|
|
627
|
-
test: 'z-index',
|
|
628
|
-
ios: formatZIndex,
|
|
629
|
-
android: formatZIndex,
|
|
630
|
-
harmony: formatZIndex
|
|
631
|
-
},
|
|
632
616
|
// {
|
|
633
617
|
// test: 'box-shadow',
|
|
634
618
|
// ios: formatBoxShadow,
|
package/lib/react/processJSON.js
CHANGED
|
@@ -12,6 +12,7 @@ const { transSubpackage } = require('../utils/trans-async-sub-rules')
|
|
|
12
12
|
const createJSONHelper = require('../json-compiler/helper')
|
|
13
13
|
const getRulesRunner = require('../platform/index')
|
|
14
14
|
const { RESOLVE_IGNORED_ERR } = require('../utils/const')
|
|
15
|
+
const { processExtendComponents } = require('../utils/process-extend-components')
|
|
15
16
|
const RecordResourceMapDependency = require('../dependencies/RecordResourceMapDependency')
|
|
16
17
|
const RecordPageConfigsMapDependency = require('../dependencies/RecordPageConfigsMapDependency')
|
|
17
18
|
|
|
@@ -32,11 +33,19 @@ module.exports = function (jsonContent, {
|
|
|
32
33
|
mode,
|
|
33
34
|
srcMode,
|
|
34
35
|
env,
|
|
35
|
-
projectRoot
|
|
36
|
+
projectRoot,
|
|
37
|
+
useExtendComponents = {},
|
|
38
|
+
appInfo
|
|
36
39
|
} = mpx
|
|
37
40
|
|
|
38
41
|
const context = loaderContext.context
|
|
39
42
|
|
|
43
|
+
let hasApp = true
|
|
44
|
+
|
|
45
|
+
if (!appInfo.name) {
|
|
46
|
+
hasApp = false
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
const emitWarning = (msg) => {
|
|
41
50
|
loaderContext.emitWarning(
|
|
42
51
|
new Error('[Mpx json warning][' + loaderContext.resource + ']: ' + msg)
|
|
@@ -118,6 +127,16 @@ module.exports = function (jsonContent, {
|
|
|
118
127
|
if (ctorType !== 'app') {
|
|
119
128
|
rulesRunnerOptions.mainKey = ctorType
|
|
120
129
|
}
|
|
130
|
+
if (!hasApp || ctorType === 'app') {
|
|
131
|
+
if (useExtendComponents[mode]) {
|
|
132
|
+
const extendComponents = processExtendComponents({
|
|
133
|
+
useExtendComponents,
|
|
134
|
+
mode,
|
|
135
|
+
emitWarning
|
|
136
|
+
})
|
|
137
|
+
jsonObj.usingComponents = Object.assign({}, extendComponents, jsonObj.usingComponents)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
121
140
|
|
|
122
141
|
const rulesRunner = getRulesRunner(rulesRunnerOptions)
|
|
123
142
|
|
|
@@ -68,6 +68,7 @@ import { getComponent, getAsyncSuspense } from ${stringifyRequest(loaderContext,
|
|
|
68
68
|
output += buildI18n({ loaderContext })
|
|
69
69
|
}
|
|
70
70
|
output += getRequireScript({ ctorType, script, loaderContext })
|
|
71
|
+
|
|
71
72
|
output += `export default global.__mpxOptionsMap[${JSON.stringify(moduleId)}]\n`
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -4,7 +4,6 @@ const parseRequest = require('../utils/parse-request')
|
|
|
4
4
|
const shallowStringify = require('../utils/shallow-stringify')
|
|
5
5
|
const normalize = require('../utils/normalize')
|
|
6
6
|
const addQuery = require('../utils/add-query')
|
|
7
|
-
|
|
8
7
|
function stringifyRequest (loaderContext, request) {
|
|
9
8
|
return loaderUtils.stringifyRequest(loaderContext, request)
|
|
10
9
|
}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="mpx-recycle-view">
|
|
3
|
+
<scroll-view
|
|
4
|
+
id="scrollViewContainer"
|
|
5
|
+
class="scroll-view-container"
|
|
6
|
+
wx:ref="mpxRecycleView"
|
|
7
|
+
scroll-y="{{true}}"
|
|
8
|
+
scroll-top="{{scrollTop}}"
|
|
9
|
+
enhanced="{{enhanced}}"
|
|
10
|
+
scroll-with-animation="{{scrollWithAnimation}}"
|
|
11
|
+
scroll-animation-duration="{{scrollAnimationDuration}}"
|
|
12
|
+
refresher-enabled="{{refresherEnabled}}"
|
|
13
|
+
refresher-triggered="{{refresherTriggered}}"
|
|
14
|
+
bindscroll="onScroll"
|
|
15
|
+
bindscrolltolower="onScrolltolower"
|
|
16
|
+
bindrefresherrefresh="onRefresherrefresh"
|
|
17
|
+
wx:style="{{scrollViewStyle}}"
|
|
18
|
+
>
|
|
19
|
+
<view class="content-wrapper">
|
|
20
|
+
<block wx:if="{{useListHeader}}">
|
|
21
|
+
<list-header listHeaderData="{{listHeaderData}}"></list-header>
|
|
22
|
+
</block>
|
|
23
|
+
<view
|
|
24
|
+
class="infinite-list-placeholder"
|
|
25
|
+
ref="infinitePlaceholder"
|
|
26
|
+
wx:style="{{placeholderStyle}}"
|
|
27
|
+
></view>
|
|
28
|
+
<view class="infinite-list" ref="infiniteList" wx:style="{{infiniteListStyle}}">
|
|
29
|
+
<block wx:for="{{visibleData}}">
|
|
30
|
+
<section-header
|
|
31
|
+
wx:if="{{item.itemData.isSectionHeader}}"
|
|
32
|
+
wx:key="header{{_index}}"
|
|
33
|
+
itemData="{{item.itemData}}"
|
|
34
|
+
/>
|
|
35
|
+
<recycle-item
|
|
36
|
+
wx:if="{{!item.itemData.isSectionHeader}}"
|
|
37
|
+
wx:key="item{{_index}}"
|
|
38
|
+
itemData="{{item.itemData}}"
|
|
39
|
+
/>
|
|
40
|
+
</block>
|
|
41
|
+
</view>
|
|
42
|
+
</view>
|
|
43
|
+
<block
|
|
44
|
+
wx:if="{{ _stickyHeaders &&
|
|
45
|
+
_stickyHeaders.length &&
|
|
46
|
+
positions.length &&
|
|
47
|
+
enableSticky
|
|
48
|
+
}}"
|
|
49
|
+
>
|
|
50
|
+
<sticky-header
|
|
51
|
+
scroll-view-id="scrollViewContainer"
|
|
52
|
+
sticky-id="mpx-sticky-header-{{index}}"
|
|
53
|
+
wx:ref="stickyHeader"
|
|
54
|
+
class="sticky-section"
|
|
55
|
+
wx:style="top: {{(positions[stickyItem._index] && positions[stickyItem._index].top) || 0}}px"
|
|
56
|
+
wx:for="{{_stickyHeaders}}"
|
|
57
|
+
wx:for-item="stickyItem"
|
|
58
|
+
wx:key="_index"
|
|
59
|
+
enable-polling="{{enablePolling}}"
|
|
60
|
+
polling-duration="{{pollingDuration}}"
|
|
61
|
+
>
|
|
62
|
+
<section-header
|
|
63
|
+
wx:key="{{'header' + stickyItem._index}}"
|
|
64
|
+
itemData="{{stickyItem.itemData}}"
|
|
65
|
+
/>
|
|
66
|
+
</sticky-header>
|
|
67
|
+
</block>
|
|
68
|
+
</scroll-view>
|
|
69
|
+
</view>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<script>
|
|
73
|
+
import mpx, { createComponent } from '@mpxjs/core'
|
|
74
|
+
createComponent({
|
|
75
|
+
properties: {
|
|
76
|
+
width: String | Number,
|
|
77
|
+
height: String | Number,
|
|
78
|
+
listData: {
|
|
79
|
+
type: Array,
|
|
80
|
+
value: () => {
|
|
81
|
+
return []
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
scrollOptions: {
|
|
85
|
+
type: Object,
|
|
86
|
+
value: () => {
|
|
87
|
+
return {}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
minRenderCount: {
|
|
91
|
+
type: Number,
|
|
92
|
+
value: 10
|
|
93
|
+
},
|
|
94
|
+
bufferScale: {
|
|
95
|
+
type: Number,
|
|
96
|
+
value: 1
|
|
97
|
+
},
|
|
98
|
+
itemHeight: {
|
|
99
|
+
type: Object,
|
|
100
|
+
value: () => {
|
|
101
|
+
return {}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
listHeaderHeight: {
|
|
105
|
+
type: Object,
|
|
106
|
+
value: () => {
|
|
107
|
+
return {}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
sectionHeaderHeight: {
|
|
111
|
+
type: Object,
|
|
112
|
+
value: () => {
|
|
113
|
+
return {}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
listHeaderData: {
|
|
117
|
+
type: Object,
|
|
118
|
+
value: () => {
|
|
119
|
+
return {}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
enhanced: {
|
|
123
|
+
type: Boolean,
|
|
124
|
+
value: false
|
|
125
|
+
},
|
|
126
|
+
refresherEnabled: {
|
|
127
|
+
type: Boolean,
|
|
128
|
+
value: false
|
|
129
|
+
},
|
|
130
|
+
refresherTriggered: {
|
|
131
|
+
type: Boolean,
|
|
132
|
+
value: false
|
|
133
|
+
},
|
|
134
|
+
enableSticky: {
|
|
135
|
+
type: Boolean,
|
|
136
|
+
value: false
|
|
137
|
+
},
|
|
138
|
+
scrollWithAnimation: {
|
|
139
|
+
type: Boolean,
|
|
140
|
+
value: false
|
|
141
|
+
},
|
|
142
|
+
scrollAnimationDuration: {
|
|
143
|
+
type: Number,
|
|
144
|
+
value: 300
|
|
145
|
+
},
|
|
146
|
+
enablePolling: {
|
|
147
|
+
type: Boolean,
|
|
148
|
+
value: false
|
|
149
|
+
},
|
|
150
|
+
pollingDuration: {
|
|
151
|
+
type: Number,
|
|
152
|
+
value: 3000
|
|
153
|
+
},
|
|
154
|
+
useListHeader: {
|
|
155
|
+
type: Boolean,
|
|
156
|
+
value: false
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
data: {
|
|
160
|
+
start: 0,
|
|
161
|
+
end: 0,
|
|
162
|
+
containerHeight: 0,
|
|
163
|
+
positions: [],
|
|
164
|
+
visibleCounts: [],
|
|
165
|
+
infiniteListStyle: '',
|
|
166
|
+
placeholderStyle: '',
|
|
167
|
+
scrollTop: 0
|
|
168
|
+
},
|
|
169
|
+
computed: {
|
|
170
|
+
_listData() {
|
|
171
|
+
return this.listData.map((item, index) => {
|
|
172
|
+
return {
|
|
173
|
+
itemData: item,
|
|
174
|
+
_index: `_${index}`
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
_stickyHeaders() {
|
|
179
|
+
const data = []
|
|
180
|
+
this.listData.forEach((item, index) => {
|
|
181
|
+
if (item.isSectionHeader) {
|
|
182
|
+
data.push({
|
|
183
|
+
itemData: item,
|
|
184
|
+
_index: index
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
return data
|
|
189
|
+
},
|
|
190
|
+
scrollViewStyle() {
|
|
191
|
+
return `height: ${this.formatDimension(
|
|
192
|
+
this.height
|
|
193
|
+
)};width: ${this.formatDimension(this.width)}`
|
|
194
|
+
},
|
|
195
|
+
visibleCount() {
|
|
196
|
+
if (!this.visibleCounts.length) return this.minRenderCount
|
|
197
|
+
return Math.max(this.visibleCounts[this.start], this.minRenderCount)
|
|
198
|
+
},
|
|
199
|
+
aboveCount() {
|
|
200
|
+
if (!this._listData.length || !this.visibleCounts.length) return 0
|
|
201
|
+
let count = 0
|
|
202
|
+
const startIndex = Math.max(0, this.start)
|
|
203
|
+
const endIndex = Math.max(0, startIndex - this.bufferScale)
|
|
204
|
+
|
|
205
|
+
for (let i = startIndex; i > endIndex; i--) {
|
|
206
|
+
count += this.visibleCounts[i] || 0
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return count
|
|
210
|
+
},
|
|
211
|
+
belowCount() {
|
|
212
|
+
if (!this._listData.length || !this.visibleCounts.length) return 0
|
|
213
|
+
let count = 0
|
|
214
|
+
const startIndex = Math.min(this.start, this._listData.length - 1)
|
|
215
|
+
const endIndex = Math.min(startIndex + this.bufferScale, this._listData.length - 1)
|
|
216
|
+
|
|
217
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
218
|
+
count += this.visibleCounts[i] || 0
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return count
|
|
222
|
+
},
|
|
223
|
+
visibleData() {
|
|
224
|
+
if (!this._listData.length) return []
|
|
225
|
+
|
|
226
|
+
const start = Math.min(Math.max(0, this.start - this.aboveCount), this._listData.length - 1)
|
|
227
|
+
|
|
228
|
+
let end = Math.min(this._listData.length, this.start + this.visibleCount + this.belowCount)
|
|
229
|
+
|
|
230
|
+
// 如果接近列表末尾,确保显示所有剩余项目
|
|
231
|
+
if (end > this._listData.length - 3) {
|
|
232
|
+
end = this._listData.length
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return this._listData.slice(start, end).map((item, idx) => {
|
|
236
|
+
const realIndex = start + idx
|
|
237
|
+
return {
|
|
238
|
+
...item,
|
|
239
|
+
_index: `_${realIndex}`
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
},
|
|
243
|
+
_listHeaderHeight() {
|
|
244
|
+
let listHeaderHeight = 0
|
|
245
|
+
if (this.useListHeader) {
|
|
246
|
+
listHeaderHeight = this.getItemHeight(this.listHeaderData, 0, 'listHeaderHeight') || 0
|
|
247
|
+
}
|
|
248
|
+
return listHeaderHeight
|
|
249
|
+
},
|
|
250
|
+
placeholderHeight() {
|
|
251
|
+
if (!this.positions.length) return 0
|
|
252
|
+
return this.positions[this.positions.length - 1].bottom - this._listHeaderHeight || 0
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
watch: {
|
|
256
|
+
listData: {
|
|
257
|
+
handler() {
|
|
258
|
+
this.initPositions()
|
|
259
|
+
this.setPlaceholderStyle()
|
|
260
|
+
// 更新真实偏移量
|
|
261
|
+
this.setStartOffset()
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
containerHeight() {
|
|
265
|
+
this.calculateVisibleCounts()
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
ready() {
|
|
269
|
+
this.initPositions()
|
|
270
|
+
this.getContainerHeight()
|
|
271
|
+
this.setPlaceholderStyle()
|
|
272
|
+
if (!this.positions || !this.positions.length) {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
this.start = this.getStartIndex()
|
|
276
|
+
this.end = this.start + this.visibleCount
|
|
277
|
+
this.setStartOffset()
|
|
278
|
+
},
|
|
279
|
+
methods: {
|
|
280
|
+
getContainerHeight() {
|
|
281
|
+
mpx
|
|
282
|
+
.createSelectorQuery()
|
|
283
|
+
.select('#scrollViewContainer')
|
|
284
|
+
.boundingClientRect((rect) => {
|
|
285
|
+
if (!rect) return
|
|
286
|
+
this.containerHeight = rect.height
|
|
287
|
+
})
|
|
288
|
+
.exec()
|
|
289
|
+
},
|
|
290
|
+
formatDimension(value) {
|
|
291
|
+
return typeof value === 'number' ? `${value}px` : value || '100%'
|
|
292
|
+
},
|
|
293
|
+
setPlaceholderStyle() {
|
|
294
|
+
this.placeholderStyle = `height: ${this.placeholderHeight}px`
|
|
295
|
+
},
|
|
296
|
+
initPositions() {
|
|
297
|
+
let bottom = this._listHeaderHeight || 0
|
|
298
|
+
this.positions = this._listData.map((item, index) => {
|
|
299
|
+
const height = this.getItemHeight(
|
|
300
|
+
item.itemData,
|
|
301
|
+
index,
|
|
302
|
+
item.itemData.isSectionHeader ? 'sectionHeaderHeight' : 'itemHeight'
|
|
303
|
+
)
|
|
304
|
+
const position = {
|
|
305
|
+
index,
|
|
306
|
+
height: height,
|
|
307
|
+
top: bottom,
|
|
308
|
+
bottom: bottom + height
|
|
309
|
+
}
|
|
310
|
+
bottom = position.bottom
|
|
311
|
+
return position
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
if (this.containerHeight) {
|
|
315
|
+
this.calculateVisibleCounts()
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
calculateVisibleCounts() {
|
|
319
|
+
this.visibleCounts = this.positions.map((_, startIndex) => {
|
|
320
|
+
let count = 0
|
|
321
|
+
let totalHeight = 0
|
|
322
|
+
|
|
323
|
+
for (let i = startIndex; i < this.positions.length; i++) {
|
|
324
|
+
totalHeight += this.positions[i].height
|
|
325
|
+
if (totalHeight > this.containerHeight) {
|
|
326
|
+
break
|
|
327
|
+
}
|
|
328
|
+
count++
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 如果是最后几个项目,确保全部显示
|
|
332
|
+
if (startIndex + count > this.positions.length - 3) {
|
|
333
|
+
count = this.positions.length - startIndex
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return count
|
|
337
|
+
})
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
getStartIndex(scrollTop = 0) {
|
|
341
|
+
// 确保不会返回超出范围的索引
|
|
342
|
+
if (!this.positions.length) {
|
|
343
|
+
return 0
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 如果滚动位置为0,直接返回0
|
|
347
|
+
if (scrollTop <= 0) {
|
|
348
|
+
return 0
|
|
349
|
+
}
|
|
350
|
+
const index = this.binarySearch(this.positions, scrollTop)
|
|
351
|
+
return Math.max(0, Math.min(index, this._listData.length - 1))
|
|
352
|
+
},
|
|
353
|
+
binarySearch(list, value) {
|
|
354
|
+
if (!list.length) return 0
|
|
355
|
+
|
|
356
|
+
// 如果 scrollTop 超过了最后一个元素的底部
|
|
357
|
+
if (value >= list[list.length - 1].bottom) {
|
|
358
|
+
return list.length - 1
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let start = 0
|
|
362
|
+
let end = list.length - 1
|
|
363
|
+
|
|
364
|
+
while (start <= end) {
|
|
365
|
+
const midIndex = Math.floor((start + end) / 2)
|
|
366
|
+
const midValue = list[midIndex]
|
|
367
|
+
|
|
368
|
+
if (value >= midValue.top && value < midValue.bottom) {
|
|
369
|
+
return midIndex
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (value < midValue.top) {
|
|
373
|
+
end = midIndex - 1
|
|
374
|
+
} else {
|
|
375
|
+
start = midIndex + 1
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return Math.min(Math.max(0, start - 1), list.length - 1)
|
|
380
|
+
},
|
|
381
|
+
setStartOffset() {
|
|
382
|
+
if (!this.positions.length) return
|
|
383
|
+
if (this.start >= 1) {
|
|
384
|
+
// 确保 startIndex 不会超出范围
|
|
385
|
+
const startIndex = Math.min(
|
|
386
|
+
Math.max(0, this.start - this.aboveCount),
|
|
387
|
+
this.positions.length - 1
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
const offset = this.positions[startIndex].top
|
|
391
|
+
this.infiniteListStyle = `transform: translateY(${offset}px)`
|
|
392
|
+
} else {
|
|
393
|
+
this.infiniteListStyle = `transform: translateY(${this.positions[0].top}px)`
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
getItemHeight(item, index, key) {
|
|
397
|
+
const { value, getter } = this[key]
|
|
398
|
+
if (typeof getter === 'function') {
|
|
399
|
+
return getter(item, index) || 0
|
|
400
|
+
} else {
|
|
401
|
+
return value || 0
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
onScroll(e) {
|
|
405
|
+
const { scrollTop } = e.detail
|
|
406
|
+
const newStart = this.getStartIndex(scrollTop)
|
|
407
|
+
// 只有当start发生足够变化时才更新,避免滚动触发重渲染
|
|
408
|
+
if (Math.abs(newStart - this.end) >= Math.floor(this.belowCount / 2)) {
|
|
409
|
+
this.start = newStart
|
|
410
|
+
this.end = this.start + this.visibleCount
|
|
411
|
+
this.setStartOffset()
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this.triggerEvent('scroll', e)
|
|
415
|
+
},
|
|
416
|
+
onScrolltolower(e) {
|
|
417
|
+
this.triggerEvent('scrolltolower', e)
|
|
418
|
+
},
|
|
419
|
+
onRefresherrefresh(e) {
|
|
420
|
+
this.triggerEvent('refresherrefresh', e)
|
|
421
|
+
},
|
|
422
|
+
scrollToIndex({ index, viewPosition = 0 }) {
|
|
423
|
+
const isStickyHeader = this._listData[index].itemData?.isSectionHeader
|
|
424
|
+
let prevHeaderHeight = 0
|
|
425
|
+
// 如果不是sticky header 查找最近一个吸顶的 sticky header
|
|
426
|
+
if (!isStickyHeader && this.enableSticky) {
|
|
427
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
428
|
+
if (this._listData[i].itemData?.isSectionHeader) {
|
|
429
|
+
prevHeaderHeight = this.positions[i].height
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const itemTop = (this.positions[index]?.top || 0) - prevHeaderHeight
|
|
436
|
+
const itemHeight = this.positions[index]?.height || 0
|
|
437
|
+
const containerHeight = this.containerHeight
|
|
438
|
+
|
|
439
|
+
let targetTop = itemTop
|
|
440
|
+
if (viewPosition === 1) {
|
|
441
|
+
// 滚动到可视区底部
|
|
442
|
+
targetTop = itemTop - (containerHeight - itemHeight)
|
|
443
|
+
} else if (viewPosition === 0.5) {
|
|
444
|
+
// 滚动到可视区中央
|
|
445
|
+
targetTop = itemTop - (containerHeight - itemHeight) / 2
|
|
446
|
+
}
|
|
447
|
+
// 支付宝小程序不支持 animated 参数,也不支持 scrollTo 方法滚动
|
|
448
|
+
this.scrollTop = targetTop
|
|
449
|
+
|
|
450
|
+
setTimeout(() => {
|
|
451
|
+
this.refreshStickyHeader()
|
|
452
|
+
}, this.scrollAnimationDuration + 100)
|
|
453
|
+
},
|
|
454
|
+
refreshStickyHeader() {
|
|
455
|
+
if (!this.$refs.stickyHeader) return
|
|
456
|
+
|
|
457
|
+
this.$refs.stickyHeader.forEach((header) => {
|
|
458
|
+
if (header && header.refresh) {
|
|
459
|
+
header.refresh()
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
</script>
|
|
466
|
+
|
|
467
|
+
<style scoped>
|
|
468
|
+
.scroll-view-container {
|
|
469
|
+
position: relative;
|
|
470
|
+
}
|
|
471
|
+
.mpx-recycle-view {
|
|
472
|
+
position: relative;
|
|
473
|
+
overflow: hidden;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.content-wrapper {
|
|
477
|
+
position: relative;
|
|
478
|
+
width: 100%;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.infinite-list {
|
|
482
|
+
left: 0;
|
|
483
|
+
right: 0;
|
|
484
|
+
top: 0;
|
|
485
|
+
position: absolute;
|
|
486
|
+
will-change: transform;
|
|
487
|
+
-webkit-backface-visibility: hidden;
|
|
488
|
+
backface-visibility: hidden;
|
|
489
|
+
/* 支付宝环境需要设置允许事件穿透, 否则滑不动 */
|
|
490
|
+
/* @mpx-if (__mpx_mode__ === 'ali') */
|
|
491
|
+
pointer-events: none;
|
|
492
|
+
/* @mpx-endif */
|
|
493
|
+
}
|
|
494
|
+
.sticky-section {
|
|
495
|
+
position: absolute;
|
|
496
|
+
width: 100%;
|
|
497
|
+
height: 100%;
|
|
498
|
+
}
|
|
499
|
+
</style>
|
|
500
|
+
<script type="application/json">
|
|
501
|
+
{
|
|
502
|
+
"disableScroll": true,
|
|
503
|
+
"usingComponents": {
|
|
504
|
+
"sticky-header": "./mpx-sticky-header.mpx"
|
|
505
|
+
},
|
|
506
|
+
"componentGenerics": {
|
|
507
|
+
"recycle-item": {
|
|
508
|
+
"default": "../wx/mpx-recycle-item-default.mpx"
|
|
509
|
+
},
|
|
510
|
+
"section-header": {
|
|
511
|
+
"default": "../wx/mpx-section-header-default.mpx"
|
|
512
|
+
},
|
|
513
|
+
"list-header": {
|
|
514
|
+
"default": "../wx/mpx-list-header-default.mpx"
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
</script>
|