From ddab83ac518d78337377c365cbb5c92eadda947a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=8D=E5=A6=82=E6=91=B8=E9=B1=BC=E5=8E=BB?=
<1780903673@qq.com>
Date: Wed, 30 Jul 2025 12:34:41 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20=E6=96=B0=E5=A2=9E=20pull-r?=
=?UTF-8?q?efresh=20=E4=B8=8B=E6=8B=89=E5=88=B7=E6=96=B0=E7=BB=84=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/component/pull-refresh.md | 189 ++++++++++++
docs/en-US/component/pull-refresh.md | 189 ++++++++++++
src/locale/en-US.json | 1 +
src/locale/zh-CN.json | 1 +
src/pages.json | 15 +
src/pages/index/Index.vue | 4 +
src/subPages/pullRefresh/Index.vue | 100 +++++++
.../components/common/abstracts/variable.scss | 20 +-
.../components/wd-pull-refresh/index.scss | 42 +++
.../components/wd-pull-refresh/types.ts | 68 +++++
.../wd-pull-refresh/wd-pull-refresh.vue | 273 ++++++++++++++++++
src/uni_modules/wot-design-uni/global.d.ts | 2 +
tests/components/wd-pull-refresh.test.ts | 121 ++++++++
13 files changed, 1024 insertions(+), 1 deletion(-)
create mode 100644 docs/component/pull-refresh.md
create mode 100644 docs/en-US/component/pull-refresh.md
create mode 100644 src/subPages/pullRefresh/Index.vue
create mode 100644 src/uni_modules/wot-design-uni/components/wd-pull-refresh/index.scss
create mode 100644 src/uni_modules/wot-design-uni/components/wd-pull-refresh/types.ts
create mode 100644 src/uni_modules/wot-design-uni/components/wd-pull-refresh/wd-pull-refresh.vue
create mode 100644 tests/components/wd-pull-refresh.test.ts
diff --git a/docs/component/pull-refresh.md b/docs/component/pull-refresh.md
new file mode 100644
index 00000000..ccaf042a
--- /dev/null
+++ b/docs/component/pull-refresh.md
@@ -0,0 +1,189 @@
+# PullRefresh 下拉刷新
+
+用于提供下拉刷新的交互操作。
+
+## 基础用法
+
+下拉刷新时会触发 `refresh` 事件,在事件的回调函数中可以进行同步或异步操作,操作完成后将 `v-model` 设置为 `false`,表示加载完成。
+
+```html
+
+
+
+
+
+```
+
+```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
+
+
+
+
+
+```
+
+## 成功提示
+
+通过 `success-text` 可以设置刷新成功后的提示文案,通过 `success-duration` 可以设置提示展示时长。
+
+```html
+
+
+
+
+
+```
+
+## 自定义插槽
+
+通过插槽可以自定义下拉刷新过程中的提示内容。
+
+```html
+
+
+
+
+ 下拉距离: {{ Math.round(distance) }}px
+
+
+
+
+
+
+ 释放距离: {{ Math.round(distance) }}px
+
+
+
+
+
+
+ 正在刷新数据...
+
+
+
+
+
+
+ 刷新完成
+
+
+
+
+
+
+
+```
+
+## 禁用状态
+
+通过 `disabled` 属性可以禁用下拉刷新。
+
+```html
+
+
+
+
+
+```
+
+## 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
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/docs/en-US/component/pull-refresh.md b/docs/en-US/component/pull-refresh.md
new file mode 100644
index 00000000..e1cb1a06
--- /dev/null
+++ b/docs/en-US/component/pull-refresh.md
@@ -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
+
+
+
+
+
+```
+
+```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
+
+
+
+
+
+```
+
+## Success Tip
+
+You can set the prompt text after successful refresh through `success-text`, and set the prompt display duration through `success-duration`.
+
+```html
+
+
+
+
+
+```
+
+## Custom Slots
+
+You can customize the prompt content during the pull-to-refresh process through slots.
+
+```html
+
+
+
+
+ Pull distance: {{ Math.round(distance) }}px
+
+
+
+
+
+
+ Release distance: {{ Math.round(distance) }}px
+
+
+
+
+
+
+ Refreshing data...
+
+
+
+
+
+
+ Refresh completed
+
+
+
+
+
+
+
+```
+
+## Disabled State
+
+You can disable pull-to-refresh through the `disabled` property.
+
+```html
+
+
+
+
+
+```
+
+## 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
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/src/locale/en-US.json b/src/locale/en-US.json
index a6a5e41a..f291dcbc 100644
--- a/src/locale/en-US.json
+++ b/src/locale/en-US.json
@@ -864,6 +864,7 @@
"progress-title": "Progress",
"pu-tong-an-niu": "Normal button",
"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-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",
diff --git a/src/locale/zh-CN.json b/src/locale/zh-CN.json
index 0e8bfbd6..b705ac80 100644
--- a/src/locale/zh-CN.json
+++ b/src/locale/zh-CN.json
@@ -864,6 +864,7 @@
"progress-title": "Progress 进度条",
"pu-tong-an-niu": "普通按钮",
"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-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": "其他信息",
diff --git a/src/pages.json b/src/pages.json
index 6224104c..75b48e4f 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -1326,6 +1326,21 @@
"navigationBarTitleText": "%rootportal-title%"
// #endif
}
+ },
+ {
+ "path": "pullRefresh/Index",
+ "name": "pullRefresh",
+ "style": {
+ "mp-alipay": {
+ "allowsBounceVertical": "NO"
+ },
+ // #ifdef MP
+ "navigationBarTitleText": "PullRefresh 下拉刷新",
+ // #endif
+ // #ifndef MP
+ "navigationBarTitleText": "%pullrefresh-title%"
+ // #endif
+ }
}
]
}
diff --git a/src/pages/index/Index.vue b/src/pages/index/Index.vue
index ba5149d2..c7cdd47d 100644
--- a/src/pages/index/Index.vue
+++ b/src/pages/index/Index.vue
@@ -319,6 +319,10 @@ const list = computed(() => [
{
id: 'numberKeyboard',
name: t('numberkeyboard-shu-zi-jian-pan')
+ },
+ {
+ id: 'pullRefresh',
+ name: t('pullrefresh-xia-la-shua-xin')
}
]
},
diff --git a/src/subPages/pullRefresh/Index.vue b/src/subPages/pullRefresh/Index.vue
new file mode 100644
index 00000000..38744aa0
--- /dev/null
+++ b/src/subPages/pullRefresh/Index.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss b/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss
index bdbb88e1..77d7a62e 100644
--- a/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss
+++ b/src/uni_modules/wot-design-uni/components/common/abstracts/variable.scss
@@ -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; // 外部阴影
/* 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-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-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; // 动画持续时间
+
diff --git a/src/uni_modules/wot-design-uni/components/wd-pull-refresh/index.scss b/src/uni_modules/wot-design-uni/components/wd-pull-refresh/index.scss
new file mode 100644
index 00000000..f1f0a213
--- /dev/null
+++ b/src/uni_modules/wot-design-uni/components/wd-pull-refresh/index.scss
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/uni_modules/wot-design-uni/components/wd-pull-refresh/types.ts b/src/uni_modules/wot-design-uni/components/wd-pull-refresh/types.ts
new file mode 100644
index 00000000..1f748c78
--- /dev/null
+++ b/src/uni_modules/wot-design-uni/components/wd-pull-refresh/types.ts
@@ -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('page'),
+ /**
+ * scroll-view 模式下的固定高度
+ */
+ height: makeNumericProp(400)
+}
+
+export type PullRefreshProps = ExtractPropTypes
diff --git a/src/uni_modules/wot-design-uni/components/wd-pull-refresh/wd-pull-refresh.vue b/src/uni_modules/wot-design-uni/components/wd-pull-refresh/wd-pull-refresh.vue
new file mode 100644
index 00000000..e59098a2
--- /dev/null
+++ b/src/uni_modules/wot-design-uni/components/wd-pull-refresh/wd-pull-refresh.vue
@@ -0,0 +1,273 @@
+
+
+
+
+
+
+
+ {{ pullingText }}
+
+
+
+ {{ loosingText }}
+
+
+
+
+
+ {{ loadingText }}
+
+
+
+
+ {{ successText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/uni_modules/wot-design-uni/global.d.ts b/src/uni_modules/wot-design-uni/global.d.ts
index 94014a34..47217a05 100644
--- a/src/uni_modules/wot-design-uni/global.d.ts
+++ b/src/uni_modules/wot-design-uni/global.d.ts
@@ -40,6 +40,7 @@ declare module 'vue' {
WdPopover: typeof import('./components/wd-popover/wd-popover.vue')['default']
WdPopup: typeof import('./components/wd-popup/wd-popup.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']
WdRadioGroup: typeof import('./components/wd-radio-group/wd-radio-group.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']
WdSignature: typeof import('./components/wd-signature/wd-signature.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']
}
}
diff --git a/tests/components/wd-pull-refresh.test.ts b/tests/components/wd-pull-refresh.test.ts
new file mode 100644
index 00000000..a35e1f37
--- /dev/null
+++ b/tests/components/wd-pull-refresh.test.ts
@@ -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: 'Content
'
+ }
+ })
+
+ 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: 'Content
'
+ }
+ })
+
+ 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: 'Content
'
+ }
+ })
+
+ 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: 'Content
'
+ }
+ })
+
+ 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: 'Content
'
+ }
+ })
+
+ await nextTick()
+ // 默认模式应该是 page
+ expect(wrapper.vm.scrollMode).toBe('page')
+ })
+
+ test('should expose correct methods', () => {
+ const wrapper = mount(WdPullRefresh, {
+ props: {
+ modelValue: false
+ },
+ slots: {
+ default: 'Content
'
+ }
+ })
+
+ // 检查暴露的方法
+ 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: 'Content
'
+ }
+ })
+
+ wrapper.vm.$emit('refresh')
+ await nextTick()
+
+ // 检查是否可以正常触发刷新事件
+ expect(wrapper.emitted('refresh')).toBeTruthy()
+ })
+})