瀏覽代碼

feat: 弹框封装

liangan 1 月之前
父節點
當前提交
4d49633c02

+ 98 - 0
src/components/DialogBox/DialogBox.vue

@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+interface Props {
+  /** 是否显示对话框 */
+  show?: boolean
+  /** 按钮文本 */
+  btnText?: string
+  /** 图标名称 */
+  icon?: string
+  /** 图标大小 */
+  iconSize?: string
+  /** 主要消息内容 */
+  message?: string
+  /** 提示信息 */
+  tip?: string
+  /** 是否显示按钮 */
+  showButton?: boolean
+  /** 是否显示提示信息 */
+  showTip?: boolean
+  /** 点击遮罩是否关闭 */
+  closeOnClickOverlay?: boolean
+}
+
+interface Emits {
+  /** 更新显示状态 */
+  (e: 'update:show', value: boolean): void
+  /** 按钮点击事件 */
+  (e: 'confirm'): void
+  /** 对话框关闭事件 */
+  (e: 'close'): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  show: false,
+  btnText: 'Got it',
+  icon: 'info-circle',
+  iconSize: '120rpx',
+  message: '',
+  tip: '',
+  showButton: true,
+  showTip: false,
+  closeOnClickOverlay: true,
+})
+
+const emit = defineEmits<Emits>()
+
+// 处理遮罩点击
+function handleOverlayClick() {
+  if (props.closeOnClickOverlay) {
+    handleClose()
+  }
+}
+
+// 处理关闭
+function handleClose() {
+  emit('update:show', false)
+  emit('close')
+}
+
+// 处理按钮点击
+function handleConfirm() {
+  emit('confirm')
+  handleClose()
+}
+</script>
+
+<template>
+  <wd-overlay :show="props.show" @click="handleOverlayClick">
+    <view class="wrapper">
+      <view class="rounded-24rpx bg-white p-40rpx text-center" @click.stop>
+        <wd-icon :name="props.icon" :size="props.iconSize" />
+        <view class="pb-58rpx pt-34rpx text-center text-32rpx">
+          {{ props.message }}
+        </view>
+        <wd-button
+          v-if="props.showButton"
+
+          plain block
+          @click="handleConfirm"
+        >
+          {{ props.btnText }}
+        </wd-button>
+        <view v-if="props.showTip && props.tip" class="mt-20rpx text-24rpx text-gray-500">
+          {{ props.tip }}
+        </view>
+      </view>
+    </view>
+  </wd-overlay>
+</template>
+
+<style lang="scss" scoped>
+.wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  padding: 0 24rpx;
+}
+</style>

+ 148 - 0
src/components/DialogBox/README.md

