Kaynağa Gözat

feat: 商品详情页完善

liangan 1 ay önce
ebeveyn
işleme
478a33513f

+ 4 - 60
.promptx/pouch.json

@@ -1,72 +1,16 @@
 {
-  "currentState": "memory_saved",
+  "currentState": "initialized",
   "stateHistory": [
     {
       "from": "initial",
       "command": "init",
-      "timestamp": "2025-07-16T02:02:21.718Z",
+      "timestamp": "2025-07-16T06:01:20.043Z",
       "args": [
         {
-          "workingDirectory": "d:\\work\\bandhu-buy\\uniapp",
-          "ideType": "cursor"
+          "workingDirectory": "/Users/liangan/Documents/work_files/code/BandhuBuyUniBest"
         }
       ]
-    },
-    {
-      "from": "initialized",
-      "command": "welcome",
-      "timestamp": "2025-07-16T02:02:26.513Z",
-      "args": []
-    },
-    {
-      "from": "service_discovery",
-      "command": "action",
-      "timestamp": "2025-07-16T02:02:31.030Z",
-      "args": [
-        "noface"
-      ]
-    },
-    {
-      "from": "role_activated_with_memory",
-      "command": "learn",
-      "timestamp": "2025-07-16T02:08:54.421Z",
-      "args": [
-        "@file://d:\\work\\bandhu-buy\\uniapp/src\\pages\\income\\income.vue"
-      ]
-    },
-    {
-      "from": "learn_error",
-      "command": "learn",
-      "timestamp": "2025-07-16T02:09:02.443Z",
-      "args": [
-        "@file://d:\\work\\bandhu-buy\\uniapp/src\\pages\\referEarn\\referEarn.vue"
-      ]
-    },
-    {
-      "from": "learn_error",
-      "command": "learn",
-      "timestamp": "2025-07-16T02:09:13.148Z",
-      "args": [
-        "@file://d:\\work\\bandhu-buy\\uniapp/vite-plugins/copyNativeRes.ts"
-      ]
-    },
-    {
-      "from": "learn_error",
-      "command": "learn",
-      "timestamp": "2025-07-16T02:09:20.149Z",
-      "args": [
-        "@file://d:\\work\\bandhu-buy\\uniapp/vite-plugins/updatePackageJson.ts"
-      ]
-    },
-    {
-      "from": "learn_error",
-      "command": "remember",
-      "timestamp": "2025-07-16T02:46:16.275Z",
-      "args": [
-        "noface",
-        "Vue3 UniApp项目开发规范总结:\n\n## 页面类型规范\n1. **系统原生导航栏页面**:使用 layout: 'default' + 自定义导航栏配置(如income.vue)\n2. **自定义导航栏页面**:使用 navigationStyle: 'custom' + 手动处理安全区域(如referEarn.vue)\n\n## 代码书写习惯\n1. **UnoCSS样式**:完全使用UnoCSS原子类,避免传统CSS,优先使用内联样式避免动态类名问题\n2. **组件库**:wot-design-uni (wd-前缀组件),包括wd-form、wd-input、wd-button等\n3. **分页组件**:z-paging统一处理列表和滚动,配合useZPaging hook\n4. **TypeScript**:严格类型定义,defineOptions命名规范\n\n## 项目结构规范\n1. **路由配置**:使用json5格式的route块,自动生成pages.json\n2. **生命周期**:必须导入页面生命周期(即使未直接使用)\n3. **插件配置**:自定义vite插件处理构建逻辑\n4. **组件结构**:route配置 → script setup → template → style\n\n## 具体编码规范\n1. **安全区域**:自定义导航栏页面必须处理safeAreaInsets\n2. **组件命名**:defineOptions中明确name属性\n3. **表单处理**:使用wd-form + wd-input + 验证规则\n4. **样式写法**:优先UnoCSS原子类,必要时使用:deep()修改组件样式\n5. **背景图片**:统一使用/static/login-bg.png作为登录相关页面背景"
-      ]
     }
   ],
-  "lastUpdated": "2025-07-16T02:46:16.302Z"
+  "lastUpdated": "2025-07-16T06:01:20.076Z"
 }

+ 2 - 2
.promptx/resource/project.registry.json

