mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 17:18:40 +08:00
feat: ✨ 新增 pull-refresh 下拉刷新组件
This commit is contained in:
parent
cade06fe4a
commit
ddab83ac51
189
docs/component/pull-refresh.md
Normal file
189
docs/component/pull-refresh.md
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# PullRefresh 下拉刷新
|
||||||
|
|
||||||
|
用于提供下拉刷新的交互操作。
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
下拉刷新时会触发 `refresh` 事件,在事件的回调函数中可以进行同步或异步操作,操作完成后将 `v-model` 设置为 `false`,表示加载完成。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="loading" @refresh="onRefresh">
|
||||||
|
<view class="content">
|
||||||
|
<wd-cell v-for="item in list" :key="item" :title="`列表项 ${item}`" />
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const list = ref([1, 2, 3, 4, 5])
|
||||||
|
|
||||||
|
function onRefresh() {
|
||||||
|
setTimeout(() => {
|
||||||
|
list.value = [1, 2, 3, 4, 5, 6]
|
||||||
|
loading.value = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义文案
|
||||||
|
|
||||||
|
通过 `pulling-text`、`loosing-text`、`loading-text`、`success-text` 属性可以自定义不同状态下的文案。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh
|
||||||
|
v-model="loading"
|
||||||
|
pulling-text="用力拉..."
|
||||||
|
loosing-text="快松手..."
|
||||||
|
loading-text="拼命加载中..."
|
||||||
|
success-text="加载成功!"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
>
|
||||||
|
<view class="content">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 成功提示
|
||||||
|
|
||||||
|
通过 `success-text` 可以设置刷新成功后的提示文案,通过 `success-duration` 可以设置提示展示时长。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh
|
||||||
|
v-model="loading"
|
||||||
|
success-text="刷新成功"
|
||||||
|
:success-duration="1500"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
>
|
||||||
|
<view class="content">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义插槽
|
||||||
|
|
||||||
|
通过插槽可以自定义下拉刷新过程中的提示内容。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="loading" @refresh="onRefresh">
|
||||||
|
<template #pulling="{ distance }">
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-icon name="arrow-down" size="20" />
|
||||||
|
<text>下拉距离: {{ Math.round(distance) }}px</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #loosing="{ distance }">
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-icon name="arrow-up" size="20" />
|
||||||
|
<text>释放距离: {{ Math.round(distance) }}px</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-loading size="20" />
|
||||||
|
<text>正在刷新数据...</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #success>
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-icon name="check" size="20" color="#34d19d" />
|
||||||
|
<text>刷新完成</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view class="content">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 禁用状态
|
||||||
|
|
||||||
|
通过 `disabled` 属性可以禁用下拉刷新。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="loading" disabled @refresh="onRefresh">
|
||||||
|
<view class="content">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|
||||||
|
|------|------|------|-------|--------|---------|
|
||||||
|
| v-model | 是否处于加载中状态 | `boolean` | - | `false` | - |
|
||||||
|
| disabled | 是否禁用下拉刷新 | `boolean` | - | `false` | - |
|
||||||
|
| pulling-text | 下拉过程提示文案 | `string` | - | `'下拉即可刷新...'` | - |
|
||||||
|
| loosing-text | 释放过程提示文案 | `string` | - | `'释放即可刷新...'` | - |
|
||||||
|
| loading-text | 加载过程提示文案 | `string` | - | `'加载中...'` | - |
|
||||||
|
| success-text | 刷新成功提示文案 | `string` | - | `''` | - |
|
||||||
|
| success-duration | 刷新成功提示展示时长(ms) | `number \| string` | - | `500` | - |
|
||||||
|
| animation-duration | 动画时长(ms) | `number \| string` | - | `300` | - |
|
||||||
|
| head-height | 顶部内容高度 | `number \| string` | - | `50` | - |
|
||||||
|
| pull-distance | 触发下拉刷新的距离 | `number \| string` | - | 与 `head-height` 相同 | - |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| 事件名 | 说明 | 参数 | 最低版本 |
|
||||||
|
|--------|------|------|---------|
|
||||||
|
| refresh | 下拉刷新时触发 | - | - |
|
||||||
|
| change | 拖拽时或状态改变时触发 | `{ status: PullRefreshStatus, distance: number }` | - |
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
| 名称 | 说明 | 参数 | 最低版本 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| default | 内容区 | - | - |
|
||||||
|
| normal | 非下拉状态时顶部内容 | - | - |
|
||||||
|
| pulling | 下拉过程中顶部内容 | `{ distance: number }` | - |
|
||||||
|
| loosing | 释放过程中顶部内容 | `{ distance: number }` | - |
|
||||||
|
| loading | 加载过程中顶部内容 | `{ distance: number }` | - |
|
||||||
|
| success | 刷新成功时顶部内容 | - | - |
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
通过 ref 可以获取到 PullRefresh 实例并调用实例方法。
|
||||||
|
|
||||||
|
| 方法名 | 说明 | 参数 | 返回值 | 最低版本 |
|
||||||
|
|--------|------|------|--------|---------|
|
||||||
|
| finish | 结束刷新状态 | - | - | - |
|
||||||
|
|
||||||
|
### 类型定义
|
||||||
|
|
||||||
|
组件导出以下类型定义:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { PullRefreshProps, PullRefreshStatus } from '@/uni_modules/wot-design-uni'
|
||||||
|
|
||||||
|
type PullRefreshStatus = 'normal' | 'pulling' | 'loosing' | 'loading' | 'success'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 在某些情况下拖拽不生效?
|
||||||
|
|
||||||
|
请检查是否在页面滚动容器上设置了 `overflow: hidden` 或其他影响滚动的样式。
|
||||||
|
|
||||||
|
### 如何实现上拉加载更多?
|
||||||
|
|
||||||
|
可以结合 `wd-loadmore` 组件实现上拉加载更多功能。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||||
|
<view class="content">
|
||||||
|
<wd-cell v-for="item in list" :key="item" :title="item" />
|
||||||
|
</view>
|
||||||
|
<wd-loadmore v-model="loading" @loadmore="onLoadmore" />
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
189
docs/en-US/component/pull-refresh.md
Normal file
189
docs/en-US/component/pull-refresh.md
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# PullRefresh
|
||||||
|
|
||||||
|
Provides pull-to-refresh interaction.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
The `refresh` event will be triggered when pull-to-refresh, you can perform synchronous or asynchronous operations in the event callback function. After the operation is completed, set `v-model` to `false` to indicate that the loading is complete.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="loading" @refresh="onRefresh">
|
||||||
|
<view class="content">
|
||||||
|
<wd-cell v-for="item in list" :key="item" :title="`List item ${item}`" />
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const list = ref([1, 2, 3, 4, 5])
|
||||||
|
|
||||||
|
function onRefresh() {
|
||||||
|
setTimeout(() => {
|
||||||
|
list.value = [1, 2, 3, 4, 5, 6]
|
||||||
|
loading.value = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Text
|
||||||
|
|
||||||
|
You can customize the text in different states through `pulling-text`, `loosing-text`, `loading-text`, and `success-text` properties.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh
|
||||||
|
v-model="loading"
|
||||||
|
pulling-text="Pull hard..."
|
||||||
|
loosing-text="Release quickly..."
|
||||||
|
loading-text="Loading desperately..."
|
||||||
|
success-text="Load successfully!"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
>
|
||||||
|
<view class="content">
|
||||||
|
<!-- Content -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Tip
|
||||||
|
|
||||||
|
You can set the prompt text after successful refresh through `success-text`, and set the prompt display duration through `success-duration`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh
|
||||||
|
v-model="loading"
|
||||||
|
success-text="Refresh successful"
|
||||||
|
:success-duration="1500"
|
||||||
|
@refresh="onRefresh"
|
||||||
|
>
|
||||||
|
<view class="content">
|
||||||
|
<!-- Content -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Slots
|
||||||
|
|
||||||
|
You can customize the prompt content during the pull-to-refresh process through slots.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="loading" @refresh="onRefresh">
|
||||||
|
<template #pulling="{ distance }">
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-icon name="arrow-down" size="20" />
|
||||||
|
<text>Pull distance: {{ Math.round(distance) }}px</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #loosing="{ distance }">
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-icon name="arrow-up" size="20" />
|
||||||
|
<text>Release distance: {{ Math.round(distance) }}px</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #loading>
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-loading size="20" />
|
||||||
|
<text>Refreshing data...</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #success>
|
||||||
|
<view class="custom-slot">
|
||||||
|
<wd-icon name="check" size="20" color="#34d19d" />
|
||||||
|
<text>Refresh completed</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view class="content">
|
||||||
|
<!-- Content -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disabled State
|
||||||
|
|
||||||
|
You can disable pull-to-refresh through the `disabled` property.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="loading" disabled @refresh="onRefresh">
|
||||||
|
<view class="content">
|
||||||
|
<!-- Content -->
|
||||||
|
</view>
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| Parameter | Description | Type | Optional Values | Default Value | Minimum Version |
|
||||||
|
|-----------|-------------|------|----------------|---------------|----------------|
|
||||||
|
| v-model | Whether it is in loading state | `boolean` | - | `false` | - |
|
||||||
|
| disabled | Whether to disable pull-to-refresh | `boolean` | - | `false` | - |
|
||||||
|
| pulling-text | Text during pulling process | `string` | - | `'Pull to refresh...'` | - |
|
||||||
|
| loosing-text | Text during releasing process | `string` | - | `'Release to refresh...'` | - |
|
||||||
|
| loading-text | Text during loading process | `string` | - | `'Loading...'` | - |
|
||||||
|
| success-text | Text for successful refresh | `string` | - | `''` | - |
|
||||||
|
| success-duration | Display duration of success tip (ms) | `number \| string` | - | `500` | - |
|
||||||
|
| animation-duration | Animation duration (ms) | `number \| string` | - | `300` | - |
|
||||||
|
| head-height | Height of top content | `number \| string` | - | `50` | - |
|
||||||
|
| pull-distance | Distance to trigger pull-to-refresh | `number \| string` | - | Same as `head-height` | - |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Event Name | Description | Parameters | Minimum Version |
|
||||||
|
|------------|-------------|------------|----------------|
|
||||||
|
| refresh | Triggered when pull-to-refresh | - | - |
|
||||||
|
| change | Triggered when dragging or status changes | `{ status: PullRefreshStatus, distance: number }` | - |
|
||||||
|
|
||||||
|
### Slots
|
||||||
|
|
||||||
|
| Name | Description | Parameters | Minimum Version |
|
||||||
|
|------|-------------|------------|----------------|
|
||||||
|
| default | Content area | - | - |
|
||||||
|
| normal | Top content when not pulling | - | - |
|
||||||
|
| pulling | Top content during pulling | `{ distance: number }` | - |
|
||||||
|
| loosing | Top content during releasing | `{ distance: number }` | - |
|
||||||
|
| loading | Top content during loading | `{ distance: number }` | - |
|
||||||
|
| success | Top content when refresh succeeds | - | - |
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
You can get the PullRefresh instance through ref and call instance methods.
|
||||||
|
|
||||||
|
| Method Name | Description | Parameters | Return Value | Minimum Version |
|
||||||
|
|-------------|-------------|------------|--------------|----------------|
|
||||||
|
| finish | End refresh state | - | - | - |
|
||||||
|
|
||||||
|
### Type Definitions
|
||||||
|
|
||||||
|
The component exports the following type definitions:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { PullRefreshProps, PullRefreshStatus } from '@/uni_modules/wot-design-uni'
|
||||||
|
|
||||||
|
type PullRefreshStatus = 'normal' | 'pulling' | 'loosing' | 'loading' | 'success'
|
||||||
|
```
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Dragging doesn't work in some cases?
|
||||||
|
|
||||||
|
Please check if `overflow: hidden` or other styles that affect scrolling are set on the page scroll container.
|
||||||
|
|
||||||
|
### How to implement pull-up to load more?
|
||||||
|
|
||||||
|
You can combine with the `wd-loadmore` component to implement pull-up to load more functionality.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<wd-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||||
|
<view class="content">
|
||||||
|
<wd-cell v-for="item in list" :key="item" :title="item" />
|
||||||
|
</view>
|
||||||
|
<wd-loadmore v-model="loading" @loadmore="onLoadmore" />
|
||||||
|
</wd-pull-refresh>
|
||||||
|
```
|
||||||
@ -864,6 +864,7 @@
|
|||||||
"progress-title": "Progress",
|
"progress-title": "Progress",
|
||||||
"pu-tong-an-niu": "Normal button",
|
"pu-tong-an-niu": "Normal button",
|
||||||
"pu-tong-shu-zhi": "Ordinary",
|
"pu-tong-shu-zhi": "Ordinary",
|
||||||
|
"pullrefresh-xia-la-shua-xin": "",
|
||||||
"q1-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo": "Q1: The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales.",
|
"q1-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo": "Q1: The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales. The seven day no reason return and exchange policy allows customers to accept returns and exchanges for all products within 7 days (based on the delivery receipt) without affecting secondary sales.",
|
||||||
"q1-you-hui-quan-shi-yong-xiang-qing-xiang-qing-ye-mian-wo-de-wo-de-you-hui-you-hui-quan-gui-ze-shuo-ming": "Q1: What are the details of coupon usage? Details page 【 My 】 - 【 My Discounts 】 - 【 Coupon Rules Explanation 】.",
|
"q1-you-hui-quan-shi-yong-xiang-qing-xiang-qing-ye-mian-wo-de-wo-de-you-hui-you-hui-quan-gui-ze-shuo-ming": "Q1: What are the details of coupon usage? Details page 【 My 】 - 【 My Discounts 】 - 【 Coupon Rules Explanation 】.",
|
||||||
"qi-ta-xin-xi": "Other information",
|
"qi-ta-xin-xi": "Other information",
|
||||||
|
|||||||
@ -864,6 +864,7 @@
|
|||||||
"progress-title": "Progress 进度条",
|
"progress-title": "Progress 进度条",
|
||||||
"pu-tong-an-niu": "普通按钮",
|
"pu-tong-an-niu": "普通按钮",
|
||||||
"pu-tong-shu-zhi": "普通数值",
|
"pu-tong-shu-zhi": "普通数值",
|
||||||
|
"pullrefresh-xia-la-shua-xin": "Pullrefresh 下拉刷新",
|
||||||
"q1-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo": "Q1:七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。",
|
"q1-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo-qi-tian-wu-li-you-tui-huan-huo-zhi-du-suo-you-shang-pin-zai-bu-ying-xiang-er-ci-xiao-shou-de-qing-kuang-xia-7-tian-nei-yi-kuai-di-dan-qian-shou-wei-zhun-jun-jie-shou-ke-hu-tui-huan-huo": "Q1:七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。七天无理由退换货制度,所有商品在不影响二次销售的情况下7天内(以快递单签收为准)均接受客户退换货。",
|
||||||
"q1-you-hui-quan-shi-yong-xiang-qing-xiang-qing-ye-mian-wo-de-wo-de-you-hui-you-hui-quan-gui-ze-shuo-ming": "Q1:优惠券使用详情?详情页面【我的】-【我的优惠】-【优惠券规则说明】。",
|
"q1-you-hui-quan-shi-yong-xiang-qing-xiang-qing-ye-mian-wo-de-wo-de-you-hui-you-hui-quan-gui-ze-shuo-ming": "Q1:优惠券使用详情?详情页面【我的】-【我的优惠】-【优惠券规则说明】。",
|
||||||
"qi-ta-xin-xi": "其他信息",
|
"qi-ta-xin-xi": "其他信息",
|
||||||
|
|||||||
@ -1326,6 +1326,21 @@
|
|||||||
"navigationBarTitleText": "%rootportal-title%"
|
"navigationBarTitleText": "%rootportal-title%"
|
||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pullRefresh/Index",
|
||||||
|
"name": "pullRefresh",
|
||||||
|
"style": {
|
||||||
|
"mp-alipay": {
|
||||||
|
"allowsBounceVertical": "NO"
|
||||||
|
},
|
||||||
|
// #ifdef MP
|
||||||
|
"navigationBarTitleText": "PullRefresh 下拉刷新",
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP
|
||||||
|
"navigationBarTitleText": "%pullrefresh-title%"
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -319,6 +319,10 @@ const list = computed(() => [
|
|||||||
{
|
{
|
||||||
id: 'numberKeyboard',
|
id: 'numberKeyboard',
|
||||||
name: t('numberkeyboard-shu-zi-jian-pan')
|
name: t('numberkeyboard-shu-zi-jian-pan')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pullRefresh',
|
||||||
|
name: t('pullrefresh-xia-la-shua-xin')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
100
src/subPages/pullRefresh/Index.vue
Normal file
100
src/subPages/pullRefresh/Index.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<wd-tabs v-model="activeTab" @change="handleTabChange" animated sticky swipeable>
|
||||||
|
<wd-tab title="基础用法">
|
||||||
|
<wd-pull-refresh v-model="loading1" @refresh="onRefresh1">
|
||||||
|
<wd-cell v-for="item in list1" :key="item" :title="`列表项 ${item}`" />
|
||||||
|
</wd-pull-refresh>
|
||||||
|
</wd-tab>
|
||||||
|
|
||||||
|
<wd-tab title="局部滚动">
|
||||||
|
<wd-pull-refresh v-model="loading7" scroll-mode="scroll-view" :height="300" @refresh="onRefresh7">
|
||||||
|
<wd-cell title="scroll-view 滚动模式" label="固定高度 300px,支持局部滚动" />
|
||||||
|
<wd-cell v-for="item in list7" :key="item" :title="`scroll-view 滚动 ${item}`" />
|
||||||
|
<wd-cell v-for="item in 15" :key="`extra-${item}`" :title="`额外内容 ${item}`" label="用于测试滚动效果" />
|
||||||
|
</wd-pull-refresh>
|
||||||
|
</wd-tab>
|
||||||
|
|
||||||
|
<wd-tab title="成功提示">
|
||||||
|
<wd-pull-refresh v-model="loading3" success-text="刷新成功" :success-duration="1500" @refresh="onRefresh3">
|
||||||
|
<wd-cell v-for="item in list3" :key="item" :title="`成功提示 ${item}`" />
|
||||||
|
</wd-pull-refresh>
|
||||||
|
</wd-tab>
|
||||||
|
|
||||||
|
<wd-tab title="自定义提示">
|
||||||
|
<wd-pull-refresh
|
||||||
|
v-model="loading2"
|
||||||
|
pulling-text="用力拉..."
|
||||||
|
loosing-text="快松手..."
|
||||||
|
loading-text="拼命加载中..."
|
||||||
|
success-text="加载成功!"
|
||||||
|
@refresh="onRefresh2"
|
||||||
|
>
|
||||||
|
<wd-cell v-for="item in list2" :key="item" :title="`自定义文案 ${item}`" />
|
||||||
|
</wd-pull-refresh>
|
||||||
|
</wd-tab>
|
||||||
|
</wd-tabs>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
// Tab 相关
|
||||||
|
const activeTab = ref(0)
|
||||||
|
const refreshCount = ref(1)
|
||||||
|
|
||||||
|
// Loading 状态
|
||||||
|
const loading1 = ref(false)
|
||||||
|
const loading2 = ref(false)
|
||||||
|
const loading3 = ref(false)
|
||||||
|
const loading7 = ref(false)
|
||||||
|
const loading8 = ref(false)
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const list1 = ref(Array.from({ length: 20 }, (_, i) => i + 1))
|
||||||
|
const list2 = ref(Array.from({ length: 20 }, (_, i) => i + 1))
|
||||||
|
const list3 = ref(Array.from({ length: 20 }, (_, i) => i + 1))
|
||||||
|
const list7 = ref(Array.from({ length: 20 }, (_, i) => i + 1))
|
||||||
|
const list8 = ref(Array.from({ length: 20 }, (_, i) => i + 1))
|
||||||
|
|
||||||
|
// Tab 切换事件
|
||||||
|
function handleTabChange(index: number) {
|
||||||
|
console.log('切换到tab:', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新事件处理
|
||||||
|
function onRefresh1() {
|
||||||
|
setTimeout(() => {
|
||||||
|
list1.value = Array.from({ length: 20 }, (_, i) => i + 1)
|
||||||
|
loading1.value = false
|
||||||
|
refreshCount.value++
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh2() {
|
||||||
|
setTimeout(() => {
|
||||||
|
list2.value = Array.from({ length: 20 }, (_, i) => i + 1)
|
||||||
|
loading2.value = false
|
||||||
|
refreshCount.value++
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh3() {
|
||||||
|
setTimeout(() => {
|
||||||
|
list3.value = Array.from({ length: 20 }, (_, i) => i + 1)
|
||||||
|
loading3.value = false
|
||||||
|
refreshCount.value++
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRefresh7() {
|
||||||
|
setTimeout(() => {
|
||||||
|
list7.value = [1, 2, 3, 4, 5, 6]
|
||||||
|
loading7.value = false
|
||||||
|
refreshCount.value++
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@ -658,7 +658,14 @@ $-toast-loading-margin-bottom: var(--wot-toast-loading-margin-bottom, 16px) !def
|
|||||||
$-toast-box-shadow: var(--wot-toast-box-shadow, 0px 6px 16px 0px rgba(0, 0, 0, 0.08)) !default; // 外部阴影
|
$-toast-box-shadow: var(--wot-toast-box-shadow, 0px 6px 16px 0px rgba(0, 0, 0, 0.08)) !default; // 外部阴影
|
||||||
|
|
||||||
/* loading */
|
/* loading */
|
||||||
$-loading-size: var(--wot-loading-size, 32px) !default; // loading 大小
|
$-loading-size: var(--wot-loading-size, 30px) !default; // loading图标大小
|
||||||
|
|
||||||
|
/* pull-refresh */
|
||||||
|
$-pull-refresh-head-height: var(--wot-pull-refresh-head-height, 50px) !default; // 头部高度
|
||||||
|
$-pull-refresh-head-font-size: var(--wot-pull-refresh-head-font-size, 14px) !default; // 头部字体大小
|
||||||
|
$-pull-refresh-head-color: var(--wot-pull-refresh-head-color, rgba(0, 0, 0, 0.6)) !default; // 头部字体颜色
|
||||||
|
$-pull-refresh-loading-size: var(--wot-pull-refresh-loading-size, 20px) !default; // 加载图标大小
|
||||||
|
$-pull-refresh-animation-duration: var(--wot-pull-refresh-animation-duration, 300ms) !default; // 动画持续时间 大小
|
||||||
|
|
||||||
/* tooltip */
|
/* tooltip */
|
||||||
$-tooltip-bg: var(--wot-tooltip-bg, rgba(38, 39, 40, 0.8)) !default; // 背景色
|
$-tooltip-bg: var(--wot-tooltip-bg, rgba(38, 39, 40, 0.8)) !default; // 背景色
|
||||||
@ -970,5 +977,16 @@ $-signature-border: var(--wot-signature-border, 1px solid $-color-gray-5) !defau
|
|||||||
$-signature-footer-margin-top: var(--wot-signature-footer-margin-top, 8px) !default; // 底部按钮上边距
|
$-signature-footer-margin-top: var(--wot-signature-footer-margin-top, 8px) !default; // 底部按钮上边距
|
||||||
$-signature-button-margin-left: var(--wot-signature-button-margin-left, 8px) !default; // 底部按钮左边距
|
$-signature-button-margin-left: var(--wot-signature-button-margin-left, 8px) !default; // 底部按钮左边距
|
||||||
|
|
||||||
|
/* pull-refresh */
|
||||||
|
$-pull-refresh-head-height: var(--wot-pull-refresh-head-height, 50px) !default; // 头部高度
|
||||||
|
$-pull-refresh-head-bg: var(--wot-pull-refresh-head-bg, $-color-white) !default; // 头部背景色
|
||||||
|
$-pull-refresh-content-bg: var(--wot-pull-refresh-content-bg, $-color-white) !default; // 内容背景色
|
||||||
|
$-pull-refresh-text-fs: var(--wot-pull-refresh-text-fs, $-fs-content) !default; // 文字字体大小
|
||||||
|
$-pull-refresh-text-color: var(--wot-pull-refresh-text-color, $-color-secondary) !default; // 文字颜色
|
||||||
|
$-pull-refresh-text-line-height: var(--wot-pull-refresh-text-line-height, 1.5) !default; // 文字行高
|
||||||
|
$-pull-refresh-loading-margin: var(--wot-pull-refresh-loading-margin, 8px) !default; // 加载状态文字左边距
|
||||||
|
$-pull-refresh-loading-size: var(--wot-pull-refresh-loading-size, 16px) !default; // 加载图标大小
|
||||||
|
$-pull-refresh-animation-duration: var(--wot-pull-refresh-animation-duration, 300ms) !default; // 动画持续时间
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
@import "../common/abstracts/_mixin.scss";
|
||||||
|
@import "../common/abstracts/variable.scss";
|
||||||
|
|
||||||
|
@include b(pull-refresh) {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
@include e(head) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: $-pull-refresh-head-height;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: $-color-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(content) {
|
||||||
|
position: relative;
|
||||||
|
background-color: $-pull-refresh-content-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(text) {
|
||||||
|
font-size: $-pull-refresh-text-fs;
|
||||||
|
color: $-pull-refresh-text-color;
|
||||||
|
line-height: $-pull-refresh-text-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(loading) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.wd-pull-refresh__text {
|
||||||
|
margin-left: $-pull-refresh-loading-margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Solo Coding
|
||||||
|
* @Date: 2024-12-19
|
||||||
|
* @LastEditTime: 2024-12-19
|
||||||
|
* @LastEditors: Solo Coding
|
||||||
|
* @Description: PullRefresh 下拉刷新组件类型定义
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-pull-refresh/types.ts
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeStringProp, makeNumericProp } from '../common/props'
|
||||||
|
|
||||||
|
export type PullRefreshStatus = 'normal' | 'pulling' | 'loosing' | 'loading' | 'success'
|
||||||
|
|
||||||
|
export type ScrollMode = 'scroll-view' | 'page'
|
||||||
|
|
||||||
|
export const pullRefreshProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 是否处于加载中状态
|
||||||
|
*/
|
||||||
|
modelValue: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否禁用下拉刷新
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 下拉过程提示文案
|
||||||
|
*/
|
||||||
|
pullingText: makeStringProp('下拉即可刷新...'),
|
||||||
|
/**
|
||||||
|
* 释放过程提示文案
|
||||||
|
*/
|
||||||
|
loosingText: makeStringProp('释放即可刷新...'),
|
||||||
|
/**
|
||||||
|
* 加载过程提示文案
|
||||||
|
*/
|
||||||
|
loadingText: makeStringProp('加载中...'),
|
||||||
|
/**
|
||||||
|
* 刷新成功提示文案
|
||||||
|
*/
|
||||||
|
successText: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 刷新成功提示展示时长(ms)
|
||||||
|
*/
|
||||||
|
successDuration: makeNumericProp(300),
|
||||||
|
/**
|
||||||
|
* 动画时长(ms)
|
||||||
|
*/
|
||||||
|
animationDuration: makeNumericProp(300),
|
||||||
|
/**
|
||||||
|
* 顶部内容高度
|
||||||
|
*/
|
||||||
|
headHeight: makeNumericProp(50),
|
||||||
|
/**
|
||||||
|
* 触发下拉刷新的距离
|
||||||
|
*/
|
||||||
|
pullDistance: makeNumericProp(''),
|
||||||
|
/**
|
||||||
|
* 滚动模式:scroll-view(局部滚动) | page(页面滚动)
|
||||||
|
*/
|
||||||
|
scrollMode: makeStringProp<ScrollMode>('page'),
|
||||||
|
/**
|
||||||
|
* scroll-view 模式下的固定高度
|
||||||
|
*/
|
||||||
|
height: makeNumericProp(400)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PullRefreshProps = ExtractPropTypes<typeof pullRefreshProps>
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-pull-refresh ${props.customClass}`" :style="props.customStyle">
|
||||||
|
<!-- 统一的下拉头部区域 -->
|
||||||
|
<view class="wd-pull-refresh__head" :style="headStyle">
|
||||||
|
<slot v-if="status === 'normal'" name="normal"></slot>
|
||||||
|
|
||||||
|
<slot v-else-if="status === 'pulling'" name="pulling" :distance="distance">
|
||||||
|
<view class="wd-pull-refresh__text">{{ pullingText }}</view>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<slot v-else-if="status === 'loosing'" name="loosing" :distance="distance">
|
||||||
|
<view class="wd-pull-refresh__text">{{ loosingText }}</view>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<slot v-else-if="status === 'loading'" name="loading" :distance="distance">
|
||||||
|
<view class="wd-pull-refresh__loading">
|
||||||
|
<wd-loading :size="loadingSize" />
|
||||||
|
<view v-if="loadingText" class="wd-pull-refresh__text">{{ loadingText }}</view>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<slot v-else-if="status === 'success'" name="success">
|
||||||
|
<view class="wd-pull-refresh__text">{{ successText }}</view>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 根据滚动模式渲染不同的内容区域 -->
|
||||||
|
<!-- scroll-view 局部滚动模式 -->
|
||||||
|
<scroll-view
|
||||||
|
v-if="scrollMode === 'scroll-view'"
|
||||||
|
class="wd-pull-refresh__content"
|
||||||
|
:style="contentStyle"
|
||||||
|
:scroll-y="true"
|
||||||
|
:scroll-top="scrollTop"
|
||||||
|
@scroll="handleScrollViewScroll"
|
||||||
|
@touchstart="handleTouchStart"
|
||||||
|
@touchmove="handleTouchMove"
|
||||||
|
@touchend="handleTouchEnd"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 页面全局滚动模式 -->
|
||||||
|
<view
|
||||||
|
v-else
|
||||||
|
class="wd-pull-refresh__content"
|
||||||
|
:style="contentStyle"
|
||||||
|
@touchstart="handleTouchStart"
|
||||||
|
@touchmove="handleTouchMove"
|
||||||
|
@touchend="handleTouchEnd"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch, type CSSProperties } from 'vue'
|
||||||
|
import { defineOptions } from 'vue'
|
||||||
|
import { onPageScroll } from '@dcloudio/uni-app'
|
||||||
|
import { useTouch } from '../composables/useTouch'
|
||||||
|
import { addUnit } from '../common/util'
|
||||||
|
import { pullRefreshProps, type PullRefreshStatus } from './types'
|
||||||
|
import WdLoading from '../wd-loading/wd-loading.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'wd-pull-refresh',
|
||||||
|
options: {
|
||||||
|
virtualHost: true,
|
||||||
|
addGlobalClass: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps(pullRefreshProps)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
refresh: []
|
||||||
|
change: [payload: { status: PullRefreshStatus; distance: number }]
|
||||||
|
'update:modelValue': [value: boolean]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const status = ref<PullRefreshStatus>('normal')
|
||||||
|
const distance = ref<number>(0)
|
||||||
|
const duration = ref<number>(0)
|
||||||
|
const isRefreshing = ref<boolean>(false)
|
||||||
|
const scrollTop = ref<number>(0)
|
||||||
|
const pageScrollTop = ref<number>(0)
|
||||||
|
|
||||||
|
const touch = useTouch()
|
||||||
|
|
||||||
|
// scroll-view 滚动事件处理(用于 scroll-view 模式)
|
||||||
|
function handleScrollViewScroll(event: any) {
|
||||||
|
if (props.scrollMode === 'scroll-view') {
|
||||||
|
scrollTop.value = event.detail.scrollTop || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 uni-app 的 onPageScroll 钩子监听页面滚动(用于 page 模式)
|
||||||
|
onPageScroll((event) => {
|
||||||
|
if (props.scrollMode === 'page') {
|
||||||
|
const scrollTop = event.scrollTop || 0
|
||||||
|
pageScrollTop.value = scrollTop
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const headHeight = computed(() => addUnit(props.headHeight))
|
||||||
|
const pullDistance = computed(() => {
|
||||||
|
return props.pullDistance ? Number(props.pullDistance) : Number(props.headHeight)
|
||||||
|
})
|
||||||
|
const loadingSize = computed(() => {
|
||||||
|
return Math.min(Number(props.headHeight) * 0.4, 20)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 头部样式
|
||||||
|
const headStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {
|
||||||
|
height: headHeight.value,
|
||||||
|
transform: `translateY(${distance.value - Number(props.headHeight)}px)`,
|
||||||
|
transition: duration.value ? `transform ${duration.value}ms ease-out` : 'none'
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
})
|
||||||
|
|
||||||
|
// 内容区域样式
|
||||||
|
const contentStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {
|
||||||
|
transform: `translateY(${distance.value}px)`,
|
||||||
|
transition: duration.value ? `transform ${duration.value}ms ease-out` : 'none'
|
||||||
|
}
|
||||||
|
if (props.scrollMode === 'scroll-view') {
|
||||||
|
style.height = addUnit(props.height)
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听 v-model 变化
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal && isRefreshing.value) {
|
||||||
|
// 刷新完成,立即开始回弹动画
|
||||||
|
duration.value = Number(props.animationDuration)
|
||||||
|
|
||||||
|
if (props.successText) {
|
||||||
|
// 如果有成功文案,先显示成功状态
|
||||||
|
setStatus('success')
|
||||||
|
setTimeout(() => {
|
||||||
|
resetPosition()
|
||||||
|
isRefreshing.value = false
|
||||||
|
}, Number(props.successDuration))
|
||||||
|
} else {
|
||||||
|
// 没有成功文案,直接回弹
|
||||||
|
resetPosition()
|
||||||
|
isRefreshing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 触摸事件处理
|
||||||
|
function handleTouchStart(event: TouchEvent) {
|
||||||
|
if (props.disabled || isRefreshing.value) return
|
||||||
|
|
||||||
|
// 检查是否在顶部
|
||||||
|
const currentScrollTop = getCurrentScrollTop()
|
||||||
|
if (currentScrollTop > 0) return
|
||||||
|
|
||||||
|
touch.touchStart(event)
|
||||||
|
duration.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMove(event: TouchEvent) {
|
||||||
|
if (props.disabled || isRefreshing.value) return
|
||||||
|
|
||||||
|
touch.touchMove(event)
|
||||||
|
|
||||||
|
// 只处理向下滑动
|
||||||
|
if (touch.deltaY.value <= 0 || touch.direction.value !== 'vertical') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否在顶部
|
||||||
|
const currentScrollTop = getCurrentScrollTop()
|
||||||
|
if (currentScrollTop > 0) return
|
||||||
|
|
||||||
|
// 阻止默认滚动
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
// 计算下拉距离,添加阻尼效果
|
||||||
|
const damping = 0.5
|
||||||
|
distance.value = Math.max(0, touch.deltaY.value * damping)
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
if (distance.value >= pullDistance.value) {
|
||||||
|
setStatus('loosing')
|
||||||
|
} else if (distance.value > 0) {
|
||||||
|
setStatus('pulling')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchEnd() {
|
||||||
|
if (props.disabled || isRefreshing.value) return
|
||||||
|
|
||||||
|
duration.value = Number(props.animationDuration)
|
||||||
|
|
||||||
|
if (status.value === 'loosing') {
|
||||||
|
// 触发刷新
|
||||||
|
distance.value = Number(props.headHeight)
|
||||||
|
setStatus('loading')
|
||||||
|
isRefreshing.value = true
|
||||||
|
emit('update:modelValue', true)
|
||||||
|
emit('refresh')
|
||||||
|
} else {
|
||||||
|
// 重置位置
|
||||||
|
resetPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置状态
|
||||||
|
function setStatus(newStatus: PullRefreshStatus) {
|
||||||
|
if (status.value !== newStatus) {
|
||||||
|
status.value = newStatus
|
||||||
|
emit('change', { status: newStatus, distance: distance.value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置位置
|
||||||
|
function resetPosition() {
|
||||||
|
// 设置动画时长
|
||||||
|
duration.value = Number(props.animationDuration)
|
||||||
|
distance.value = 0
|
||||||
|
setStatus('normal')
|
||||||
|
|
||||||
|
// 动画完成后清除duration
|
||||||
|
setTimeout(() => {
|
||||||
|
duration.value = 0
|
||||||
|
}, Number(props.animationDuration))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前滚动位置
|
||||||
|
function getCurrentScrollTop(): number {
|
||||||
|
try {
|
||||||
|
if (props.scrollMode === 'scroll-view') {
|
||||||
|
// scroll-view 模式:使用 scroll-view 的 scrollTop
|
||||||
|
return scrollTop.value
|
||||||
|
} else {
|
||||||
|
// page 模式:使用页面滚动位置
|
||||||
|
return pageScrollTop.value
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成刷新
|
||||||
|
function finish() {
|
||||||
|
if (isRefreshing.value) {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件使用
|
||||||
|
defineExpose({
|
||||||
|
finish,
|
||||||
|
scrollMode: computed(() => props.scrollMode)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
2
src/uni_modules/wot-design-uni/global.d.ts
vendored
2
src/uni_modules/wot-design-uni/global.d.ts
vendored
@ -40,6 +40,7 @@ declare module 'vue' {
|
|||||||
WdPopover: typeof import('./components/wd-popover/wd-popover.vue')['default']
|
WdPopover: typeof import('./components/wd-popover/wd-popover.vue')['default']
|
||||||
WdPopup: typeof import('./components/wd-popup/wd-popup.vue')['default']
|
WdPopup: typeof import('./components/wd-popup/wd-popup.vue')['default']
|
||||||
WdProgress: typeof import('./components/wd-progress/wd-progress.vue')['default']
|
WdProgress: typeof import('./components/wd-progress/wd-progress.vue')['default']
|
||||||
|
WdPullRefresh: typeof import('./components/wd-pull-refresh/wd-pull-refresh.vue')['default']
|
||||||
WdRadio: typeof import('./components/wd-radio/wd-radio.vue')['default']
|
WdRadio: typeof import('./components/wd-radio/wd-radio.vue')['default']
|
||||||
WdRadioGroup: typeof import('./components/wd-radio-group/wd-radio-group.vue')['default']
|
WdRadioGroup: typeof import('./components/wd-radio-group/wd-radio-group.vue')['default']
|
||||||
WdRate: typeof import('./components/wd-rate/wd-rate.vue')['default']
|
WdRate: typeof import('./components/wd-rate/wd-rate.vue')['default']
|
||||||
@ -95,6 +96,7 @@ declare module 'vue' {
|
|||||||
WdFloatingPanel: typeof import('./components/wd-floating-panel/wd-floating-panel.vue')['default']
|
WdFloatingPanel: typeof import('./components/wd-floating-panel/wd-floating-panel.vue')['default']
|
||||||
WdSignature: typeof import('./components/wd-signature/wd-signature.vue')['default']
|
WdSignature: typeof import('./components/wd-signature/wd-signature.vue')['default']
|
||||||
WdRootPortal: typeof import('./components/wd-root-portal/wd-root-portal.vue')['default']
|
WdRootPortal: typeof import('./components/wd-root-portal/wd-root-portal.vue')['default']
|
||||||
|
WdPullRefresh: typeof import('./components/wd-pull-refresh/wd-pull-refresh.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
121
tests/components/wd-pull-refresh.test.ts
Normal file
121
tests/components/wd-pull-refresh.test.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import WdPullRefresh from '@/uni_modules/wot-design-uni/components/wd-pull-refresh/wd-pull-refresh.vue'
|
||||||
|
import { describe, expect, test, vi } from 'vitest'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
|
||||||
|
describe('WdPullRefresh', () => {
|
||||||
|
test('should render correctly', () => {
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('.wd-pull-refresh').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('.wd-pull-refresh__content').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('.wd-pull-refresh__head').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should support scroll mode prop', async () => {
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false,
|
||||||
|
scrollMode: 'page'
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(wrapper.vm.scrollMode).toBe('page')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle page scroll correctly', async () => {
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false,
|
||||||
|
scrollMode: 'page'
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// 验证组件正常渲染
|
||||||
|
expect(wrapper.vm.scrollMode).toBe('page')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should render scroll-view mode correctly', async () => {
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false,
|
||||||
|
scrollMode: 'scroll-view'
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// 验证 scroll-view 模式下的渲染
|
||||||
|
expect(wrapper.find('.wd-pull-refresh__scroll-view').exists()).toBe(true)
|
||||||
|
expect(wrapper.vm.scrollMode).toBe('scroll-view')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should use default scroll mode', async () => {
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
// 默认模式应该是 page
|
||||||
|
expect(wrapper.vm.scrollMode).toBe('page')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should expose correct methods', () => {
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查暴露的方法
|
||||||
|
expect(typeof wrapper.vm.finish).toBe('function')
|
||||||
|
expect(wrapper.vm.scrollMode).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle refresh correctly', async () => {
|
||||||
|
const onRefresh = vi.fn()
|
||||||
|
const wrapper = mount(WdPullRefresh, {
|
||||||
|
props: {
|
||||||
|
modelValue: false,
|
||||||
|
scrollMode: 'page'
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: '<div>Content</div>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
wrapper.vm.$emit('refresh')
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// 检查是否可以正常触发刷新事件
|
||||||
|
expect(wrapper.emitted('refresh')).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user