@@ -0,0 +1,148 @@
+# DialogBox 通用对话框组件
+
+一个基于 Wot Design Uni 的通用对话框组件,支持自定义图标、消息、按钮等。
+
+## 功能特性
+
+- 🎨 支持自定义图标和大小
+- 📝 支持多行消息显示
+- 🔘 可选择显示/隐藏按钮
+- 💡 可选择显示/隐藏提示信息
+- 🖱️ 支持点击遮罩关闭
+- 📱 响应式设计,适配移动端
+
+## Props
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| show | boolean | false | 是否显示对话框 |
+| btnText | string | 'Got it' | 按钮文本 |
+| icon | string | 'info-circle' | 图标名称 |
+| iconSize | string | '120rpx' | 图标大小 |
+| message | string | '' | 主要消息内容 |
+| tip | string | '' | 提示信息 |
+| showButton | boolean | true | 是否显示按钮 |
+| showTip | boolean | false | 是否显示提示信息 |
+| closeOnClickOverlay | boolean | true | 点击遮罩是否关闭 |
+
+## Events
+
+| 事件名 | 说明 | 回调参数 |
+|--------|------|----------|
+| update:show | 显示状态更新 | (value: boolean) |
+| confirm | 按钮点击事件 | - |
+| close | 对话框关闭事件 | - |
+
+## 使用示例
+
+### 基础用法
+
+```vue
+<template>
+  <DialogBox 
+    v-model:show="showDialog"
+    message="这是一个基础对话框"
+    @confirm="handleConfirm"
+  />
+</template>
+
+<script setup>
+import DialogBox from '@/components/DialogBox/DialogBox.vue'
+
+const showDialog = ref(false)
+
+const handleConfirm = () => {
+  console.log('用户点击了确认')
+}
+</script>
+```
+
+### 带提示信息
+
+```vue
+<template>
+  <DialogBox 
+    v-model:show="showDialog"
+    icon="warning"
+    message="余额不足,请充值!"
+    tip="充值享受最高5%折扣"
+    :showTip="true"
+    btnText="去充值"
+    @confirm="handleRecharge"
+  />
+</template>
+```
+
+### 仅显示消息(无按钮)
+
+```vue
+<template>
+  <DialogBox 
+    v-model:show="showDialog"
+    icon="success"
+    message="操作成功!"
+    :showButton="false"
+    :closeOnClickOverlay="true"
+  />
+</template>
+```
+
+### 自定义样式
+
+```vue
+<template>
+  <DialogBox 
+    v-model:show="showDialog"
+    icon="error"
+    iconSize="80rpx"
+    message="网络连接失败"
+    btnText="重试"
+    @confirm="handleRetry"
+  />
+</template>
+```
+
+## 工具函数
+
+组件提供了便捷的工具函数来快速创建不同类型的对话框:
+
+```vue
+<script setup>
+import DialogBox from '@/components/DialogBox'
+import { DialogUtils, createSuccessDialog } from '@/components/DialogBox/utils'
+
+const dialogConfig = ref({})
+
+// 使用工具类
+const showSuccess = () => {
+  dialogConfig.value = DialogUtils.success('操作成功!')
+}
+
+// 使用创建函数
+const showInfo = () => {
+  dialogConfig.value = createSuccessDialog('信息提示', {
+    tip: '这是一个提示信息',
+    showTip: true
+  })
+}
+</script>
+
+<template>
+  <DialogBox v-bind="dialogConfig" />
+</template>
+```
+
+### 可用的工具函数
+
+- `DialogUtils.info(message, options?)` - 信息对话框
+- `DialogUtils.success(message, options?)` - 成功对话框
+- `DialogUtils.warning(message, options?)` - 警告对话框
+- `DialogUtils.error(message, options?)` - 错误对话框
+
+## 注意事项
+
+1. 组件使用了 `v-model:show` 进行双向绑定
+2. 消息内容支持换行符 `\n`
+3. 图标名称需要符合 Wot Design Uni 的图标规范
+4. 组件已适配 UnoCSS 原子类样式
+5. 提供了 TypeScript 类型定义,支持完整的类型检查

+ 78 - 0
src/components/DialogBox/types.ts

@@ -0,0 +1,78 @@
+/**
+ * DialogBox 组件相关类型定义
+ */
+
+/** DialogBox 组件 Props */
+export interface DialogBoxProps {
+  /** 是否显示对话框 */
+  show?: boolean
+  /** 按钮文本 */
+  btnText?: string
+  /** 图标名称 */
+  icon?: string
+  /** 图标大小 */
+  iconSize?: string
+  /** 主要消息内容 */
+  message?: string
+  /** 提示信息 */
+  tip?: string
+  /** 是否显示按钮 */
+  showButton?: boolean
+  /** 是否显示提示信息 */
+  showTip?: boolean
+  /** 点击遮罩是否关闭 */
+  closeOnClickOverlay?: boolean
+}
+
+/** DialogBox 组件 Emits */
+export interface DialogBoxEmits {
+  /** 更新显示状态 */
+  (e: 'update:show', value: boolean): void
+  /** 按钮点击事件 */
+  (e: 'confirm'): void
+  /** 对话框关闭事件 */
+  (e: 'close'): void
+}
+
+/** 对话框类型枚举 */
+export enum DialogType {
+  /** 信息提示 */
+  INFO = 'info',
+  /** 成功提示 */
+  SUCCESS = 'success',
+  /** 警告提示 */
+  WARNING = 'warning',
+  /** 错误提示 */
+  ERROR = 'error',
+}
+
+/** 预设对话框配置 */
+export interface DialogPreset {
+  icon: string
+  iconSize?: string
+  btnText?: string
+}
+
+/** 预设对话框配置映射 */
+export const DIALOG_PRESETS: Record<DialogType, DialogPreset> = {
+  [DialogType.INFO]: {
+    icon: 'info-circle',
+    iconSize: '120rpx',
+    btnText: 'Got it',
+  },
+  [DialogType.SUCCESS]: {
+    icon: 'success',
+    iconSize: '120rpx',
+    btnText: 'OK',
+  },
+  [DialogType.WARNING]: {
+    icon: 'warning',
+    iconSize: '120rpx',
+    btnText: 'I know',
+  },
+  [DialogType.ERROR]: {
+    icon: 'error',
+    iconSize: '120rpx',
+    btnText: 'Retry',
+  },
+}

