# MatchPrizeSet 奖品设置组件

奖品设置组件,支持奖品信息配置、图片上传、数量设置等功能。支持编辑模式和查看模式,提供灵活的表单字段配置,可用于比赛奖品管理场景。适用于多奖品列表展示,支持动态添加、删除奖品。

# 引入

import PressMatchPrizeSet from 'press-next/press-match-prize-set/press-match-prize-set.vue';

# 代码演示

# 基础用法

基础用法展示了组件的基本功能,包括奖品信息展示、编辑和操作。

<template>
  <PressMatchPrizeSet
    v-model:data="prizeData"
    :is-view-mode="false"
    @change="handleChange"
    @delete="handleDelete"
    @add="handleAdd"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { PrizeSetData } from 'press-next/press-match-prize-set/press-match-prize-set.vue';

const prizeData = ref<PrizeSetData>({
  title: '奖项配置',
  name: '王者荣耀皮肤',
  description: '限定皮肤礼包',
  type: '实物奖品',
  image: 'https://placehold.co/74x74',
  quantity: 10,
  quantityType: 'per_person',
});

const handleChange = (data: PrizeSetData) => {
  console.log('奖品数据变更:', data);
};

const handleDelete = () => {
  console.log('删除奖品');
};

const handleAdd = () => {
  console.log('添加奖品');
};
</script>

# 自定义表单字段

通过 formFields 属性可以自定义表单字段的配置,支持输入框和选择器两种类型。

<template>
  <PressMatchPrizeSet
    :data="prizeData"
    :form-fields="customFormFields"
    :is-view-mode="false"
    @change="handleChange"
    @field-select="handleFieldSelect"
  />
  
  <!-- 字段选择器 -->
  <PressActionSheet
    v-model:show="showFieldSheet"
    :actions="fieldActions"
    :title="currentFieldLabel"
    @select="handleFieldValueSelect"
  />
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import type { FormFieldConfig } from 'press-next/press-match-prize-set/press-match-prize-set.vue';

const prizeData = ref({
  title: '一等奖',
  name: '游戏皮肤',
  description: '限定皮肤礼包',
  image: 'https://placehold.co/74x74',
  quantity: 5,
  quantityType: 'total'
});

// 自定义表单字段配置
const customFormFields = ref<FormFieldConfig[]>([
  {
    key: 'name',
    label: '奖项名称',
    placeholder: '请输入奖项名称',
    type: 'input',
  },
  {
    key: 'description',
    label: '奖品名称',
    placeholder: '请输入奖品名称',
    type: 'input',
  },
  {
    key: 'type',
    label: '奖品类型',
    type: 'select',
    selectedValue: '实物奖品', // 回选值
    defaultText: '请选择',
  },
  {
    key: 'level',
    label: '奖品等级',
    type: 'select',
    selectedValue: '',
    defaultText: '请选择等级',
  },
]);

const showFieldSheet = ref(false);
const currentFieldKey = ref('');
const currentFieldLabel = ref('');

// 字段选择器选项
const fieldOptionsMap: Record<string, { name: string; value: string }[]> = {
  type: [
    { name: '实物奖品', value: '实物奖品' },
    { name: '虚拟奖品', value: '虚拟奖品' },
    { name: '现金红包', value: '现金红包' },
  ],
  level: [
    { name: '一等奖', value: '一等奖' },
    { name: '二等奖', value: '二等奖' },
    { name: '三等奖', value: '三等奖' },
  ],
};

const fieldActions = computed(() => fieldOptionsMap[currentFieldKey.value] || []);

const handleChange = () => {
  console.log('奖品数据变更');
};

const handleFieldSelect = (fieldKey: string) => {
  currentFieldKey.value = fieldKey;
  const field = customFormFields.value.find(f => f.key === fieldKey);
  if (field) {
    currentFieldLabel.value = `选择${field.label}`;
    showFieldSheet.value = true;
  }
};

