This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
import type { ExtractPropTypes } from 'vue'
import { UPDATE_MODEL_EVENT } from '../_constants'
import {
commonProps,
isNumber,
isString,
makeArrayProp,
makeNumberProp,
makeNumericProp,
makeStringProp,
truthProp,
} from '../_utils'
import type { EcardChangeEvent, EcardDataItem, EcardDataValue } from './type'
export const ecardProps = {
...commonProps,
/**
* @description 购买电子卡所需价钱
*/
modelValue: makeNumericProp(0),
/**
* @description 电子卡面值列表
*/
list: makeArrayProp<EcardDataItem>([]),
/**
* @description 选择面值文案
*/
chooseText: makeStringProp(''),
/**
* @description 是否显示其他面值
*/
showOther: truthProp,
/**
* @description 其他面值文案
*/
otherValueText: makeStringProp(''),
/**
* @description 其他面值默认提示语
*/
placeholder: makeStringProp(''),
/**
* @description 符号标示
*/
suffix: makeStringProp('¥'),
/**
* @description 其它面值最小值
*/
cardAmountMin: makeNumericProp(1),
/**
* @description 其他面值最大值
*/
cardAmountMax: makeNumericProp(9999),
/**
* @description 是否显示步进
*/
showStep: truthProp,
/**
* @description 购买数量最小值
*/
cardBuyMin: makeNumberProp(1),
/**
* @description 购买数量最大值
*/
cardBuyMax: makeNumberProp(9999),
}
export type ECardProps = ExtractPropTypes<typeof ecardProps>
export const ecardEmits = {
[UPDATE_MODEL_EVENT]: (val: EcardDataValue) => isNumber(val) || isString(val),
update: (val: EcardDataValue) => isNumber(val) || isString(val),
change: (evt: EcardChangeEvent) => evt instanceof Object,
inputChange: (val: string) => isString(val),
changeStep: (val1: number, val2: EcardDataValue) => isNumber(val1) && (isNumber(val2) || isString(val2)),
inputClick: () => true,
}
export type ECardEmits = typeof ecardEmits

View File

@@ -0,0 +1,205 @@
<script setup lang="ts">
import type { InputOnInputEvent } from '@uni-helper/uni-app-types'
import { computed, defineComponent, nextTick, ref } from 'vue'
import { PREFIX, UPDATE_MODEL_EVENT } from '../_constants'
import { getMainClass } from '../_utils'
import { useTranslate } from '../../locale'
import NutInputNumber from '../inputnumber/inputnumber.vue'
import { ecardEmits, ecardProps } from './ecard'
import type { EcardDataItem, EcardDataValue, EcardUpdateOptions } from './type'
const props = defineProps(ecardProps)
const emit = defineEmits(ecardEmits)
const innerValue = ref<EcardDataValue>(0)
const currentIndex = ref<number | null>(null)
const inputValue = ref<string>('')
const innerCount = ref<number>(props.cardBuyMin)
const finalValue = computed(() => {
return Number(innerValue.value) * innerCount.value
})
const classes = computed(() => {
return getMainClass(props, componentName)
})
async function forceUpdateInputValue(value: string) {
if (value !== inputValue.value) {
inputValue.value = value
return
}
inputValue.value = value.slice(0, -1)
await nextTick()
inputValue.value = value
}
function handleClick(item: EcardDataItem, index: number) {
innerValue.value = item.price
currentIndex.value = index
forceUpdateInputValue('')
innerCount.value = props.cardBuyMin
emit('change', item)
emit(UPDATE_MODEL_EVENT, finalValue.value)
emit('update', finalValue.value)
}
function handleInputValue(value: number) {
if (value > Number(props.cardAmountMax))
return props.cardAmountMax
if (value < Number(props.cardAmountMin))
return props.cardAmountMin
return value
}
function handleInputClick() {
emit('inputClick')
if (currentIndex.value === -1)
return
innerValue.value = 0
currentIndex.value = -1
forceUpdateInputValue('')
innerCount.value = props.cardBuyMin
emit(UPDATE_MODEL_EVENT, finalValue.value)
emit('update', finalValue.value)
}
function handleInputChange(event: InputOnInputEvent) {
const value = Number(event.detail.value.replace(/\D/g, ''))
const valued = handleInputValue(value)
const stringValued = String(valued)
forceUpdateInputValue(stringValued)
innerValue.value = valued
emit('inputChange', stringValued)
emit(UPDATE_MODEL_EVENT, finalValue.value)
emit('update', finalValue.value)
}
function handleStepChange(value: number | string) {
innerCount.value = Number(value)
emit('changeStep', innerCount.value, innerValue.value)
emit(UPDATE_MODEL_EVENT, finalValue.value)
emit('update', finalValue.value)
}
function update(options: EcardUpdateOptions) {
const { index, input, count } = options
if (index !== undefined) {
if (index != null && (index < -1 || index >= props.list.length))
return
currentIndex.value = index
if (index == null || index === -1) {
if (input != null) {
innerValue.value = Number(input)
forceUpdateInputValue(input)
}
}
else {
innerValue.value = props.list[index].price
forceUpdateInputValue('')
}
}
if (count != null)
innerCount.value = count
emit(UPDATE_MODEL_EVENT, finalValue.value)
emit('update', finalValue.value)
}
defineExpose({
update,
})
</script>
<script lang="ts">
const componentName = `${PREFIX}-ecard`
const { translate } = useTranslate(componentName)
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="classes" :style="props.customStyle">
<view class="nut-ecard__title">
{{ props.chooseText || translate('chooseText') }}
</view>
<view class="nut-ecard__list">
<view
v-for="(item, index) in props.list"
:key="index"
class="nut-ecard__list__item"
:class="{ active: currentIndex === index }"
@click="handleClick(item, index)"
>
{{ item.price }}
</view>
<view
v-if="props.showOther"
class="nut-ecard__list__input"
:class="{ active: currentIndex === -1 }"
@click="handleInputClick"
>
<view>{{ props.otherValueText || translate('otherValueText') }}</view>
<view class="nut-ecard__list__input--con">
<input
class="nut-ecard-input"
:value="inputValue as string"
type="text"
:placeholder="props.placeholder || translate('placeholder')"
@input="handleInputChange"
>
{{ props.suffix }}
</view>
</view>
<view v-if="props.showStep" class="nut-ecard__list__step">
<view>{{ props.suffix }}{{ props.modelValue }}</view>
<NutInputNumber
:model-value="innerCount"
:min="props.cardBuyMin"
:max="props.cardBuyMax"
@change="handleStepChange"
/>
</view>
</view>
</view>
</template>
<style lang="scss">
@import "./index";
</style>