@@ -4,8 +4,8 @@
   "metadata": {
     "version": "2.0.0",
     "description": "project 级资源注册表",
-    "createdAt": "2025-07-16T02:02:21.742Z",
-    "updatedAt": "2025-07-16T02:02:21.742Z",
+    "createdAt": "2025-07-16T06:01:20.073Z",
+    "updatedAt": "2025-07-16T06:01:20.074Z",
     "resourceCount": 0
   },
   "resources": [],

+ 3 - 6
src/pages.json

@@ -41,6 +41,7 @@
       }
     ]
   },
+  "__esModule": true,
   "pages": [
     {
       "path": "pages/index/index",
@@ -146,11 +147,7 @@
     },
     {
       "path": "pages/share/share",
-      "type": "page",
-      "layout": "default",
-      "style": {
-        "navigationStyle": "custom"
-      }
+      "type": "page"
     },
     {
       "path": "pages/topChampions/topChampions",
@@ -171,4 +168,4 @@
     }
   ],
   "subPackages": []
-}
+}

+ 159 - 0
src/pages/productDetail/components/CustomTooltip.vue

@@ -0,0 +1,159 @@
+<script lang="ts" setup>
+interface Props {
+  visible?: boolean
+  autoHide?: boolean
+  autoHideDelay?: number
+  icon?: string
+  iconSize?: string
+  message?: string
+  highlightText1?: string
+  highlightText2?: string
+  width?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  visible: false,
+  autoHide: true,
+  autoHideDelay: 10000,
+  icon: 'shop',
+  iconSize: '32rpx',
+  message: 'Full refund for unsuccessful group purchase $99.99 Receive red envelope rewards $1.8',
+  highlightText1: '$99.99',
+  highlightText2: '$1.8',
+  width: '600rpx',
+})
+
+const emit = defineEmits<{
+  'update:visible': [value: boolean]
+  'hide': []
+}>()
+
+const internalVisible = ref(props.visible)
+let hideTimer: number | null = null
+
+// 监听 visible prop 变化
+watch(() => props.visible, (newVal) => {
+  internalVisible.value = newVal
+  if (newVal && props.autoHide) {
+    startAutoHideTimer()
+  }
+  else {
+    clearAutoHideTimer()
+  }
+})
+
+// 监听内部 visible 变化
+watch(internalVisible, (newVal) => {
+  emit('update:visible', newVal)
+  if (!newVal) {
+    emit('hide')
+    clearAutoHideTimer()
+  }
+})
+
+// 启动自动隐藏定时器
+function startAutoHideTimer() {
+  clearAutoHideTimer()
+  if (props.autoHide && props.autoHideDelay > 0) {
+    hideTimer = setTimeout(() => {
+      hide()
+    }, props.autoHideDelay)
+  }
+}
+
+// 清除自动隐藏定时器
+function clearAutoHideTimer() {
+  if (hideTimer) {
+    clearTimeout(hideTimer)
+    hideTimer = null
+  }
+}
+
+// 显示提示框
+function show() {
+  internalVisible.value = true
+  if (props.autoHide) {
+    startAutoHideTimer()
+  }
+}
+
+// 隐藏提示框
+function hide() {
+  internalVisible.value = false
+}
+
+// 切换显示状态
+function toggle() {
+  if (internalVisible.value) {
+    hide()
+  }
+  else {
+    show()
+  }
+}
+
+// 处理消息文本,高亮特定文本
+function formatMessage() {
+  let formattedMessage = props.message
+
+  if (props.highlightText1) {
+    formattedMessage = formattedMessage.replace(
+      props.highlightText1,
+      `<text class="text-[var(--wot-color-theme)]">${props.highlightText1}</text>`,
+    )
+  }
+
+  if (props.highlightText2) {
+    formattedMessage = formattedMessage.replace(
+      props.highlightText2,
+      `<text class="text-[var(--wot-color-theme)]">${props.highlightText2}</text>`,
+    )
+  }
+
+  return formattedMessage
+}
+
+// 组件卸载时清除定时器
+onUnmounted(() => {
+  clearAutoHideTimer()
+})
+
+// 暴露方法给父组件
+defineExpose({
+  show,
+  hide,
+  toggle,
+})
+</script>
+
+<template>
+  <view
+    v-if="internalVisible"
+    class="absolute bottom-full left-1/2 mb-20rpx transform rounded-full bg-black bg-opacity-80 px-20rpx py-12rpx text-24rpx text-white -translate-x-1/2"
+    :style="{ width }"
+  >
+    <view class="flex items-center">
+      <wd-icon
+        v-if="icon"
+        :name="icon"
+        :size="iconSize"
+        class="mr-12rpx flex-shrink-0"
+      />
+      <view class="text-center">
+        <text>Full refund for unsuccessful group purchase </text>
+        <text class="text-[var(--wot-color-theme)]">
+          {{ highlightText1 }}
+        </text>
+        <text> Receive red envelope rewards </text>
+        <text class="text-[var(--wot-color-theme)]">
+          {{ highlightText2 }}
+        </text>
+      </view>
+    </view>
+    <!-- 倒三角箭头 -->
+    <view
+      class="absolute left-1/2 top-full h-0 w-0 -translate-x-1/2"
+      style="border-left: 16rpx solid transparent; border-right: 16rpx solid transparent; border-top: 16rpx solid rgba(0, 0, 0, 0.8);"
+    />
+  </view>
+</template>

