# MatchPrizeSet Prize Setting Component

Prize setting component that supports prize information configuration, image upload, quantity setting and other functions. Supports edit mode and view mode with flexible form field configuration, suitable for competition prize management scenarios. Suitable for multi-prize list display with support for dynamic adding and deleting prizes.

# Import

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

# Code Examples

# Basic Usage

Basic usage demonstrates the component's core features, including prize information display, editing, and operations.

<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: 'Prize Configuration',
  name: 'Game Skin',
  description: 'Limited Skin Pack',
  type: 'Physical Prize',
  image: 'https://placehold.co/74x74',
  quantity: 10,
  quantityType: 'per_person',
});

const handleChange = (data: PrizeSetData) => {
  console.log('Prize data changed:', data);
};

const handleDelete = () => {
  console.log('Delete prize');
};

const handleAdd = () => {
  console.log('Add prize');
};
</script>

# Custom Form Fields

Use the formFields property to customize form field configuration, supporting both input and select types.

<template>
  <PressMatchPrizeSet
    :data="prizeData"
    :form-fields="customFormFields"
    :is-view-mode="false"
    @change="handleChange"
    @field-select="handleFieldSelect"
  />
  
  <!-- Field Selector -->
  <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: 'First Prize',
  name: 'Game Skin',
  description: 'Limited Skin Pack',
  image: 'https://placehold.co/74x74',
  quantity: 5,
  quantityType: 'total'
});

// Custom form field configuration
const customFormFields = ref<FormFieldConfig[]>([
  {
    key: 'name',
    label: 'Prize Name',
    placeholder: 'Enter prize name',
    type: 'input',
  },
  {
    key: 'description',
    label: 'Prize Description',
    placeholder: 'Enter prize description',
    type: 'input',
  },
  {
    key: 'type',
    label: 'Prize Type',
    type: 'select',
    selectedValue: 'Physical Prize', // Selected value
    defaultText: 'Please select',
  },
  {
    key: 'level',
    label: 'Prize Level',
    type: 'select',
    selectedValue: '',
    defaultText: 'Select level',
  },
]);

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

// Field selector options
const fieldOptionsMap: Record<string, { name: string; value: string }[]> = {
  type: [
    { name: 'Physical Prize', value: 'Physical Prize' },
    { name: 'Virtual Prize', value: 'Virtual Prize' },
    { name: 'Cash Reward', value: 'Cash Reward' },
  ],
  level: [
    { name: 'First Prize', value: 'First Prize' },
    { name: 'Second Prize', value: 'Second Prize' },
    { name: 'Third Prize', value: 'Third Prize' },
  ],
};

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

const handleChange = () => {
  console.log('Prize data changed');
};