const handleFieldValueSelect = (action: { value: string }) => {
  const fieldIndex = customFormFields.value.findIndex(f => f.key === currentFieldKey.value);
  if (fieldIndex !== -1) {
    customFormFields.value[fieldIndex].selectedValue = action.value;
  }
  showFieldSheet.value = false;
};
</script>

# 多奖品列表

支持多个奖品的统一管理,只在第一个组件显示标题,只在最后一个组件显示"添加奖品"按钮。

<template>
  <div class="prize-list">
    <PressMatchPrizeSet
      v-for="(prize, index) in prizeList"
      :key="prize.id"
      v-model:data="prizeList[index]"
      :prize-title="index === 0 ? '奖品设置' : ''"
      :show-title="index === 0"
      :is-last-item="index === prizeList.length - 1"
      :is-view-mode="false"
      @change="handleChange(index, $event)"
      @delete="handleDelete(index)"
      @add="handleAdd"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { PrizeSetData } from 'press-next/press-match-prize-set/press-match-prize-set.vue';

const prizeList = ref<PrizeSetData[]>([
  {
    id: 1,
    title: '一等奖',
    name: '王者荣耀限定皮肤',
    description: '赛季限定皮肤礼包',
    type: '实物奖品',
    image: 'https://placehold.co/74x74',
    quantity: 5,
    quantityType: 'total',
  },
  {
    id: 2,
    title: '二等奖',
    name: '游戏点券',
    description: '500点券',
    type: '虚拟奖品',
    image: 'https://placehold.co/74x74',
    quantity: 10,
    quantityType: 'total',
  },
]);

const handleChange = (index: number, data: PrizeSetData) => {
  console.log(`奖品 ${index + 1} 数据变更:`, data);
};

const handleDelete = (index: number) => {
  if (prizeList.value.length > 1) {
    prizeList.value.splice(index, 1);
  }
};

const handleAdd = () => {
  const newId = Math.max(...prizeList.value.map(p => Number(p.id) || 0)) + 1;
  const prizeNames = ['一等奖', '二等奖', '三等奖', '四等奖', '五等奖'];
  const title = prizeNames[prizeList.value.length] || `奖项${prizeList.value.length + 1}`;
  
  prizeList.value.push({
    id: newId,
    title,
    name: '',
    description: '',
    type: '',
    image: 'https://placehold.co/74x74',
    quantity: 1,
    quantityType: 'per_person',
  });
};
</script>

# 查看模式

查看模式下,所有编辑功能被禁用,选择器类型字段自动隐藏,仅显示只读的数据内容。

<template>
  <PressMatchPrizeSet
    :data="viewModeData"
    :is-view-mode="true"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { PrizeSetData } from 'press-next/press-match-prize-set/press-match-prize-set.vue';

const viewModeData = ref<PrizeSetData>({
  title: '一等奖',
  name: '王者荣耀限定皮肤',
  description: '赛季限定皮肤礼包 + 专属头像框',
  type: '实物奖品',
  image: 'https://placehold.co/74x74',
  quantity: 5,
  quantityType: 'total',
});
</script>

# 完整功能示例

<template>
  <PressMatchPrizeSet
    :data="prizeData"
    :form-fields="customFormFields"
    :is-view-mode="false"
    custom-class="custom-prize-set"
    @change="handleChange"
    @delete="handleDelete"
    @add="handleAdd"
    @image-edit="handleImageEdit"
    @field-select="handleFieldSelect"
    @quantity-type-change="handleQuantityTypeChange"
  />
  
  <!-- 字段选择器 -->
  <PressActionSheet
    v-model:show="showFieldSheet"
    :actions="fieldActions"
    :title="currentFieldLabel"
    @select="handleFieldValueSelect"
  />
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import type { FormFieldConfig } from 'press-next/press-match-prize-set/press-match-prize-set.vue';

const prizeData = ref({
  id: '1',
  title: '一等奖',
  name: '游戏皮肤',
  description: '限定皮肤礼包',
  image: 'https://placehold.co/74x74',
  quantity: 5,
  quantityType: 'total'
});

