修改带单位的输入框组件,解决转换精度问题。
This commit is contained in:
parent
c7b4db67bf
commit
a57f1d6b6d
44
api/system/sysUnitConvert.js
Normal file
44
api/system/sysUnitConvert.js
Normal 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'
|
||||
})
|
||||
}
|
125
pages/index.vue
125
pages/index.vue
@ -1,43 +1,84 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<image class="logo" src="@/static/logo.png"></image>
|
||||
<view class="text-area">
|
||||
<text class="title">Hello RuoYi</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
onLoad: function() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 200rpx;
|
||||
width: 200rpx;
|
||||
margin-top: 200rpx;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.text-area {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
color: #8f8f94;
|
||||
}
|
||||
<template>
|
||||
<view class="content">
|
||||
<image class="logo" src="@/static/logo.png"></image>
|
||||
{{ 'initialValue' + initialValue }}
|
||||
{{ 'newValue' + newValue }}
|
||||
{{ 'oldUnit' + oldUnit }}
|
||||
{{ 'newUnit' + newUnit }}
|
||||
{{ 'oldOrder' + oldOrder }}
|
||||
{{ 'newOrder' + newOrder }}
|
||||
|
||||
<view class="text-area">
|
||||
<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' }"
|
||||
@conversion="onchange"
|
||||
></yjly-inputunit>
|
||||
|
||||
<text class="title">Hello RuoYi</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
onLoad: function () {},
|
||||
data() {
|
||||
return {
|
||||
mylength: 10,
|
||||
myunitname: 1,
|
||||
|
||||
initialValue: 0,
|
||||
newValue: 0,
|
||||
oldUnit: '',
|
||||
newUnit: '',
|
||||
oldOrder: 0,
|
||||
newOrder: 0
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onchange(e) {
|
||||
this.initialValue = e.initialValue;
|
||||
this.newValue = e.newValue;
|
||||
this.oldUnit = e.oldUnit;
|
||||
this.newUnit = e.newUnit;
|
||||
this.oldOrder = e.oldOrder;
|
||||
this.newOrder = e.newOrder;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 200rpx;
|
||||
width: 200rpx;
|
||||
margin-top: 200rpx;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.text-area {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
color: #8f8f94;
|
||||
}
|
||||
</style>
|
||||
|
6
uni_modules/yjly-inputunit/changelog.md
Normal file
6
uni_modules/yjly-inputunit/changelog.md
Normal file
@ -0,0 +1,6 @@
|
||||
## 1.0.2(2025-02-19)
|
||||
修改单位转换精度,将原始单位和值暂存,换算到基准单位值,再换算到新单位值。
|
||||
## 1.0.1(2025-02-15)
|
||||
修改单位类型默认值
|
||||
## 1.0.0(2025-02-15)
|
||||
1.0.0
|
@ -0,0 +1,430 @@
|
||||
<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,
|
||||
|
||||
/* 新增三个数据项 */
|
||||
originalValue: this.value, // 原始输入值
|
||||
originalUnit: null, // 原始输入单位
|
||||
isInternalUpdate: false // 更新锁定标志
|
||||
};
|
||||
},
|
||||
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) {
|
||||
if (!this.isInternalUpdate) {
|
||||
// 外部更新时重置原始值
|
||||
this.originalValue = newVal;
|
||||
this.originalUnit = this.currentUnit;
|
||||
this.inputValue = newVal;
|
||||
}
|
||||
this.isInternalUpdate = false;
|
||||
}
|
||||
},
|
||||
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 {
|
||||
// 后台的API可自行开发,这里提供了js数据格式
|
||||
// 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.originalUnit = this.currentUnit;
|
||||
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 newUnit = this.sortedUnits[(index + 1) % this.sortedUnits.length];
|
||||
this.currentUnit = newUnit;
|
||||
this.textUnitName = this.showEnglishOnly ? newUnit.unitName.split('(')[0].trim() : newUnit.unitName;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.updateInputWidth();
|
||||
this.convertAndEmit();
|
||||
});
|
||||
},
|
||||
|
||||
toggleUnitSelector() {
|
||||
this.showUnitSelector = !this.showUnitSelector;
|
||||
if (this.showUnitSelector) {
|
||||
this.positionUnitSelector();
|
||||
}
|
||||
},
|
||||
|
||||
/* 修改单位选择方法 */
|
||||
selectUnit(unit) {
|
||||
this.currentUnit = unit;
|
||||
this.showUnitSelector = false;
|
||||
this.$nextTick(() => {
|
||||
this.updateInputWidth();
|
||||
this.convertAndEmit();
|
||||
});
|
||||
},
|
||||
|
||||
/* 修改输入处理 */
|
||||
handleInputChange() {
|
||||
// 记录原始值和单位
|
||||
this.originalValue = this.inputValue;
|
||||
this.originalUnit = this.currentUnit;
|
||||
this.$emit('input', this.inputValue);
|
||||
},
|
||||
|
||||
/* 优化后的转换方法 */
|
||||
convertAndEmit() {
|
||||
if (!this.currentUnit || !this.baseUnit || !this.originalUnit) return;
|
||||
|
||||
let newValue = 0;
|
||||
if (this.unitType === 'temperature') {
|
||||
newValue = this.handleTemperatureConversion();
|
||||
} else {
|
||||
// 通过基准单位进行两次精确转换
|
||||
const baseValue = this.originalValue / this.originalUnit.conversionFactor;
|
||||
newValue = baseValue * this.currentUnit.conversionFactor;
|
||||
}
|
||||
|
||||
const roundedValue = this.roundValue(newValue);
|
||||
this.isInternalUpdate = true; // 锁定更新
|
||||
this.inputValue = roundedValue;
|
||||
this.$emit('input', roundedValue);
|
||||
this.$emit('update:unitOrder', this.currentUnit.unitOrder);
|
||||
|
||||
this.$emit('conversion', {
|
||||
initialValue: this.originalValue,
|
||||
newValue: roundedValue,
|
||||
oldUnit: this.originalUnit.unitName,
|
||||
newUnit: this.currentUnit.unitName,
|
||||
oldOrder: this.originalUnit.unitOrder,
|
||||
newOrder: this.currentUnit.unitOrder
|
||||
});
|
||||
},
|
||||
|
||||
/* 优化温度转换方法 */
|
||||
handleTemperatureConversion() {
|
||||
const oldUnit = this.originalUnit;
|
||||
const newUnit = this.currentUnit;
|
||||
const oldOrder = oldUnit.unitOrder;
|
||||
const newOrder = newUnit.unitOrder;
|
||||
|
||||
// 使用原始值计算
|
||||
let celsius;
|
||||
switch (oldOrder) {
|
||||
case 0:
|
||||
celsius = this.originalValue;
|
||||
break;
|
||||
case 1:
|
||||
celsius = ((this.originalValue - 32) * 5) / 9;
|
||||
break;
|
||||
case 2:
|
||||
celsius = this.originalValue - 273.15;
|
||||
break;
|
||||
default:
|
||||
throw new Error('无效温度单位');
|
||||
}
|
||||
|
||||
switch (newOrder) {
|
||||
case 0:
|
||||
return celsius;
|
||||
case 1:
|
||||
return (celsius * 9) / 5 + 32;
|
||||
case 2:
|
||||
return celsius + 273.15;
|
||||
default:
|
||||
throw new Error('无效温度单位');
|
||||
}
|
||||
},
|
||||
|
||||
// 四舍六入五成双
|
||||
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>
|
@ -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;
|
@ -1,14 +1,15 @@
|
||||
{
|
||||
"id": "yjly-number_unit",
|
||||
"displayName": "yjly-number_unit",
|
||||
"version": "1.0.0",
|
||||
"description": "yjly-number_unit",
|
||||
"id": "yjly-inputunit",
|
||||
"displayName": "单位转换组件,双向数值绑定,单位循环切换、弹窗选择,支持自定义单位显示",
|
||||
"version": "1.0.2",
|
||||
"description": "单位转换组件 支持双向数值绑定 内置单位换算逻辑 支持单位循环切换/弹窗选择两种模式 兼容 Vue2/Vue3 和 UniApp 多平台 支持自定义单位显示 可配置小数位数精度",
|
||||
"keywords": [
|
||||
"yjly-number_unit"
|
||||
"单位转换组件",
|
||||
"双向数值绑定",
|
||||
"自定义单位显示"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
"engines": {
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
@ -24,9 +25,9 @@
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "",
|
||||
"data": "",
|
||||
"permissions": ""
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
@ -35,36 +36,37 @@
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "u",
|
||||
"aliyun": "u",
|
||||
"alipay": "u"
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "u",
|
||||
"vue3": "u"
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "u",
|
||||
"app-uvue": "u"
|
||||
"app-vue": "y",
|
||||
"app-nvue": "u",
|
||||
"app-uvue": "u",
|
||||
"app-harmony": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"Chrome": "y",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Edge": "y",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
158
uni_modules/yjly-inputunit/readme.md
Normal file
158
uni_modules/yjly-inputunit/readme.md
Normal 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 覆盖默认样式
|
||||
|
@ -1,272 +0,0 @@
|
||||
<template>
|
||||
<view class="unit-converter">
|
||||
<!-- 数值输入框 -->
|
||||
<input v-model="inputValue" type="number" @input="handleInputChange" class="input-field" :style="{ width: width + 'px' }" />
|
||||
<!-- 单位标签 -->
|
||||
<text @click="toggleUnit" @dblclick="openUnitSelector" class="unit-label" :style="{ color: 'blue' }">
|
||||
{{ displayUnitName }}
|
||||
</text>
|
||||
<!-- 单位选择弹出窗口 -->
|
||||
<!-- uni-popup 弹窗 -->
|
||||
<uni-popup ref="unitSelectorPopup" type="bottom" :show="showUnitSelector" @close="onPopupClose">
|
||||
<view class="unit-selector">
|
||||
<view v-for="unit in filteredUnitList" :key="unit.id" @click="selectUnit(unit)" class="unit-option">
|
||||
{{ showEnglishOnly ? unit.unitName.split('(')[0] : unit.unitName }}
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listConversion } from '@/api/system/conversion.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 单位类型
|
||||
unitType: {
|
||||
type: String,
|
||||
default: 'length'
|
||||
},
|
||||
// 是否只显示英文部分
|
||||
showEnglishOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当前单位名称
|
||||
unitName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
// 当前数值
|
||||
value: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
// 小数点位数
|
||||
decimalPlaces: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 80
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: this.value, // 输入的数值
|
||||
currentUnit: null, // 当前单位
|
||||
unitList: [], // 单位列表
|
||||
showUnitSelector: false // 是否显示单位选择窗口
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
// 在页面加载时添加全局点击事件监听器
|
||||
uni.$on('globalClick', this.handleClickOutside);
|
||||
},
|
||||
onUnload() {
|
||||
// 在页面卸载时移除全局点击事件监听器
|
||||
uni.$off('globalClick', this.handleClickOutside);
|
||||
},
|
||||
computed: {
|
||||
// 显示的单位名称
|
||||
displayUnitName() {
|
||||
if (!this.currentUnit) return '';
|
||||
return this.showEnglishOnly ? this.currentUnit.unitName.split('(')[0] : this.currentUnit.unitName;
|
||||
},
|
||||
// 过滤后的单位列表(排除当前单位)
|
||||
filteredUnitList() {
|
||||
return this.unitList.filter((unit) => unit.unitName !== this.currentUnit?.unitName);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听 unitType 变化,重新加载单位列表
|
||||
unitType: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.loadUnitList(newVal);
|
||||
}
|
||||
},
|
||||
// 监听 unitName 变化,更新当前单位
|
||||
unitName: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
console.log(newVal);
|
||||
this.currentUnit = this.unitList.find((unit) => unit.unitName == newVal);
|
||||
}
|
||||
},
|
||||
// 监听 value 变化,更新输入框的值
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.inputValue = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理全局点击事件
|
||||
handleClickOutside(event) {
|
||||
if (this.$refs.unitSelector && !this.$refs.unitSelector.contains(event.target)) {
|
||||
this.showUnitSelector = false;
|
||||
}
|
||||
},
|
||||
// 切换单位选择器的显示状态
|
||||
toggleUnitSelector() {
|
||||
this.showUnitSelector = !this.showUnitSelector;
|
||||
},
|
||||
// 加载单位列表
|
||||
async loadUnitList(unitType) {
|
||||
try {
|
||||
const response = await listConversion({ pageSize: 100, unitType: unitType });
|
||||
console.log(response);
|
||||
this.unitList = response.rows;
|
||||
if (this.unitName == '') {
|
||||
// 处理默认基准单位
|
||||
this.currentUnit = this.unitList.find((unit) => unit.baseUnit == 1);
|
||||
console.log(this.currentUnit);
|
||||
} else {
|
||||
this.currentUnit = this.unitList.find((unit) => unit.unitName == this.unitName);
|
||||
console.log(this.currentUnit);
|
||||
if (this.currentUnit === undefined) {
|
||||
this.currentUnit = this.unitList.find((unit) => unit.baseUnit === 1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load unit list:', error);
|
||||
}
|
||||
},
|
||||
// 处理输入框变化
|
||||
handleInputChange() {
|
||||
this.$emit('input', this.inputValue); // 将输入值同步到父组件
|
||||
this.convertAndEmit();
|
||||
},
|
||||
// 切换单位
|
||||
toggleUnit() {
|
||||
const currentIndex = this.unitList.indexOf(this.currentUnit);
|
||||
const nextIndex = (currentIndex + 1) % this.unitList.length;
|
||||
let oldUnit = this.currentUnit;
|
||||
let newUnit = this.unitList[nextIndex];
|
||||
this.currentUnit = newUnit;
|
||||
// console.log('切换单位', currentIndex, nextIndex, this.currentUnit);
|
||||
this.convertAndEmit(oldUnit, newUnit);
|
||||
},
|
||||
// 打开单位选择窗口
|
||||
openUnitSelector() {
|
||||
this.showUnitSelector = true;
|
||||
},
|
||||
// 选择单位
|
||||
selectUnit(unit) {
|
||||
console.log('所选择的单位', unit);
|
||||
let oldUnit = this.currentUnit;
|
||||
let newUnit = unit;
|
||||
this.currentUnit = unit;
|
||||
this.showUnitSelector = false;
|
||||
this.convertAndEmit(oldUnit, newUnit);
|
||||
},
|
||||
// 换算并提交结果
|
||||
convertAndEmit(oldUnit, newUnit) {
|
||||
if (!newUnit) return;
|
||||
// 获取基准单位
|
||||
const baseUnit = this.unitList.find((unit) => unit.baseUnit === 1);
|
||||
if (!baseUnit) return;
|
||||
// 将输入值转换为基准单位的值
|
||||
const baseValue = this.inputValue / oldUnit.conversionFactor;
|
||||
// 将基准单位的值转换为新单位的值
|
||||
const newValue = baseValue * newUnit.conversionFactor;
|
||||
|
||||
console.log('转换值', newUnit.unitName, this.inputValue, baseValue, newValue);
|
||||
|
||||
let roundedValue;
|
||||
if (newUnit.unitName === 'ly(光年)') {
|
||||
// 对于光年,使用科学计数法显示结果
|
||||
roundedValue = newValue.toExponential();
|
||||
} else {
|
||||
roundedValue = this.roundToDecimalPlaces(newValue);
|
||||
}
|
||||
|
||||
// 提交结果给父组件
|
||||
this.$emit('conversion', {
|
||||
initialValue: this.inputValue,
|
||||
newValue: roundedValue,
|
||||
oldUnit: oldUnit.unitName,
|
||||
newUnit: newUnit.unitName
|
||||
});
|
||||
|
||||
this.inputValue = parseFloat(roundedValue);
|
||||
|
||||
// 更新父组件的 unitName
|
||||
this.$emit('update:unitName', this.currentUnit.unitName);
|
||||
},
|
||||
// 四舍六入五留双
|
||||
// 按照四舍六入五留双处理结果 conversionFactor
|
||||
roundToDecimalPlaces(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;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
padding: 2px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.unit-label {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.unit-selector {
|
||||
/* 设置容器的最大高度,当内容超出这个高度时会出现滚动条 */
|
||||
max-height: 200px;
|
||||
/* 超出内容时显示纵向滚动条 */
|
||||
overflow-y: auto;
|
||||
/* 横向内容不溢出,隐藏多余部分 */
|
||||
overflow-x: hidden;
|
||||
/* 其他样式保持不变 */
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 2px 0;
|
||||
min-width: 100px;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.unit-option {
|
||||
padding: 2px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.unit-option:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
</style>
|
@ -1 +0,0 @@
|
||||
# yjly-number_unit
|
Loading…
Reference in New Issue
Block a user