ruoyi-ui/src/components/inputValueUnit/index.vue

434 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="unit-converter" :style="{ width: width + 'px' }">
<!-- 数值输入框 -->
<input
v-model.number="inputValue"
:disabled="inputDisable"
class="input-field"
type="number"
ref="inputRef"
:style="{ width: inputWidth + 'px', height: height + 'px' }"
@change="handleInputChange"
@focus="selectAllText"
/>
<!-- 单位标签 -->
<span v-if="enableConvert" ref="unitLabel" @click="cycleUnit" @dblclick="toggleUnitSelector" class="unit-label" :style="{ color: 'blue', height: height + 'px' }">
{{ textUnitName }}
</span>
<span v-else ref="unitLabel" class="unit-label" :style="{ color: 'blue', height: height + 'px' }">
{{ textUnitName }}
</span>
<!-- 单位选择弹出窗口 -->
<div v-if="showUnitSelector" class="unit-selector" ref="unitSelector" :style="{ left: unitSelectorLeft + 'px', top: unitSelectorTop + 'px' }">
<div v-for="unit in sortedUnits" :key="unit.id" @click="selectUnit(unit)" class="unit-option">
{{ showEnglishOnly ? unit.unitName.split('(')[0] : unit.unitName }}
</div>
</div>
</div>
</template>
<script>
import { listSysUnitConvert } from '@/api/system/sysUnitConvert.js';
export default {
name: 'UnitConverter',
props: {
unitType: {
// 当前单位类型 (required)
type: String,
required: true
},
unitOrder: {
// 当前单位序号 (sync)
type: Number,
default: 0
},
value: {
// 输入数值 (sync)
type: Number,
required: true
},
showEnglishOnly: {
// 是否只显示英文
type: Boolean,
default: false
},
decimalPlaces: {
// 小数位数
type: Number,
default: 5,
validator: (v) => v >= 0 && v <= 10
},
width: {
type: Number,
required: false,
default: 180
},
height: {
type: Number,
required: false,
default: 32
},
enableConvert: {
//是否能够进行单位换算
type: Boolean,
default: true
},
userDefined: {
//自定义输入单位显示
type: Boolean,
default: false
},
userDefinedunitName: {
//自定义输入单位显示
type: String,
default: ''
},
inputDisable: {
type: Boolean,
required: false,
default: false
}
},
mounted() {
this.$nextTick(() => {
this.updateInputWidth();
});
// 在挂载时添加全局点击事件监听器
document.addEventListener('click', this.handleClickOutside);
},
beforeUnmount() {
// 在组件销毁前移除全局点击事件监听器
document.removeEventListener('click', this.handleClickOutside);
},
data() {
return {
inputValue: this.value,
unitData: [], // 原始单位数据
showUnitSelector: false, // 显示选择器
currentUnit: null, // 当前选中单位
baseUnit: null, // 基准单位
inputWidth: 120, //输入框的宽度
textUnitName: '',
unitSelectorLeft: 0,
unitSelectorTop: 0,
// 新增三个数据项
originalValue: this.value, // 原始输入值
originalUnit: null, // 原始输入单位
isInternalUpdate: false // 更新锁定标志
};
},
computed: {
// 显示的单位文本
displayUnitText() {
if (!this.currentUnit) return '';
return this.showEnglishOnly ? this.currentUnit.unitName.split('(')[0].trim() : this.currentUnit.unitName;
},
// 当前类型的单位列表按unitOrder排序
sortedUnits() {
return this.unitData.filter((u) => u.unitType === this.unitType).sort((a, b) => a.unitOrder - b.unitOrder);
},
// 弹窗定位样式
popupPosition() {
if (!this.$refs.unitLabel) return {};
const rect = this.$refs.unitLabel.getBoundingClientRect();
return {
top: `${rect.bottom + window.scrollY}px`,
left: `${rect.left + window.scrollX}px`
};
}
},
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 === 'NaN' ? 0 : newVal;
this.originalUnit = this.currentUnit;
this.inputValue = newVal === '' ? 0 : newVal;
}
this.isInternalUpdate = false;
}
},
methods: {
selectAllText(event) {
// 通过 event.target 获取触发事件的 input 元素
const input = event.target;
// 调用 select() 方法全选文本
input.select();
},
handleMouseMove(event) {
if (this.showUnitSelector) {
// 鼠标右下方偏移量
const offsetX = 10;
const offsetY = 10;
this.unitSelectorLeft = event.clientX + offsetX;
this.unitSelectorTop = event.clientY + offsetY;
// 获取弹窗元素
const unitSelector = this.$refs.unitSelector;
if (unitSelector) {
// 检查是否超出屏幕右边界
if (this.unitSelectorLeft + unitSelector.offsetWidth > window.innerWidth) {
this.unitSelectorLeft = window.innerWidth - unitSelector.offsetWidth;
}
// 检查是否超出屏幕下边界
if (this.unitSelectorTop + unitSelector.offsetHeight > window.innerHeight) {
this.unitSelectorTop = window.innerHeight - unitSelector.offsetHeight;
}
}
}
},
updateInputWidth() {
const spanWidth = this.$refs.unitLabel.offsetWidth + 5;
// console.log("widht", this.width, "spanWidth", spanWidth)
this.inputWidth = this.width - spanWidth;
},
// 处理全局点击事件
handleClickOutside(event) {
if (this.$refs.unitSelector && !this.$refs.unitSelector.contains(event.target)) {
this.showUnitSelector = false;
}
},
// 切换单位选择器的显示状态
toggleUnitSelector() {
this.showUnitSelector = !this.showUnitSelector;
},
// 加载单位数据
async loadUnits(unitType) {
try {
const res = await listSysUnitConvert({
unitType: unitType,
status: 'Y'
});
// console.log(res);
this.unitData = res.rows;
this.baseUnit = this.unitData.find((u) => u.baseUnit === 'Y');
// console.log(this.baseUnit.unitName);
} 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 ? target.unitName.split('(')[0].trim() : target.unitName;
this.$nextTick(() => {
this.updateInputWidth();
});
},
// 输入变化处理
handleInputChange() {
// 记录原始值和单位
this.originalValue = this.inputValue;
this.originalUnit = this.currentUnit;
// console.log(this.originalValue);
this.$emit('input', this.inputValue);
// this.convertAndEmit();
},
// 循环切换单位
// 修改单位切换方法
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(); // 触发转换
});
},
// 选择单位
selectUnit(unit) {
this.currentUnit = unit;
this.showUnitSelector = false;
this.$nextTick(() => {
this.updateInputWidth();
this.convertAndEmit(); // 触发转换
});
},
// 执行换算并提交事件
convertAndEmit() {
// 移除参数
if (!this.currentUnit || !this.baseUnit || !this.originalUnit) return;
let newValue = 0;
if (this.unitType === 'temperature') {
newValue = this.handleTemperatureConversion(this.originalUnit, this.currentUnit);
} 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(oldUnit, newUnit) {
const oldOrder = oldUnit.unitOrder;
const newOrder = newUnit.unitOrder;
// console.log(1111, this.originalValue);
// 使用原始值计算
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;
gap: 2px;
position: relative;
}
.input-field {
border: 1px solid #d6d5d5;
border-radius: 4px;
font-size: 14px;
}
.unit-label {
cursor: pointer;
font-weight: bold;
border: 1px solid #d6d5d5;
border-radius: 4px;
font-size: 12px;
padding-left: 10px;
padding-right: 5px;
white-space: nowrap;
}
.unit-label:hover {
background: #f0f8ff;
}
.unit-selector {
/* 设置容器的最大高度,当内容超出这个高度时会出现滚动条 */
max-height: 100px;
/* 超出内容时显示纵向滚动条 */
overflow-y: auto;
/* 横向内容不溢出,隐藏多余部分 */
overflow-x: hidden;
/* 其他样式保持不变 */
position: absolute;
z-index: 9999;
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: 200px;
list-style-type: none;
margin-left: 100px;
}
.unit-option {
padding: 2px 10px;
cursor: pointer;
font-size: 14px;
}
.unit-option:hover {
background-color: #f0f0f0;
}
</style>