+ 110 - 0
src/pages/productDetail/components/NotificationCarousel.vue

@@ -0,0 +1,110 @@
+<script lang="ts" setup>
+interface Notification {
+  id: number
+  name: string
+  time: string
+}
+
+interface Props {
+  notifications?: Notification[]
+  interval?: number
+  top?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  notifications: () => [
+    { id: 1, name: 'Aamir Khan', time: '10s' },
+    { id: 2, name: 'John Smith', time: '30s' },
+    { id: 3, name: 'Maria Garcia', time: '1m' },
+  ],
+  interval: 3000,
+  top: '0px',
+})
+
+const notificationIndex = ref(0)
+const prevNotificationIndex = ref(0)
+let notificationTimer: number | null = null
+
+// 启动通知轮播
+function startNotificationCarousel() {
+  if (props.notifications.length <= 1)
+    return
+
+  notificationTimer = setInterval(() => {
+    // 保存当前索引
+    prevNotificationIndex.value = notificationIndex.value
+
+    // 更新为下一个索引
+    notificationIndex.value = (notificationIndex.value + 1) % props.notifications.length
+  }, props.interval)
+}
+
+// 停止通知轮播
+function stopNotificationCarousel() {
+  if (notificationTimer) {
+    clearInterval(notificationTimer)
+    notificationTimer = null
+  }
+}
+
+// 重启轮播(当 props 变化时)
+function restartCarousel() {
+  stopNotificationCarousel()
+  notificationIndex.value = 0
+  prevNotificationIndex.value = 0
+  nextTick(() => {
+    startNotificationCarousel()
+  })
+}
+
+// 监听 notifications 变化
+watch(() => props.notifications, restartCarousel, { deep: true })
+
+// 监听 interval 变化
+watch(() => props.interval, restartCarousel)
+
+// 生命周期
+onMounted(() => {
+  startNotificationCarousel()
+})
+
+onUnmounted(() => {
+  stopNotificationCarousel()
+})
+
+// 暴露方法给父组件
+defineExpose({
+  start: startNotificationCarousel,
+  stop: stopNotificationCarousel,
+  restart: restartCarousel,
+})
+</script>
+
+<template>
+  <view
+    v-if="notifications.length > 0"
+    class="absolute left-24rpx h-56rpx w-70% overflow-hidden rounded-full"
+    :style="{ top }"
+  >
+    <view
+      v-for="(notification, index) in notifications"
+      :key="notification.id"
+      class="flex items-center rounded-full bg-#000000/60 py-8rpx pl-8rpx pr-14rpx transition-all duration-500 ease-in-out"
+      :style="{
+        opacity: index === notificationIndex ? 1 : 0,
+        transform: index === notificationIndex ? 'translateY(0)'
+          : (index === prevNotificationIndex ? 'translateY(-100%)' : 'translateY(100%)'),
+        position: 'absolute',
+        top: 0,
+        left: 0,
+        width: '100%',
+        zIndex: index === notificationIndex ? 2 : 1,
+      }"
+    >
+      <wd-img width="40rpx" round height="40rpx" src="/static/images/avatar.jpg" />
+      <text class="ml-12rpx truncate text-24rpx text-white">
+        {{ notification.name }} joined this group {{ notification.time }} ago!
+      </text>
+    </view>
+  </view>
+</template>