const customFormFields = ref<FormFieldConfig[]>([
  { key: 'name', label: '奖项名称', type: 'input', placeholder: '请输入奖项名称' },
  { key: 'description', label: '奖品名称', type: 'input', placeholder: '请输入奖品名称' },
  { key: 'type', label: '奖品类型', type: 'select', selectedValue: '实物奖品' },
  { key: 'level', label: '奖品等级', type: 'select', selectedValue: '' },
]);

const showFieldSheet = ref(false);
const currentFieldKey = ref('');
const currentFieldLabel = ref('');

const fieldOptionsMap: Record<string, { name: string; value: string }[]> = {
  type: [
    { name: '实物奖品', value: '实物奖品' },
    { name: '虚拟奖品', value: '虚拟奖品' },
  ],
  level: [
    { name: '一等奖', value: '一等奖' },
    { name: '二等奖', value: '二等奖' },
  ],
};

const fieldActions = computed(() => fieldOptionsMap[currentFieldKey.value] || []);

const handleChange = () => {
  console.log('奖品数据变更');
};

const handleDelete = () => {
  console.log('删除奖品');
};

const handleAdd = () => {
  console.log('添加奖品');
};

const handleImageEdit = () => {
  console.log('编辑奖品图片');
};

const handleFieldSelect = (fieldKey: string) => {
  currentFieldKey.value = fieldKey;
  const field = customFormFields.value.find(f => f.key === fieldKey);
  if (field) {
    currentFieldLabel.value = `选择${field.label}`;
    showFieldSheet.value = true;
  }
};

const handleFieldValueSelect = (action: { value: string }) => {
  const fieldIndex = customFormFields.value.findIndex(f => f.key === currentFieldKey.value);
  if (fieldIndex !== -1) {
    customFormFields.value[fieldIndex].selectedValue = action.value;
  }
  showFieldSheet.value = false;
};

const handleQuantityTypeChange = (type: 'per_person' | 'total') => {
  console.log('数量类型变更:', type);
};
</script>

# 自定义样式

<template>
  <PressMatchPrizeSet
    :data="prizeData"
    custom-class="custom-prize-set"
  />
</template>

<style>
.custom-prize-set {
  --pmps-card-bg: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%);
  --pmps-title-text-color: #1890ff;
  --pmps-image-size: 2rem;
}
</style>

# API

# Props

参数 说明 类型 默认值
data 奖品数据 PrizeSetData {}
prize-title 奖品标题(显示在组件顶部) string '奖品配置'
show-title 是否显示标题 boolean false
is-last-item 是否为最后一项(控制"添加奖品"按钮显示) boolean false
is-view-mode 是否为查看模式 boolean false
custom-class 自定义样式类名 string ''
form-fields 表单字段配置 FormFieldConfig[] 默认配置

# Data 数据结构

interface PrizeSetData {
  id?: string | number;        // 奖品唯一标识
  title?: string;              // 奖项标题(如:一等奖、二等奖)
  name: string;                // 奖项名称
  description: string;         // 奖品描述
  type?: string;               // 奖品类型
  image: string;               // 奖品图片
  quantity: number;            // 数量
  quantityType?: 'per_person' | 'total';  // 数量类型
  [key: string]: string | number | undefined; // 支持动态扩展字段
}

# FormFieldConfig 表单字段配置

interface FormFieldConfig {
  key: string;              // 字段键名
  label: string;            // 字段标签
  placeholder?: string;     // 占位符(仅input类型)
  type?: 'input' | 'select'; // 字段类型
  selectedValue?: string;   // 选择器回选值(仅select类型)
  defaultText?: string;     // 选择器默认文本(仅select类型)
}

默认表单字段配置

[
  {
    key: 'name',
    label: '奖项名称',
    placeholder: '请输入奖项名称',
    type: 'input',
  },
  {
    key: 'description',
    label: '奖品',
    placeholder: '请输入奖品名称',
    type: 'input',
  },
  {
    key: 'type',
    label: '奖品类型',
    type: 'select',
    selectedValue: '',
    defaultText: '请选择',
  },
]