const handleFieldSelect = (fieldKey: string) => {
  currentFieldKey.value = fieldKey;
  const field = customFormFields.value.find(f => f.key === fieldKey);
  if (field) {
    currentFieldLabel.value = `Select ${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>

# Multiple Prize List

Supports unified management of multiple prizes, showing title only in the first component and "Add Prize" button only in the last component.

<template>
  <div class="prize-list">
    <PressMatchPrizeSet
      v-for="(prize, index) in prizeList"
      :key="prize.id"
      v-model:data="prizeList[index]"
      :prize-title="index === 0 ? 'Prize Settings' : ''"
      :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: 'First Prize',
    name: 'Limited Game Skin',
    description: 'Season Limited Skin Pack',
    type: 'Physical Prize',
    image: 'https://placehold.co/74x74',
    quantity: 5,
    quantityType: 'total',
  },
  {
    id: 2,
    title: 'Second Prize',
    name: 'Game Points',
    description: '500 Points',
    type: 'Virtual Prize',
    image: 'https://placehold.co/74x74',
    quantity: 10,
    quantityType: 'total',
  },
]);

const handleChange = (index: number, data: PrizeSetData) => {
  console.log(`Prize ${index + 1} data changed:`, 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 = ['First Prize', 'Second Prize', 'Third Prize', 'Fourth Prize', 'Fifth Prize'];
  const title = prizeNames[prizeList.value.length] || `Prize ${prizeList.value.length + 1}`;
  
  prizeList.value.push({
    id: newId,
    title,
    name: '',
    description: '',
    type: '',
    image: 'https://placehold.co/74x74',
    quantity: 1,
    quantityType: 'per_person',
  });
};
</script>

# View Mode

In view mode, all editing features are disabled, selector type fields are automatically hidden, and only read-only data content is displayed.

<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: 'First Prize',
  name: 'Limited Game Skin',
  description: 'Season Limited Skin Pack + Exclusive Avatar Frame',
  type: 'Physical Prize',
  image: 'https://placehold.co/74x74',
  quantity: 5,
  quantityType: 'total',
});
</script>

# Complete Feature Example

<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"
  />
  
  <!-- Field Selector -->
  <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: 'First Prize',
  name: 'Game Skin',
  description: 'Limited Skin Pack',
  image: 'https://placehold.co/74x74',
  quantity: 5,
  quantityType: 'total'
});

const customFormFields = ref<FormFieldConfig[]>([
  { key: 'name', label: 'Prize Name', type: 'input', placeholder: 'Enter prize name' },
  { key: 'description', label: 'Prize Description', type: 'input', placeholder: 'Enter description' },
  { key: 'type', label: 'Prize Type', type: 'select', selectedValue: 'Physical Prize' },
  { key: 'level', label: 'Prize Level', type: 'select', selectedValue: '' },
]);

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

const fieldOptionsMap: Record<string, { name: string; value: string }[]> = {
  type: [
    { name: 'Physical Prize', value: 'Physical Prize' },
    { name: 'Virtual Prize', value: 'Virtual Prize' },
  ],
  level: [
    { name: 'First Prize', value: 'First Prize' },
    { name: 'Second Prize', value: 'Second Prize' },
  ],
};

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

const handleChange = () => {
  console.log('Prize data changed');
};

const handleDelete = () => {
  console.log('Delete prize');
};

const handleAdd = () => {
  console.log('Add prize');
};

const handleImageEdit = () => {
  console.log('Edit prize image');
};

const handleFieldSelect = (fieldKey: string) => {
  currentFieldKey.value = fieldKey;
  const field = customFormFields.value.find(f => f.key === fieldKey);
  if (field) {
    currentFieldLabel.value = `Select ${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('Quantity type changed:', type);
};
</script>

# Custom Styling

<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

Attribute Description Type Default
data Prize data PrizeSetData {}
prize-title Prize title (displayed at top of component) string 'Prize Configuration'
show-title Whether to show title boolean false
is-last-item Whether it's the last item (controls "Add Prize" button display) boolean false
is-view-mode Whether in view mode boolean false
custom-class Custom style class name string ''
form-fields Form field configuration FormFieldConfig[] Default config

# Data Structure

interface PrizeSetData {
  id?: string | number;        // Unique prize identifier
  title?: string;              // Prize title (e.g., First Prize, Second Prize)
  name: string;                // Prize name
  description: string;         // Prize description
  type?: string;               // Prize type
  image: string;               // Prize image
  quantity: number;            // Quantity
  quantityType?: 'per_person' | 'total';  // Quantity type
  [key: string]: string | number | undefined; // Support dynamic fields
}

# FormFieldConfig Form Field Configuration

interface FormFieldConfig {
  key: string;              // Field key
  label: string;            // Field label
  placeholder?: string;     // Placeholder (input type only)
  type?: 'input' | 'select'; // Field type
  selectedValue?: string;   // Selected value (select type only)
  defaultText?: string;     // Default text (select type only)
}

Default form field configuration:

[
  {
    key: 'name',
    label: 'Prize Name',
    placeholder: 'Enter prize name',
    type: 'input',
  },
  {
    key: 'description',
    label: 'Prize',
    placeholder: 'Enter prize description',
    type: 'input',
  },
  {
    key: 'type',
    label: 'Prize Type',
    type: 'select',
    selectedValue: '',
    defaultText: 'Please select',
  },
]

# Events

Event Description Parameters
change Triggered when prize data changes data: PrizeSetData - Complete data after change
update:data v-model two-way binding event data: PrizeSetData - Latest data
delete Triggered when delete button is clicked -
add Triggered when add button is clicked -
image-edit Triggered when edit image is clicked -
field-select Triggered when selector field is clicked fieldKey: string - Field key
quantity-type-change Triggered when quantity type changes type: 'per_person' | 'total'

# Style Variables

The component provides the following CSS variables for custom styling.

Name Default Value Description
--pmps-title-mb $spacing-lg Title bottom margin
--pmps-delete-mr $spacing-sm Delete button right margin
--pmps-content-padding-tb $spacing-md Content area vertical padding
--pmps-content-padding-lr $spacing-lg Content area horizontal padding
--pmps-editor-label-mr $spacing-md Editor label right margin
--pmps-form-gap $spacing-sm Form item gap
--pmps-editor-gap $spacing-lg Editor area gap
--pmps-editor-item-gap .4rem Editor item gap
--pmps-add-mt $spacing-lg Add button top margin
--pmps-add-gap $spacing-sm Add button internal gap
--pmps-editor-mt $spacing-md Editor top margin
--pmps-prize-set-mb $spacing-lg Component bottom margin
--pmps-editor-ctrls-icon-size .4rem Control icon size
--pmps-form-item-height .9rem Form item height
--pmps-form-label-width 1.52rem Form label width
--pmps-editor-item-height .5rem Editor item height
--pmps-image-size 1.4rem Image size
--pmps-image-edit-size .4rem Image edit icon size
--pmps-ctrls-icon-size .4rem Control button icon size
--pmps-tabs-item-width 1rem Tab item width
--pmps-tabs-item-height .5rem Tab item height
--pmps-card-bg linear-gradient(...) Card background gradient
--pmps-card-border-color $color-surface-brand-light2 Card border color
--pmps-title-text-color $color-text-primary Title text color
--pmps-primary-text-color $color-text-primary Primary text color
--pmps-secondary-text-color $color-text-invert-default Secondary text color
--pmps-image-edit-text-color $color-text-invert-light Image edit text color
--pmps-edit-overlay-bg rgba(0, 0, 0, .41) Edit overlay background
--pmps-tabs-border-color $color-border-brand Tab border color
--pmps-tabs-active-bg $color-surface-brand Tab active background
--pmps-tabs-text-color $color-text-primary Tab text color
--pmps-tabs-active-text-color $color-text-invert-light Tab active text color
--pmps-cell-border-color $color-divider-light Cell border color
--pmps-ctrls-text-color $color-text-primary Control button text color
--pmps-image-border-color $color-divider-default Image border color
--pmps-image-bg $color-surface-secondary Image background
--pmps-add-text-color $color-text-primary Add button text color
--pmps-add-icon-color $color-text-primary Add button icon color
--pmps-stepper-input-bg rgba(#eceff2, .4) Stepper input background
--pmps-title-font-size $font-size-lg Title font size
--pmps-label-font-size $font-size-md Label font size
--pmps-text-font-size $font-size-md Text font size
--pmps-icon-font-size $font-size-md IconPlus font size
--pmps-tabs-font-size $font-size-sm Tab font size
--pmps-add-font-size $font-size-md Add button font size
--pmps-image-border-radius $border-radius-none Image border radius
--pmps-content-border-radius $border-radius-xs Content area border radius

# Notes

  1. Data Binding:

    • Component supports v-model:data two-way binding
    • All field changes automatically trigger change and update:data events
    • External data changes are automatically synchronized to the component
  2. View Mode:

    • When is-view-mode is set to true, all editing functions are disabled
    • View mode automatically filters out fields with type: 'select'
    • Input fields are displayed in read-only state
  3. Quantity Type:

    • per_person: Quantity per person can receive
    • total: Total quantity share
  4. Image Upload:

    • Clicking the image edit button triggers the image-edit event
    • Implement image upload logic and update the data.image field yourself
  5. Form Field Configuration:

    • Supports input and select field types
    • select type fields should be used with the field-select event
    • Use selectedValue to implement selector value backfill
    • Support dynamic field extension via index signature [key: string]
  6. Type Field:

    • Component has built-in type field support
    • Configure prize type field as selector type via formFields
    • Selector fields are automatically hidden in view mode

# FAQ

# How to customize form fields?

const customFormFields = ref<FormFieldConfig[]>([
  {
    key: 'name',
    label: 'Prize Name',
    placeholder: 'Enter prize name',
    type: 'input',
  },
  {
    key: 'level',
    label: 'Prize Level',
    type: 'select',
    selectedValue: 'First Prize', // Set default selected value
    defaultText: 'Select level',
  },
]);

# How to handle selector fields?

const handleFieldSelect = (fieldKey: string) => {
  // fieldKey is the key of the field, such as 'type', 'level'
  // Display different selector options based on fieldKey
  const field = customFormFields.value.find(f => f.key === fieldKey);
  if (field) {
    // Show corresponding selector (such as ActionSheet, Picker, etc.)
    showFieldSheet.value = true;
  }
};

// Selector callback
const handleFieldValueSelect = (value: string) => {
  // Update the selected value of the corresponding field
  const fieldIndex = customFormFields.value.findIndex(f => f.key === currentFieldKey.value);
  if (fieldIndex !== -1) {
    customFormFields.value[fieldIndex].selectedValue = value;
  }
};

# How to customize card background?

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

# How to modify image size?

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

# How to handle image upload?

const handleImageEdit = () => {
  // Call image picker
  uni.chooseImage({
    count: 1,
    success: (res) => {
      // Upload image to server
      uploadImage(res.tempFilePaths[0]).then((url) => {
        prizeData.value.image = url;
      });
    }
  });
};

# How to validate prize data?

const handleChange = () => {
  if (!prizeData.value.name) {
    uni.showToast({ title: 'Please enter prize name', icon: 'none' });
    return;
  }
  if (!prizeData.value.description) {
    uni.showToast({ title: 'Please enter prize description', icon: 'none' });
    return;
  }
  if (prizeData.value.quantity < 1) {
    uni.showToast({ title: 'Quantity cannot be less than 1', icon: 'none' });
    return;
  }
  // Validation passed, save data
  savePrizeData();
};