+ 85 - 0
src/components/DialogBox/utils.ts

@@ -0,0 +1,85 @@
+import type { DialogBoxProps } from './types'
+import { DIALOG_PRESETS, DialogType } from './types'
+
+/**
+ * 创建预设对话框配置
+ * @param type 对话框类型
+ * @param message 消息内容
+ * @param options 额外配置选项
+ * @returns 对话框配置
+ */
+export function createDialogConfig(
+  type: DialogType,
+  message: string,
+  options: Partial<DialogBoxProps> = {},
+): DialogBoxProps {
+  const preset = DIALOG_PRESETS[type]
+
+  return {
+    show: true,
+    message,
+    ...preset,
+    ...options,
+  }
+}
+
+/**
+ * 创建信息对话框配置
+ */
+export function createInfoDialog(message: string, options?: Partial<DialogBoxProps>) {
+  return createDialogConfig(DialogType.INFO, message, options)
+}
+
+/**
+ * 创建成功对话框配置
+ */
+export function createSuccessDialog(message: string, options?: Partial<DialogBoxProps>) {
+  return createDialogConfig(DialogType.SUCCESS, message, options)
+}
+
+/**
+ * 创建警告对话框配置
+ */
+export function createWarningDialog(message: string, options?: Partial<DialogBoxProps>) {
+  return createDialogConfig(DialogType.WARNING, message, options)
+}
+
+/**
+ * 创建错误对话框配置
+ */
+export function createErrorDialog(message: string, options?: Partial<DialogBoxProps>) {
+  return createDialogConfig(DialogType.ERROR, message, options)
+}
+
+/**
+ * 对话框工具类
+ */
+export class DialogUtils {
+  /**
+   * 显示信息对话框
+   */
+  static info(message: string, options?: Partial<DialogBoxProps>) {
+    return createInfoDialog(message, options)
+  }
+
+  /**
+   * 显示成功对话框
+   */
+  static success(message: string, options?: Partial<DialogBoxProps>) {
+    return createSuccessDialog(message, options)
+  }
+
+  /**
+   * 显示警告对话框
+   */
+  static warning(message: string, options?: Partial<DialogBoxProps>) {
+    return createWarningDialog(message, options)
+  }
+
+  /**
+   * 显示错误对话框
+   */
+  static error(message: string, options?: Partial<DialogBoxProps>) {
+    return createErrorDialog(message, options)
+  }
+}

+ 9 - 0
src/pages.json

@@ -113,6 +113,15 @@
         "navigationBarBackgroundColor": "#fff"
       }
     },