# Events

事件名 说明 回调参数
change 奖品数据变更时触发 data: PrizeSetData - 变更后的完整数据
update:data v-model 双向绑定事件 data: PrizeSetData - 最新数据
delete 点击删除按钮时触发 -
add 点击添加按钮时触发 -
image-edit 点击编辑图片时触发 -
field-select 点击选择器字段时触发 fieldKey: string - 字段键名
quantity-type-change 数量类型变更时触发 type: 'per_person' | 'total'

# 样式变量

组件提供了下列 CSS 变量,可用于自定义样式。

名称 默认值 说明
--pmps-title-mb $spacing-lg 标题下边距
--pmps-delete-mr $spacing-sm 删除按钮右边距
--pmps-content-padding-tb $spacing-md 内容区域上下内边距
--pmps-content-padding-lr $spacing-lg 内容区域左右内边距
--pmps-editor-label-mr $spacing-md 编辑器标签右边距
--pmps-form-gap $spacing-sm 表单项间距
--pmps-editor-gap $spacing-lg 编辑器区域间距
--pmps-editor-item-gap .4rem 编辑器项间距
--pmps-add-mt $spacing-lg 添加按钮上边距
--pmps-add-gap $spacing-sm 添加按钮内部间距
--pmps-editor-mt $spacing-md 编辑器上边距
--pmps-prize-set-mb $spacing-lg 组件下边距
--pmps-editor-ctrls-icon-size .4rem 操作图标尺寸
--pmps-form-item-height .9rem 表单项高度
--pmps-form-label-width 1.52rem 表单标签宽度
--pmps-editor-item-height .5rem 编辑器项高度
--pmps-image-size 1.4rem 图片尺寸
--pmps-image-edit-size .4rem 图片编辑图标尺寸
--pmps-tabs-item-width 1rem 标签页项宽度
--pmps-tabs-item-height .5rem 标签页项高度
--pmps-placeholder-width 3.6rem 占位符宽度
--pmps-card-bg linear-gradient(...) 卡片背景渐变
--pmps-card-border-color $color-surface-brand-light2 卡片边框颜色
--pmps-title-text-color $color-text-primary 标题文字颜色
--pmps-primary-text-color $color-text-primary 主要文字颜色
--pmps-secondary-text-color $color-text-invert-default 次要文字颜色
--pmps-placeholder-text-color $color-text-primary 占位符文字颜色
--pmps-add-text-color $color-text-primary 添加按钮文字颜色
--pmps-add-icon-color $color-text-primary 添加按钮图标颜色
--pmps-image-edit-text-color $color-text-invert-light 图片编辑文字颜色
--pmps-edit-overlay-bg $color-surface-dark 编辑遮罩背景
--pmps-cell-border-color $color-divider-light 单元格边框颜色
--pmps-image-bg $color-surface-secondary 图片背景
--pmps-image-border-color $color-divider-default 图片边框颜色
--pmps-tabs-border-color $color-border-brand 标签页边框颜色
--pmps-tabs-active-bg $color-surface-brand 标签页激活背景
--pmps-tabs-text-color $color-text-primary 标签页文字颜色
--pmps-tabs-active-text-color $color-text-invert-light 标签页激活文字颜色
--pmps-cell-border-color $color-divider-light 单元格边框颜色
--pmps-ctrls-text-color $color-icon-default 控制按钮文字颜色
--pmps-image-bg $color-surface-secondary 图片背景
--pmps-image-border-color $color-divider-default 图片边框颜色
--pmps-add-text-color $color-text-primary 添加按钮文字颜色
--pmps-add-icon-color $color-text-primary 添加按钮图标颜色
--pmps-stepper-input-bg rgba(#eceff2, .4) 步进器输入框背景
--pmps-input-text-color $color-text-primary 输入框文字颜色
--pmps-input-placeholder-color $color-text-invert-default 输入框占位符颜色
--pmps-title-font-size $font-size-lg 标题字体大小
--pmps-label-font-size $font-size-md 标签字体大小
--pmps-text-font-size $font-size-md 文本字体大小
--pmps-placeholder-font-size $font-size-md 占位符字体大小
--pmps-add-font-size $font-size-md 添加按钮字体大小
--pmps-input-font-size $font-size-md 输入框字体大小
--pmps-icon-font-size $font-size-md 图标字体大小
--pmps-tabs-font-size $font-size-sm 标签页字体大小
--pmps-content-border-radius $border-radius-xs 内容区域圆角
--pmps-image-border-radius $border-radius-none 图片圆角

# 注意事项

  1. 数据绑定

    • 组件支持 v-model:data 双向绑定
    • 所有字段变更会自动触发 changeupdate:data 事件
    • 外部数据变化会自动同步到组件内部
  2. 查看模式

    • 设置 is-view-modetrue 时,所有编辑功能将被禁用
    • 查看模式下会自动过滤掉 type: 'select' 的字段
    • 输入框字段显示为只读状态
  3. 数量类型

    • per_person:每人可获得的数量
    • total:总共的数量份额
  4. 图片上传

    • 点击图片编辑按钮会触发 image-edit 事件
    • 需自行实现图片上传逻辑和更新 data.image 字段
  5. 表单字段配置

    • 支持 inputselect 两种字段类型
    • select 类型字段需配合 field-select 事件使用
    • 通过 selectedValue 实现选择器回选功能
    • 可通过索引签名 [key: string] 支持动态扩展字段
  6. 类型字段

    • 组件已内置 type 字段支持
    • 可通过 formFields 配置选择器类型的奖品类型字段
    • 查看模式下选择器字段会自动隐藏

# 常见问题

# 如何自定义表单字段?

const customFormFields = ref<FormFieldConfig[]>([
  {
    key: 'name',
    label: '奖项名称',
    placeholder: '请输入奖项名称',
    type: 'input',
  },
  {
    key: 'level',
    label: '奖品等级',
    type: 'select',
    selectedValue: '一等奖', // 设置默认回选值
    defaultText: '请选择等级',
  },
]);

# 如何处理选择器字段?

const handleFieldSelect = (fieldKey: string) => {
  // fieldKey 为字段的 key 值,如 'type', 'level'
  // 可根据 fieldKey 显示不同的选择器选项
  const field = customFormFields.value.find(f => f.key === fieldKey);
  if (field) {
    // 显示对应的选择器(如 ActionSheet、Picker 等)
    showFieldSheet.value = true;
  }
};

// 选择器回调
const handleFieldValueSelect = (value: string) => {
  // 更新对应字段的回选值
  const fieldIndex = customFormFields.value.findIndex(f => f.key === currentFieldKey.value);
  if (fieldIndex !== -1) {
    customFormFields.value[fieldIndex].selectedValue = value;
  }
};

# 如何自定义卡片背景?

.custom-prize-set {
  --pmps-card-bg: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%);
}

# 如何修改图片尺寸?

.custom-prize-set {
  --pmps-image-size: 2rem;
}

# 如何处理图片上传?

const handleImageEdit = () => {
  // 调用图片选择器
  uni.chooseImage({
    count: 1,
    success: (res) => {
      // 上传图片到服务器
      uploadImage(res.tempFilePaths[0]).then((url) => {
        prizeData.value.image = url;
      });
    }
  });
};

# 如何验证奖品数据?

const handleChange = () => {
  if (!prizeData.value.name) {
    uni.showToast({ title: '请输入奖项名称', icon: 'none' });
    return;
  }
  if (!prizeData.value.description) {
    uni.showToast({ title: '请输入奖品名称', icon: 'none' });
    return;
  }
  if (prizeData.value.quantity < 1) {
    uni.showToast({ title: '数量不能小于1', icon: 'none' });
    return;
  }
  // 验证通过,保存数据
  savePrizeData();
};