+ 210 - 0
src/pages/productDetail/components/README.md

@@ -0,0 +1,210 @@
+# ProductDetail 组件库
+
+商品详情页面的组件库,包含通知轮播和自定义提示框组件。
+
+## NotificationCarousel 组件
+
+通知轮播组件,用于在页面顶部显示滚动的通知消息。
+
+### 功能特性
+
+- 自动轮播通知消息
+- 平滑的动画过渡效果
+- 可自定义轮播间隔时间
+- 可自定义位置
+- 支持动态数据更新
+- 自动管理生命周期
+
+### 使用方法
+
+#### 基础用法
+
+```vue
+<template>
+  <NotificationCarousel :notifications="notifications" />
+</template>
+
+<script setup>
+import NotificationCarousel from './components/NotificationCarousel.vue'
+
+const notifications = ref([
+  { id: 1, name: 'Aamir Khan', time: '10s' },
+  { id: 2, name: 'John Smith', time: '30s' },
+  { id: 3, name: 'Maria Garcia', time: '1m' },
+])
+</script>
+```
+
+#### 自定义配置
+
+```vue
+<template>
+  <NotificationCarousel
+    :notifications="notifications"
+    :interval="5000"
+    :top="`${safeAreaInsets?.top + 52}px`"
+  />
+</template>
+```
+
+### Props
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| notifications | Array | 默认数据 | 通知数据数组 |
+| interval | Number | 3000 | 轮播间隔时间(毫秒) |
+| top | String | '0px' | 组件距离顶部的距离 |
+
+#### notifications 数据格式
+
+```typescript
+interface Notification {
+  id: number        // 唯一标识
+  name: string      // 用户名
+  time: string      // 时间描述
+}
+```
+
+### 方法
+
+组件暴露了以下方法,可通过 ref 调用:
+
+- `start()` - 开始轮播
+- `stop()` - 停止轮播
+- `restart()` - 重新开始轮播
+
+#### 使用示例
+
+```vue
+<template>
+  <NotificationCarousel ref="carouselRef" :notifications="notifications" />
+  <button @click="handleStop">停止轮播</button>
+  <button @click="handleStart">开始轮播</button>
+</template>
+
+<script setup>
+const carouselRef = ref()
+
+const handleStop = () => {
+  carouselRef.value?.stop()
+}
+
+const handleStart = () => {
+  carouselRef.value?.start()
+}
+</script>
+```
+
+### 注意事项
+
+1. 当 notifications 数组长度为 0 或 1 时,组件不会启动轮播
+2. 组件会自动监听 props 变化并重新启动轮播
+3. 组件会在卸载时自动清理定时器
+4. 使用了 UnoCSS 原子类进行样式设置
+
+---
+
+## CustomTooltip 组件
+
+自定义提示框组件,用于显示带箭头的气泡提示信息。
+
+### 功能特性
+
+- 支持自动显示/隐藏
+- 可自定义显示时长
+- 支持高亮文本
+- 可自定义图标和样式
+- 支持手动控制显示状态
+- 自动管理定时器
+
+### 使用方法
+
+#### 基础用法
+
+```vue
+<template>
+  <view class="relative">
+    <button>触发按钮</button>
+    <CustomTooltip v-model:visible="showTooltip" />
+  </view>
+</template>
+
+<script setup>
+import CustomTooltip from './components/CustomTooltip.vue'
+
+const showTooltip = ref(false)
+</script>
+```
+
+#### 自定义配置
+
+```vue
+<template>
+  <CustomTooltip
+    v-model:visible="showTooltip"
+    :auto-hide="true"
+    :auto-hide-delay="5000"
+    icon="shop"
+    icon-size="32rpx"
+    highlight-text1="$99.99"
+    highlight-text2="$1.8"
+    width="500rpx"
+    @hide="handleHide"
+  />
+</template>
+```
+
+### Props
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| visible | Boolean | false | 是否显示提示框(支持 v-model) |
+| autoHide | Boolean | true | 是否自动隐藏 |
+| autoHideDelay | Number | 10000 | 自动隐藏延迟时间(毫秒) |
+| icon | String | 'shop' | 图标名称 |
+| iconSize | String | '32rpx' | 图标大小 |
+| message | String | 默认消息 | 提示消息内容 |
+| highlightText1 | String | '$99.99' | 第一个高亮文本 |
+| highlightText2 | String | '$1.8' | 第二个高亮文本 |
+| width | String | '600rpx' | 提示框宽度 |
+
+### 事件
+
+| 事件名 | 说明 | 回调参数 |
+|--------|------|----------|
+| update:visible | 显示状态变化时触发 | (value: boolean) |
+| hide | 提示框隐藏时触发 | - |
+
+### 方法
+
+组件暴露了以下方法,可通过 ref 调用:
+
+- `show()` - 显示提示框
+- `hide()` - 隐藏提示框
+- `toggle()` - 切换显示状态
+
+#### 使用示例
+
+```vue
+<template>
+  <view class="relative">
+    <button @click="handleShow">显示提示</button>
+    <CustomTooltip ref="tooltipRef" />
+  </view>
+</template>
+
+<script setup>
+const tooltipRef = ref()
+
+const handleShow = () => {
+  tooltipRef.value?.show()
+}
+</script>
+```
+
+### 注意事项
+
+1. 组件使用绝对定位,需要在相对定位的容器中使用
+2. 组件会自动管理定时器,无需手动清理
+3. 支持双向绑定 visible 属性
+4. 使用了 UnoCSS 原子类进行样式设置