+    {
+      "path": "pages/productDetail/checkOut",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "Checkout",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
     {
       "path": "pages/productDetail/productDetail",
       "type": "page",

+ 318 - 0
src/pages/productDetail/checkOut.vue

@@ -0,0 +1,318 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: 'Checkout',
+    navigationBarBackgroundColor: '#fff',
+  },
+}
+</route>
+
+<script lang="ts" setup>
+import DialogBox from '@/components/DialogBox/DialogBox.vue'
+import { DialogUtils } from '@/components/DialogBox/utils'
+
+defineOptions({
+  name: 'CheckOut', // 结账页面
+})
+
+// 商品信息
+const productInfo = ref({
+  name: 'BOLON Classic Aviator Polarized Sunglasses, Exclusive Eyewear Brand',
+  color: 'Black Grey',
+  price: 1000.00,
+  quantity: 1,
+  image: '/static/images/avatar.jpg',
+})
+
+// 订单摘要
+const orderSummary = ref({
+  subTotal: 1000.00,
+  voucher: 0.00,
+  total: 1000.00,
+})
+
+// 支付方式
+const paymentMethods = ref([
+  {
+    id: 'bandhubuy',
+    name: 'BandhuBuy Wallet',
+    balance: 2000.00,
+    selected: true,
+    icon: '/static/icons/wallet-icon.png',
+  },
+])
+
+// 选中的支付方式
+const selectedPayment = ref('bandhubuy')
+
+// DialogBox 相关状态
+const dialogConfig = ref({
+  show: false,
+  icon: 'info-circle',
+  iconSize: '120rpx',
+  message: '',
+  tip: '',
+  btnText: 'Got it',
+  showButton: true,
+  showTip: false,
+  closeOnClickOverlay: true,
+})
+
+// 处理支付方式选择
+function selectPayment(methodId: string) {
+  selectedPayment.value = methodId
+  paymentMethods.value.forEach((method) => {
+    method.selected = method.id === methodId
+  })
+}
+
+// 显示余额不足对话框
+function showInsufficientBalanceDialog() {
+  Object.assign(dialogConfig.value, DialogUtils.warning(
+    'Your wallet balance is insufficient.\nPlease recharge!',
+    {
+      tip: 'Recharge Highest Discount 5%',
+      showTip: true,
+      btnText: 'Recharge Now',
+    },
+  ))
+}
+
+// 显示支付成功对话框
+function showPaymentSuccessDialog() {
+  Object.assign(dialogConfig.value, DialogUtils.success(
+    'Payment successful!\nYour order has been placed.',
+    {
+      btnText: 'View Order',
+      showButton: true,
+    },
+  ))
+}
+
+// 显示支付失败对话框
+function showPaymentFailedDialog() {
+  Object.assign(dialogConfig.value, DialogUtils.error(
+    'Payment failed!\nPlease try again or contact support.',
+    {
+      btnText: 'Retry Payment',
+    },
+  ))
+}
+
+// 显示网络错误对话框
+function showNetworkErrorDialog() {
+  Object.assign(dialogConfig.value, DialogUtils.error(
+    'Network connection failed.\nPlease check your network settings.',
+    {
+      btnText: 'Retry',
+      iconSize: '100rpx',
+    },
+  ))
+}
+
+// 显示订单信息对话框
+function showOrderInfoDialog() {
+  Object.assign(dialogConfig.value, DialogUtils.info(
+    'Order Details:\n• Product: Sunglasses\n• Quantity: 1\n• Total: ৳1,000.00',
+    {
+      btnText: 'OK',
+      showButton: true,
+    },
+  ))
+}
+
+// 下单处理
+function handlePlaceOrder() {
+  const selectedMethod = paymentMethods.value.find(method => method.selected)
+
+  if (!selectedMethod) {
+    Object.assign(dialogConfig.value, DialogUtils.info('Please select a payment method'))
+    return
+  }
+
+  // 模拟不同的支付结果
+  const random = Math.random()
+
+  if (random < 0.3) {
+    // 30% 概率余额不足
+    showInsufficientBalanceDialog()
+  }
+  else if (random < 0.7) {
+    // 40% 概率支付成功
+    showPaymentSuccessDialog()
+  }
+  else if (random < 0.9) {
+    // 20% 概率支付失败
+    showPaymentFailedDialog()
+  }
+  else {
+    // 10% 概率网络错误
+    showNetworkErrorDialog()
+  }
+}
+
+// 处理对话框确认事件
+function handleDialogConfirm() {
+  const { icon } = dialogConfig.value
+
+  if (icon === 'warning') {
+    // 余额不足,跳转到充值页面
+    uni.showToast({ title: 'Redirecting to recharge...', icon: 'none' })
+  }
+  else if (icon === 'success') {
+    // 支付成功,跳转到订单页面
+    uni.showToast({ title: 'Redirecting to orders...', icon: 'none' })
+  }
+  else if (icon === 'error') {
+    // 支付失败或网络错误,重新尝试
+    uni.showToast({ title: 'Retrying...', icon: 'loading' })
+  }
+}
+
+// 处理对话框关闭事件
+function handleDialogClose() {
+  console.log('Dialog closed')
+}
+</script>
+
+<template>
+  <z-paging>
+    <view class="pt-20rpx">
+      <view class="mb-20rpx flex items-center gap-24rpx bg-white p-24rpx">
+        <image
+          src="/static/images/avatar.jpg"
+          class="h-160rpx w-160rpx shrink-0"
+        />
+        <view class="flex-1">
+          <view class="line-clamp-2 text-28rpx">
+            SUCGLES for iPhone 14 Plus Case with MagSafe [Ultra Strong Magnetic]
+          </view>
+          <view class="py-4rpx text-24rpx text-#3A444C">
+            Color:Black Grey
+          </view>
+          <view class="flex items-center justify-between text-24rpx">
+            <view class="text-#FF0010">
+              ৳ 300
+            </view>
+            <view class="text-#3A444C">
+              Quantity:1
+            </view>
+          </view>
+        </view>
+      </view>
+      <view class="mb-20rpx bg-white p-24rpx">
+        <view class="mb-12rpx text-28rpx">
+          Oder Summary
+        </view>
+        <view class="flex flex-col gap-16rpx text-#3A444C">
+          <view class="flex items-center justify-between text-24rpx">
+            <text>SubTotal</text>
+            <text>৳1,000.00</text>
+          </view>
+        </view>
+      </view>
+      <view class="bg-white p-24rpx">
+        <view class="mb-12rpx text-28rpx">
+          Select Payment Method
+        </view>
+        <view class="flex flex-col gap-16rpx text-#3A444C">
+          <view class="flex items-center justify-between text-24rpx">
+            <view class="flex items-center">
+              <image
+                src="/static/images/avatar.jpg"
+                class="mr-32rpx h-68rpx w-68rpx rounded-full"
+              />
+              <view class="text-24rpx">
+                <text>BandhuBuy Wallet(</text>
+                <text class="text-[var(--wot-color-theme)]">
+                  Balance:৳2,000.00
+                </text>
+                <text>)</text>
+              </view>
+            </view>
+            <view>
+              <image
+                src="/static/icons/circle-check.png"
+                class="h-36rpx w-36rpx"
+              />
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <template #bottom>
+      <view class="flex items-center justify-end bg-white/60 px-28rpx py-30rpx backdrop-blur-20">
+        <view class="mr-16rpx text-24rpx">
+          <text>Total:</text>
+          <text class="text-[var(--wot-color-theme)]">
+            ৳1,000.00
+          </text>
+        </view>
+        <wd-button @click="handlePlaceOrder">
+          Place Order
+        </wd-button>
+      </view>
+    </template>
+
+    <!-- DialogBox 组件示例 -->
+    <DialogBox
+      v-model:show="dialogConfig.show"
+      :icon="dialogConfig.icon"
+      :icon-size="dialogConfig.iconSize"
+      :message="dialogConfig.message"
+      :tip="dialogConfig.tip"
+      :btn-text="dialogConfig.btnText"
+      :show-button="dialogConfig.showButton"
+      :show-tip="dialogConfig.showTip"
+      :close-on-click-overlay="dialogConfig.closeOnClickOverlay"
+      @confirm="handleDialogConfirm"
+      @close="handleDialogClose"
+    />
+
+    <!-- 测试按钮区域 (开发时使用,生产环境可删除) -->
+    <view class="fixed bottom-100rpx right-40rpx z-999 flex flex-col gap-20rpx">
+      <wd-button
+        size="small"
+        type="primary"
+        @click="showInsufficientBalanceDialog"
+      >
+        余额不足
+      </wd-button>
+      <wd-button
+        size="small"
+        type="success"
+        @click="showPaymentSuccessDialog"
+      >
+        支付成功
+      </wd-button>
+      <wd-button
+        size="small"
+        type="error"
+        @click="showPaymentFailedDialog"
+      >
+        支付失败
+      </wd-button>
+      <wd-button
+        size="small"
+        type="warning"
+        @click="showNetworkErrorDialog"
+      >
+        网络错误
+      </wd-button>
+      <wd-button
+        size="small"
+        type="info"
+        @click="showOrderInfoDialog"
+      >
+        订单信息
+      </wd-button>
+    </view>
+  </z-paging>
+</template>
+
+<style lang="scss" scoped>
+.space-y-24rpx > * + * {
+  margin-top: 24rpx;
+}
+</style>

+ 8 - 2
src/pages/productDetail/productDetail.vue

@@ -96,6 +96,12 @@ const formData = ref({
   num: 1,
 })
 
+function toPage() {
+  uni.navigateTo({
+    url: '/pages/productDetail/checkOut',
+  })
+}
+
 const dataList = ref([])
 function queryList(pageNo, pageSize) {
   // 此处请求仅为演示,请替换为自己项目中的请求
@@ -250,7 +256,7 @@ function queryList(pageNo, pageSize) {
       </view>
     </view>
     <template #bottom>
-      <view class="h-100rpx flex gap-32rpx bg-white bg-opacity-60 px-28rpx backdrop-blur-20">
+      <view class="flex gap-32rpx bg-white/60 px-28rpx py-30rpx 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" />
@@ -333,7 +339,7 @@ function queryList(pageNo, pageSize) {
         <wd-input-number v-model="formData.num" />
       </view>
       <view class="py-24rpx">
-        <wd-button block>
+        <wd-button block @click="toPage">
           Join Group
         </wd-button>
       </view>

二進制
src/static/icons/circle-check.png