增加带单位的输入框组件,修改

This commit is contained in:
廖德云 2025-02-16 00:20:25 +08:00
parent c7b4db67bf
commit 7274dd2ade
7 changed files with 847 additions and 0 deletions

View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询单位换算列表
export function listSysUnitConvert(query) {
return request({
url: '/system/sysUnitConvert/list',
method: 'get',
params: query
})
}
// 查询单位换算详细
export function getSysUnitConvert(id) {
return request({
url: '/system/sysUnitConvert/' + id,
method: 'get'
})
}
// 新增单位换算
export function addSysUnitConvert(data) {
return request({
url: '/system/sysUnitConvert',
method: 'post',
data: data
})
}
// 修改单位换算
export function updateSysUnitConvert(data) {
return request({
url: '/system/sysUnitConvert',
method: 'put',
data: data
})
}
// 删除单位换算
export function delSysUnitConvert(id) {
return request({
url: '/system/sysUnitConvert/' + id,
method: 'delete'
})
}

View File

@ -1,5 +1,14 @@
<template>
<view class="normal-login-container">
<yjly-inputunit
v-model="mylength"
:unit-type="'length'"
:unit-order.sync="myunitname"
:show-english-only="false"
:decimal-places="5"
:width="200"
:style="{ width: 200 + 'px' }"
></yjly-inputunit>
<view class="logo-content align-center justify-center flex">
<image style="width: 100rpx; height: 100rpx" :src="globalConfig.appInfo.logo" mode="widthFix"></image>
<text class="title">若依移动端登录</text>
@ -42,6 +51,8 @@ import { getCodeImg } from '@/api/login';
export default {
data() {
return {
mylength: 10,
myunitname: '',
codeUrl: '',
captchaEnabled: true,
//

View File

@ -0,0 +1,4 @@
## 1.0.12025-02-15
修改单位类型默认值
## 1.0.02025-02-15
1.0.0

View File

@ -0,0 +1,422 @@
<template>
<view class="unit-converter" :style="{ width: width + 'px' }">
<!-- 数值输入框 -->
<input
v-model.number="inputValue"
class="input-field"
type="number"
:ref="'inputRef'"
:style="{
width: inputWidth + 'px',
height: height + 'px',
lineHeight: height + 'px'
}"
@input="handleInputChange"
/>
<!-- 单位标签 -->
<view
v-if="enableConvert"
:ref="'unitLabel'"
class="unit-label"
:style="{
color: 'blue',
height: height + 'px',
lineHeight: height + 'px'
}"
@tap="cycleUnit"
@longpress="handleLongPress"
>
{{ textUnitName }}
</view>
<view
v-else
class="unit-label"
:style="{
color: 'blue',
height: height + 'px',
lineHeight: height + 'px'
}"
>
{{ textUnitName }}
</view>
<!-- 单位选择弹出窗口 -->
<view
v-if="showUnitSelector"
class="unit-selector"
:style="{
left: unitSelectorLeft + 'px',
top: unitSelectorTop + 'px'
}"
>
<scroll-view scroll-y :style="{ maxHeight: '200px' }">
<view v-for="unit in sortedUnits" :key="unit.id" class="unit-option" @tap="selectUnit(unit)">
{{ showEnglishOnly ? unit.unitName.split('(')[0] : unit.unitName }}
</view>
</scroll-view>
</view>
</view>
</template>
<script>
// Vue2/Vue3
import unitDatalist from './yjlyUnitData';
export default {
name: 'UniUnitConverter',
emits: ['input', 'update:unitOrder', 'conversion'],
props: {
unitType: {
type: String,
required: true,
default: 'length'
},
unitOrder: {
type: Number,
default: 0
},
value: {
type: Number,
required: true
},
showEnglishOnly: {
type: Boolean,
default: false
},
decimalPlaces: {
type: Number,
default: 5,
validator: (v) => v >= 0 && v <= 10
},
width: {
type: Number,
default: 180
},
height: {
type: Number,
default: 32
},
enableConvert: {
type: Boolean,
default: true
},
userDefined: {
type: Boolean,
default: false
},
userDefinedunitName: {
type: String,
default: ''
}
},
data() {
return {
inputValue: this.value,
unitData: [],
showUnitSelector: false,
currentUnit: null,
baseUnit: null,
inputWidth: 100,
textUnitName: '',
unitSelectorLeft: 0,
unitSelectorTop: 0
};
},
mounted() {
this.initComponent();
this.setGlobalClickHandler();
},
beforeDestroy() {
this.removeGlobalClickHandler();
unitOrder;
},
computed: {
sortedUnits() {
return this.unitData.filter((u) => u.unitType === this.unitType).sort((a, b) => a.unitOrder - b.unitOrder);
}
},
watch: {
unitType: {
immediate: true,
async handler(newType) {
if (this.userDefined) {
this.textUnitName = this.userDefinedunitName;
this.$nextTick(() => this.updateInputWidth());
} else {
await this.loadUnits(newType);
this.initCurrentUnit();
}
}
},
unitOrder(newOrder) {
if (this.userDefined) {
this.textUnitName = this.userDefinedunitName;
this.$nextTick(() => this.updateInputWidth());
} else {
const target = this.sortedUnits.find((u) => u.unitOrder === newOrder);
if (target) {
this.currentUnit = target;
this.textUnitName = this.showEnglishOnly ? target.unitName.split('(')[0].trim() : target.unitName;
this.$nextTick(() => this.updateInputWidth());
}
}
},
value(newVal) {
console.log(newVal);
this.inputValue = newVal;
}
},
methods: {
async initComponent() {
await this.$nextTick();
this.updateInputWidth();
},
setGlobalClickHandler() {
if (typeof document !== 'undefined') {
document.addEventListener('click', this.handleClickOutside);
}
},
removeGlobalClickHandler() {
if (typeof document !== 'undefined') {
document.removeEventListener('click', this.handleClickOutside);
}
},
handleClickOutside(event) {
if (!this.showUnitSelector) return;
const selector = this.$refs.unitSelector;
if (!selector) return;
const isOutside = !selector.$el.contains(event.target);
if (isOutside) {
this.showUnitSelector = false;
}
},
async loadUnits(unitType) {
try {
// APIjs
// const res = await listSysUnitConvert({ unitType: unitType, status: 'Y' });
// this.unitData = res.rows;
this.unitData = unitDatalist[unitType];
console.log(this.unitData);
this.baseUnit = this.unitData.find((u) => u.baseUnit === 'Y');
console.log(this.baseUnit);
} catch (e) {
console.error('单位数据加载失败:', e);
}
},
initCurrentUnit() {
const target = this.sortedUnits.find((u) => u.unitOrder === this.unitOrder);
this.currentUnit = target || this.baseUnit || this.sortedUnits[0];
this.textUnitName = this.showEnglishOnly ? this.currentUnit.unitName.split('(')[0].trim() : this.currentUnit.unitName;
this.$nextTick(() => this.updateInputWidth());
},
updateInputWidth() {
const query = uni.createSelectorQuery().in(this);
query
.select('.unit-label')
.boundingClientRect((res) => {
if (res) {
this.inputWidth = this.width - res.width - 4;
}
})
.exec();
},
handleLongPress() {
this.toggleUnitSelector();
this.positionUnitSelector();
},
positionUnitSelector() {
const query = uni.createSelectorQuery().in(this);
query
.select('.unit-label')
.boundingClientRect((res) => {
if (res) {
this.unitSelectorLeft = res.left;
this.unitSelectorTop = res.bottom + 5;
}
})
.exec();
},
cycleUnit() {
const index = this.sortedUnits.findIndex((u) => u === this.currentUnit);
const next = (index + 1) % this.sortedUnits.length;
const oldUnit = this.currentUnit;
const newUnit = this.sortedUnits[next];
this.currentUnit = newUnit;
this.textUnitName = this.showEnglishOnly ? newUnit.unitName.split('(')[0].trim() : newUnit.unitName;
this.$nextTick(() => {
this.updateInputWidth();
this.convertAndEmit(oldUnit, newUnit);
});
},
toggleUnitSelector() {
this.showUnitSelector = !this.showUnitSelector;
if (this.showUnitSelector) {
this.positionUnitSelector();
}
},
selectUnit(unit) {
const oldUnit = this.currentUnit;
this.currentUnit = unit;
this.showUnitSelector = false;
this.textUnitName = this.showEnglishOnly ? unit.unitName.split('(')[0].trim() : unit.unitName;
this.$nextTick(() => {
this.updateInputWidth();
this.convertAndEmit(oldUnit, unit);
});
},
handleInputChange() {
this.$emit('input', this.inputValue);
this.convertAndEmit(this.currentUnit, this.currentUnit);
},
convertAndEmit(oldUnit, newUnit) {
if (!newUnit || !oldUnit || !this.baseUnit) return;
let newValue = 0;
if (this.unitType === 'temperature') {
newValue = this.handleTemperatureConversion(oldUnit, newUnit);
} else {
const baseValue = this.inputValue / oldUnit.conversionFactor;
newValue = baseValue * newUnit.conversionFactor;
}
const roundedValue = this.roundValue(newValue);
this.inputValue = roundedValue;
this.$emit('input', roundedValue);
this.$emit('update:unitOrder', newUnit.unitOrder);
this.$emit('conversion', {
initialValue: this.value,
newValue: roundedValue,
oldUnit: oldUnit.unitName,
newUnit: newUnit.unitName,
oldOrder: oldUnit.unitOrder,
newOrder: newUnit.unitOrder
});
},
//
handleTemperatureConversion(oldUnit, newUnit) {
const oldUnitOrder = oldUnit.unitOrder;
const newUnitOrder = newUnit.unitOrder;
// console.log(oldUnitOrder, newUnitOrder);
//
let celsius;
switch (oldUnitOrder) {
case 0:
celsius = this.inputValue;
break;
case 1:
celsius = (this.inputValue - 32) * (5 / 9);
break;
case 2:
celsius = this.inputValue - 273.15;
break;
default:
throw new Error(`不支持的旧温度单位: ${oldUnit.unitName}`);
}
//
let result;
switch (newUnitOrder) {
case 0:
result = celsius;
break;
case 1:
result = celsius * (9 / 5) + 32;
break;
case 2:
result = celsius + 273.15;
break;
default:
throw new Error(`不支持的新温度单位: ${newUnit.unitName}`);
}
return result;
},
//
roundValue(value) {
const multiplier = Math.pow(10, this.decimalPlaces);
const val = value * multiplier;
const intVal = Math.trunc(val);
const decimalPart = val - intVal;
if (decimalPart < 0.5) {
return intVal / multiplier;
} else if (decimalPart > 0.5) {
return (intVal + 1) / multiplier;
} else {
return intVal % 2 === 0 ? intVal / multiplier : (intVal + 1) / multiplier;
}
}
}
};
</script>
<style scoped>
.unit-converter {
display: flex;
align-items: center;
position: relative;
}
.input-field {
border: 1px solid #d6d5d5;
border-radius: 4px;
font-size: 14px;
padding: 0 8px;
}
.unit-label {
display: inline-flex;
align-items: center;
padding: 0 10px;
border: 1px solid #d6d5d5;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
margin-left: 2px;
}
.unit-selector {
position: fixed;
z-index: 9999;
background-color: #ffffff;
border: 1px solid #cccccc;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-width: 120px;
}
.unit-option {
padding: 8px 12px;
font-size: 14px;
}
.unit-option:hover {
background-color: #f0f0f0;
}
scroll-view {
max-height: 200px;
}
</style>

