AmountInput.vue
5.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<template>
<div>
<!-- 标签 -->
<div v-if="label" class="text-sm text-gray-600 mb-2">
{{ label }}
<span v-if="currencyText" class="text-gray-500">({{ currencyText }})</span>
</div>
<!-- 多币种模式(方案 2 - 未来扩展) -->
<div v-if="multiCurrencyEnabled" class="mb-2">
<div class="text-sm text-gray-600 mb-2">币种</div>
<div class="flex gap-2">
<button
v-for="curr in supportedCurrencies"
:key="curr.value"
:class="[
'px-4 py-2 rounded-lg text-sm border transition-colors',
selectedCurrency === curr.value
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-600 border-gray-200'
]"
@tap="selectCurrency(curr.value)"
>
{{ curr.label }}
</button>
</div>
</div>
<!-- 保额输入 -->
<div class="border border-gray-200 rounded-lg flex items-center overflow-hidden">
<nut-input
:model-value="formattedValue"
@input="onInput"
type="digit"
:placeholder="placeholder"
class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900"
:border="false"
/>
<span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">{{ currencySymbol }}</span>
</div>
</div>
</template>
<script setup>
/**
* 保额输入组件
*
* @description 支持多币种的保额输入组件
* - 单位转换:内部存储为分(整数),显示为元(带2位小数)
* - 币种支持:CNY、USD、HKD、EUR
* - 多币种模式:通过 FEATURE_FLAGS.MULTI_CURRENCY_ENABLED 控制
* @author Claude Code
* @example
* <!-- 固定币种模式 -->
* <AmountInput
* v-model="coverage"
* label="保额"
* currency="USD"
* placeholder="请输入保额"
* />
*
* @example
* <!-- 多币种模式 -->
* <AmountInput
* v-model="coverage"
* label="保额"
* :config="{ supported_currencies: ['CNY', 'USD'], default_currency: 'CNY' }"
* placeholder="请输入保额"
* />
*/
import { ref, computed } from 'vue'
import { FEATURE_FLAGS, CURRENCY_SYMBOLS, CURRENCY_MAP } from '@/config/plan-templates'
/**
* 组件属性
*/
const props = defineProps({
/**
* 标签文本
* @type {string}
*/
label: {
type: String,
default: ''
},
/**
* 占位符文本
* @type {string}
*/
placeholder: {
type: String,
default: '请输入保额'
},
/**
* 绑定的值(单位:分)
* @type {number}
* @example 100000 表示 1000.00 元
*/
modelValue: {
type: Number,
default: null
},
/**
* 币种代码(固定币种模式)
* @type {string}
* @default 'CNY'
*/
currency: {
type: String,
default: 'CNY'
},
/**
* 模版配置(多币种模式)
* @type {Object}
* @property {Array<string>} supported_currencies - 支持的币种代码数组
* @property {string} default_currency - 默认币种代码
* @example { supported_currencies: ['CNY', 'USD'], default_currency: 'CNY' }
*/
config: {
type: Object,
default: () => ({})
}
})
/**
* 组件事件
*/
const emit = defineEmits([
/**
* 更新值事件
* @event update:modelValue
* @param {number} value - 保额值(单位:分)
*/
'update:modelValue'
])
/**
* 判断是否启用多币种
* @type {ComputedRef<boolean>}
*/
const multiCurrencyEnabled = computed(() => FEATURE_FLAGS.MULTI_CURRENCY_ENABLED)
/**
* 当前选中的币种
* @type {Ref<string>}
*/
const selectedCurrency = ref(props.config.default_currency || props.currency || 'CNY')
/**
* 支持的币种列表(多币种模式)
* @type {ComputedRef<Array<{label: string, symbol: string, value: string}>>}
*/
const supportedCurrencies = computed(() => {
if (!multiCurrencyEnabled.value) return []
return (props.config.supported_currencies || ['CNY'])
.map(code => CURRENCY_MAP[code])
.filter(Boolean)
})
/**
* 当前币种符号
* @type {ComputedRef<string>}
* @example
* // CNY -> '¥'
* // USD -> '$'
*/
const currencySymbol = computed(() => {
if (multiCurrencyEnabled.value) {
// 多币种模式:使用用户选择的币种
const curr = supportedCurrencies.value.find(c => c.value === selectedCurrency.value)
return curr?.symbol || '¥'
}
// 固定币种模式:使用 props.currency
return CURRENCY_SYMBOLS[props.currency] || '¥'
})
/**
* 币种文本(用于标签显示)
* @type {ComputedRef<string>}
*/
const currencyText = computed(() => {
if (multiCurrencyEnabled.value) {
const curr = supportedCurrencies.value.find(c => c.value === selectedCurrency.value)
return curr?.label || ''
}
const CURRENCY_NAMES = {
CNY: '人民币',
USD: '美元',
HKD: '港币',
EUR: '欧元'
}
return CURRENCY_NAMES[props.currency] || ''
})
/**
* 格式化显示值(元,带2位小数)
* @description 将分转换为元进行显示
* @type {ComputedRef<string>}
* @example
* // modelValue = 100000 (分)
* // formattedValue() // 返回: '1000.00'
*/
const formattedValue = computed(() => {
if (props.modelValue === null || props.modelValue === undefined) {
return ''
}
// 分 -> 元,保留2位小数
return (props.modelValue / 100).toFixed(2)
})
/**
* 用户输入处理
* @description 将用户输入的元转换为分存储
* @param {string} value - 输入值
*
* @example
* // 用户输入: '1000.50'
* // onInput('1000.50')
* // -> emit('update:modelValue', 100050) // 分
*/
const onInput = (value) => {
// 移除非数字和小数点
const cleanValue = value.replace(/[^\d.]/g, '')
// 转换为分(整数)
const yuan = parseFloat(cleanValue)
if (!Number.isNaN(yuan)) {
emit('update:modelValue', Math.round(yuan * 100))
} else {
emit('update:modelValue', 0)
}
}
/**
* 选择币种(多币种模式)
* @param {string} value - 币种代码
*/
const selectCurrency = (value) => {
selectedCurrency.value = value
}
</script>
<style lang="less">
/* 组件样式 */
</style>