+ 60 - 63
src/pages/productDetail/productDetail.vue

@@ -11,8 +11,9 @@
 // 必须导入需要用到的页面生命周期(即使在当前页面上没有直接使用到)
 // eslint-disable-next-line unused-imports/no-unused-imports
 import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
-import { onMounted, onUnmounted } from 'vue'
 import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
+import CustomTooltip from './components/CustomTooltip.vue'
+import NotificationCarousel from './components/NotificationCarousel.vue'
 
 defineOptions({
   name: 'ProductDetail', // 商品详情
@@ -28,20 +29,37 @@ useZPaging(paging)
 
 // 添加导航栏背景色变量
 const navBgColor = ref('transparent')
-const navIconType = ref('tr') // tr表示透明背景下的图标,默认为白色图标
 const changeNavbarThreshold = 300 // 滚动到这个高度时改变导航栏颜色
 
+const showTip = ref(false)
+
+// 添加通知轮播数据
+const notifications = ref([
+  { id: 1, name: 'Aamir Khan', time: '10s' },
+  { id: 2, name: 'John Smith', time: '30s' },
+  { id: 3, name: 'Maria Garcia', time: '1m' },
+])
+
 onPageScroll((e) => {
   // 根据滚动高度改变导航栏背景色
   if (e.scrollTop > changeNavbarThreshold) {
     navBgColor.value = '#ffffff'
-    navIconType.value = '' // 切换为深色图标
   }
   else {
     navBgColor.value = 'transparent'
-    navIconType.value = 'tr' // 切换为白色图标
   }
 })
+// 点击页面任意地方隐藏提示
+function handlePageClick() {
+  if (showTip.value) {
+    showTip.value = false
+  }
+}
+
+// 生命周期钩子
+onMounted(() => {
+  showTip.value = true // 显示提示
+})
 
 // 搜索结果
 // 轮播图
@@ -52,42 +70,6 @@ const swiperList = ref([
   '/static/images/vip-level1.png',
 ])
 
-// 添加通知轮播数据
-const notifications = ref([
-  { id: 1, name: 'Aamir Khan', time: '10s' },
-  { id: 2, name: 'John Smith', time: '30s' },
-  { id: 3, name: 'Maria Garcia', time: '1m' },
-])
-const notificationIndex = ref(0)
-// 添加上一个索引的记录,用于判断动画方向
-const prevNotificationIndex = ref(0)
-
-// 设置通知轮播定时器
-let notificationTimer: number | null = null
-
-// 生命周期钩子中启动轮播
-onMounted(() => {
-  startNotificationCarousel()
-})
-
-// 组件卸载时清除定时器
-onUnmounted(() => {
-  if (notificationTimer) {
-    clearInterval(notificationTimer)
-  }
-})
-
-// 启动通知轮播
-function startNotificationCarousel() {
-  notificationTimer = setInterval(() => {
-    // 保存当前索引
-    prevNotificationIndex.value = notificationIndex.value
-
-    // 更新为下一个索引
-    notificationIndex.value = (notificationIndex.value + 1) % notifications.value.length
-  }, 1500) // 每1.5秒切换一次
-}
-
 function handleClick(e) {
   // console.log(e)
 }
@@ -122,7 +104,7 @@ function queryList(pageNo, pageSize) {
 </script>
 
 <template>
-  <z-paging ref="paging" refresher-only use-page-scroll @query="queryList">
+  <z-paging ref="paging" refresher-only use-page-scroll @query="queryList" @click="handlePageClick">
     <wd-navbar :bordered="false" safe-area-inset-top fixed :left-arrow="false" :custom-style="`background: ${navBgColor}; transition: background 0.3s;`" custom-class="h-auto!">
       <template #title>
         <view class="box-border h-full flex items-center justify-between p-24rpx">
@@ -137,28 +119,10 @@ function queryList(pageNo, pageSize) {
         custom-indicator-class="bottom-40rpx!" :indicator="{ type: 'fraction' }" indicator-position="bottom-right"
         image-mode="aspectFit" @click="handleClick" @change="onChange"
       />
-      <view class="absolute left-24rpx h-56rpx w-70% overflow-hidden rounded-full" :style="{ top: `${safeAreaInsets?.top + 52}px` }">
-        <view
-          v-for="(notification, index) in notifications"
-          :key="notification.id"
-          class="flex items-center rounded-full bg-#000000/60 py-8rpx pl-8rpx pr-14rpx transition-all duration-500 ease-in-out"
-          :style="{
-            opacity: index === notificationIndex ? 1 : 0,
-            transform: index === notificationIndex ? 'translateY(0)'
-              : (index === prevNotificationIndex ? 'translateY(-100%)' : 'translateY(100%)'),
-            position: 'absolute',
-            top: 0,
-            left: 0,
-            width: '100%',
-            zIndex: index === notificationIndex ? 2 : 1,
-          }"
-        >
-          <wd-img width="40rpx" round height="40rpx" src="/static/images/avatar.jpg" />
-          <text class="ml-12rpx truncate text-24rpx text-white">
-            {{ notification.name }} joined this group {{ notification.time }} ago!
-          </text>
-        </view>
-      </view>
+      <NotificationCarousel
+        :notifications="notifications"
+        :top="`${safeAreaInsets?.top + 52}px`"
+      />
     </view>
     <view class="relative -top-24rpx">
       <view
@@ -277,6 +241,39 @@ function queryList(pageNo, pageSize) {
         </view>
       </view>
     </view>
+    <template #bottom>
+      <view class="h-100rpx flex gap-32rpx bg-white bg-opacity-60 px-28rpx backdrop-blur-20">
+        <view class="flex items-center justify-between gap-20rpx">
+          <view class="flex flex-col items-center justify-center">
+            <wd-icon color="#BDBDBD" name="home" size="40rpx" />
+            <text class="text-18rpx text-#757575">
+              Home
+            </text>
+          </view>
+          <view class="flex flex-col items-center justify-center">
+            <wd-icon color="#BDBDBD" name="heart-filled" size="40rpx" />
+            <text class="text-18rpx text-#757575">
+              Favorite
+            </text>
+          </view>
+        </view>
+        <view class="flex flex-1 items-center justify-end text-32rpx">
+          <view class="relative">
+            <view class="rounded-l-full bg-#2F2D31 px-34rpx py-18rpx text-white">
+              Open Group
+            </view>
+            <CustomTooltip
+              v-model:visible="showTip"
+              highlight-text1="$99.99"
+              highlight-text2="$1.8"
+            />
+          </view>
+          <view class="rounded-r-full bg-[var(--wot-color-theme)] px-34rpx py-18rpx text-white">
+            Join Group
+          </view>
+        </view>
+      </view>
+    </template>
   </z-paging>
 </template>