View File

@ -0,0 +1,122 @@
const unitData = {
pressure: [{
"id": 66,
"unitType": "pressure",
"unitName": "Pa(帕斯卡)",
"baseUnit": "Y",
"conversionFactor": 1,
"unitTypeName": "压力",
"status": "Y",
"unitOrder": 0
},
{
"id": 67,
"unitType": "pressure",
"unitName": "kPa(千帕)",
"baseUnit": "N",
"conversionFactor": 0.001,
"unitTypeName": "压力",
"status": "Y",
"unitOrder": 1
},
{
"id": 68,
"unitType": "pressure",
"unitName": "MPa(兆帕)",
"baseUnit": "N",
"conversionFactor": 0.000001,
"unitTypeName": "压力",
"status": "Y",
"unitOrder": 2
},
{
"id": 69,
"unitType": "pressure",
"unitName": "atm(标准大气压)",
"baseUnit": "N",
"conversionFactor": 0.0000098692326671601,
"unitTypeName": "压力",
"status": "Y",
"unitOrder": 3
},
{
"id": 70,
"unitType": "pressure",
"unitName": "bar(巴)",
"baseUnit": "N",
"conversionFactor": 0.00001,
"unitTypeName": "压力",
"status": "Y",
"unitOrder": 4
},
{
"id": 71,
"unitType": "pressure",
"unitName": "mbar(毫巴)",
"baseUnit": "N",
"conversionFactor": 0.01,
"unitTypeName": "压力",
"status": "Y",
"unitOrder": 5
}
],
length: [{
"id": 1,
"unitType": "length",
"unitName": "m(米)",
"baseUnit": "Y",
"conversionFactor": 1,
"unitTypeName": "长度",
"status": "Y",
"unitOrder": 0
},
{
"id": 2,
"unitType": "length",
"unitName": "dm(分米)",
"baseUnit": "N",
"conversionFactor": 10,
"unitTypeName": "长度",
"status": "Y",
"unitOrder": 1
},
{
"id": 3,
"unitType": "length",
"unitName": "cm(厘米)",
"baseUnit": "N",
"conversionFactor": 100,
"unitTypeName": "长度",
"status": "Y",
"unitOrder": 2
},
{
"id": 4,
"unitType": "length",
"unitName": "mm(毫米)",
"baseUnit": "N",
"conversionFactor": 1000,
"unitTypeName": "长度",
"status": "Y",
"unitOrder": 3
},
{
"id": 5,
"unitType": "length",
"unitName": "km(千米)",
"baseUnit": "N",
"conversionFactor": 0.001,
"unitTypeName": "长度",
"status": "Y",
"unitOrder": 4
}
]
}
export default unitData;