View File

@@ -0,0 +1,117 @@
@import "../inputnumber/index";
.nut-theme-dark {
.nut-ecard {
color: $dark-color3;
.nut-ecard__list__item {
background: $dark-background5;
border-color: $dark-background5;
&.active {
color: $dark-color2;
background: $dark-background6;
border-color: $dark-color2;
}
}
.nut-ecard__list__input {
color: $dark-color3;
background: $dark-background7;
&.active {
background: $dark-background7;
.nut-ecard-input {
background: $dark-background7;
}
}
.nut-ecard__list__input--con > .nut-ecard-input {
color: $dark-color3;
background-color: transparent;
}
}
}
}
.nut-ecard {
width: 100%;
&__title {
font-size: 15px;
font-weight: normal;
line-height: 1;
}
&__list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 15px;
&__item {
display: flex;
align-items: center;
justify-content: center;
width: 48%;
height: 46px;
margin-bottom: 12px;
background: $ecard-bg-color;
border: 1px solid $ecard-bg-color;
border-radius: 4px;
&.active {
background: $white;
border-color: $primary-color;
}
}
&__input {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 46px;
padding: 0 15px 0 20px;
font-size: 14px;
background: $ecard-bg-color;
border: 1px solid $ecard-bg-color;
border-radius: 4px;
&--con {
display: flex;
flex: 1;
justify-content: flex-end;
.nut-ecard-input {
margin-right: 10px;
text-align: right;
text-decoration: none;
caret-color: $primary-color;
background: transparent;
border: none;
}
}
&.active {
background: $white;
border-color: $primary-color;
.nut-ecard-input {
background: $white;
}
}
}
&__step {
display: flex;
justify-content: space-between;
width: 100%;
margin-top: 17px;
font-size: 20px;
font-weight: normal;
color: $primary-color;
}
}
}

View File

@@ -0,0 +1,2 @@
export * from './ecard'
export * from './type'

View File

@@ -0,0 +1,33 @@
export type EcardDataValue = number | string
export interface EcardDataItem {
price: EcardDataValue
}
export interface EcardChangeEvent {
price: EcardDataValue
}
export interface EcardUpdateOptions {
/**
* 选中项从0开始的索引-1表示选中输入框null表示不选中
*/
index?: number | null
/**
* 其他面值当index为-1或null时有效
*/
input?: string
/**
* 数量
*/
count?: number
}
export interface EcardInst {
/**
* 更新面值
*
* @param options 配置项
*/
update: (options: EcardUpdateOptions) => void
}