View File

@ -0,0 +1,86 @@
{
"id": "yjly-inputunit",
"displayName": "yjly-inputunit",
"version": "1.0.1",
"description": "单位转换组件 支持双向数值绑定 内置单位换算逻辑 支持单位循环切换/弹窗选择两种模式 兼容 Vue2/Vue3 和 UniApp 多平台 支持自定义单位显示 可配置小数位数精度",
"keywords": [
"单位转换组件",
"双向数值绑定",
"自定义单位显示"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u",
"app-uvue": "u",
"app-harmony": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "y",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,158 @@
# yjyl-input-unit 单位转换组件使用说明
功能特性
支持双向数值绑定
内置单位换算逻辑(含温度特殊处理)
支持单位循环切换/弹窗选择两种模式
兼容 Vue2/Vue3 和 UniApp 多平台
支持自定义单位显示
可配置小数位数精度
符合uni-modules标准直接使用
Props 配置
|属性名 |类型 |必填 |默认值 |说明 |
|:-: |:-: |:-: |:-: |:-: |
|unitType |String |是 |- |单位类型(例:'length', 'weight', 'temperature' |
|unitOrder |Number |否 |0 |当前单位序号(需配合 .sync 修饰符使用) |
|value |Number |是 |- |输入数值(使用 v-model 双向绑定) |
|showEnglishOnly |Boolean|否 |false |是否只显示英文单位(例:'kg' 代替 ' 千克 (kg)' |
|decimalPlaces |Number |否 |5 |小数位数0 - 10 |
|width |Number |否 |180 |组件总宽度px |
|height |Number |否 |32 |组件高度px |
|enableConvert |Boolean|否 |true |是否允许单位转换 |
|userDefined |Boolean|否 |false |是否使用自定义单位 |
|userDefinedunitName|String |否 |'' |自定义单位显示名称(需 userDefined = true 生效) |
事件说明
| 事件名称 | 说明 | 回调参数 |
| input | 数值变化时触发 | (newValue: number) |
| update:unitOrder | 单位序号变化时触发 | (newOrder: number) |
| conversion | 完成单位转换时触发 | { initialValue, newValue, oldUnit, newUnit, oldOrder, newOrder } 的 Object |
使用示例
基本用法
vue
<template>
<UniUnitConverter
v-model="value"
:unit-type="length"
:unit-order.sync="currentOrder"
/>
</template>
<script>
export default {
data() {
return {
value: 100,
currentOrder: 0
}
}
}
</script>
禁用转换模式
<UniUnitConverter
v-model="fixedValue"
:unit-type="weight"
:enable-convert="false"
user-defined
:user-definedunit-name="'特殊单位'"
/>
事件处理
vue
复制
<template>
<UniUnitConverter
v-model="tempValue"
:unit-type="temperature"
@conversion="handleConversion"
/>
</template>
<script>
export default {
methods: {
handleConversion({ oldUnit, newUnit, newValue }) {
console.log(`单位从 ${oldUnit} 转换为 ${newUnit},新值:${newValue}`)
}
}
}
</script>
API 需自己开发按照下面的数据格式进行组织参考unitData.js
组件依赖 listSysUnitConvert API 获取单位数据,需实现以下格式的接口:
javascript
{
rows: [
{
id: 1,
unitType: 'length',
unitName: '米(m)',
conversionFactor: 1,
baseUnit: 'Y',
unitOrder: 0
},
// ...其他单位数据
]
}
样式调整
可通过以下 CSS 类名自定义样式:
.unit-converter - 组件容器
.input-field - 输入框
.unit-label - 单位标签
.unit-selector - 单位选择器
.unit-option - 单位选项
平台差异
小程序端使用 longpress 事件触发选择器(长按代替双击)
H5 端自动适配点击事件
单位转换规则
温度单位需按约定顺序定义:
javascript
复制
[
{ unitOrder: 0, unitName: '℃' }, // 摄氏度
{ unitOrder: 1, unitName: '℉' }, // 华氏度
{ unitOrder: 2, unitName: 'K' } // 开尔文
]
自定义单位时需确保与现有换算逻辑兼容
常见问题
Q1: 单位选择器不显示
检查 enableConvert 是否为 true
确认单位数据加载成功
查看控制台是否有 API 错误
Q2: 数值更新不及时
确保使用 v-model 进行双向绑定
检查小数位数配置是否符合预期
确认没有在父组件中覆盖转换后的值
Q3: 样式显示异常
检查是否父容器有冲突的布局样式
确认单位标签宽度计算正确(通过 updateInputWidth 方法)
在小程序端添加 !important 覆盖默认样式