当前位置: 首页 > news >正文

Vue 3 弹出式计算器组件(源码 + 教程)

🧮 Vue 3 弹出式计算器组件(源码 + 教程)

📌 建议收藏 + 点赞 + 关注,本组件支持加减乘除、双向绑定、计算过程展示,适用于表单辅助输入场景。


在这里插入图片描述

🔧 一、完整源码(复制即用)

<!-- CalculatorInput.vue -->
<template><div class="calculator-wrapper"><!-- 自定义输入框 --><div class="custom-input-container"><input type="text" class="custom-input" :value="modelValue" @input="onInputChange" :placeholder="placeholder" /><div class="calculator-icon" @click="toggleCalculator"><span>🧮</span></div></div><!-- 计算器部分 --><div v-show="showCalculator" class="calculator-container" ref="calculatorRef"><div class="calculator"><div class="display"><div class="calculation-process">{{ calculationProcess || '&nbsp;' }}</div><div class="current-value">{{ currentValue || '0' }}</div></div><div class="buttons"><div class="button" @click="clear">C</div><div class="button" @click="changeSign">+/-</div><div class="button" @click="percentage">%</div><div class="button operator" @click="setOperation('/')">/</div><div class="button" @click="appendNumber('7')">7</div><div class="button" @click="appendNumber('8')">8</div><div class="button" @click="appendNumber('9')">9</div><div class="button operator" @click="setOperation('*')">×</div><div class="button" @click="appendNumber('4')">4</div><div class="button" @click="appendNumber('5')">5</div><div class="button" @click="appendNumber('6')">6</div><div class="button operator" @click="setOperation('-')">-</div><div class="button" @click="appendNumber('1')">1</div><div class="button" @click="appendNumber('2')">2</div><div class="button" @click="appendNumber('3')">3</div><div class="button operator" @click="setOperation('+')">+</div><div class="button zero" @click="appendNumber('0')">0</div><div class="button" @click="appendDecimal">.</div><div class="button operator" @click="calculate">=</div></div></div></div></div>
</template><script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';const props = defineProps({modelValue: { type: String, default: '' },placeholder: { type: String, default: '请输入数值' },width: { type: String, default: '200px' }
});
const emit = defineEmits(['update:modelValue']);const onInputChange = (e) => emit('update:modelValue', e.target.value);const currentValue = ref('');
const previousValue = ref('');
const operation = ref(null);
const showCalculator = ref(false);
const calculationPerformed = ref(false);
const calculationProcess = ref('');
const calculatorRef = ref(null);const toggleCalculator = () => (showCalculator.value = !showCalculator.value);const handleClickOutside = (e) => {if (showCalculator.value && calculatorRef.value && !calculatorRef.value.contains(e.target) && !e.target.closest('.calculator-icon')) {showCalculator.value = false;}
};
const handleEscKey = (e) => {if (e.key === 'Escape') showCalculator.value = false;
};
onMounted(() => {document.addEventListener('click', handleClickOutside);document.addEventListener('keydown', handleEscKey);
});
onUnmounted(() => {document.removeEventListener('click', handleClickOutside);document.removeEventListener('keydown', handleEscKey);
});watch(currentValue, (val) => {if (calculationPerformed.value && val !== '') emit('update:modelValue', val);
});const clear = () => {currentValue.value = '';previousValue.value = '';operation.value = null;calculationProcess.value = '';emit('update:modelValue', '');
};
const appendNumber = (n) => {if (calculationPerformed.value) {currentValue.value = '';calculationPerformed.value = false;calculationProcess.value = '';}currentValue.value += n;updateCalculationProcess();
};
const appendDecimal = () => {if (calculationPerformed.value) {currentValue.value = '0';calculationPerformed.value = false;calculationProcess.value = '';}if (!currentValue.value.includes('.')) currentValue.value += '.';updateCalculationProcess();
};
const setOperation = (op) => {if (!currentValue.value) return;if (previousValue.value) calculate();operation.value = op;previousValue.value = currentValue.value;currentValue.value = '';updateCalculationProcess();
};
const calculate = () => {if (!operation.value || !previousValue.value || !currentValue.value) {if (currentValue.value) {emit('update:modelValue', currentValue.value);if (calculationProcess.value === currentValue.value)calculationProcess.value = `${currentValue.value} =`;calculationPerformed.value = true;}return;}const prev = parseFloat(previousValue.value);const current = parseFloat(currentValue.value);let result = 0;switch (operation.value) {case '+': result = prev + current; break;case '-': result = prev - current; break;case '*': result = prev * current; break;case '/': result = prev / current; break;}const symbol = operation.value === '*' ? '×' : operation.value === '/' ? '÷' : operation.value;calculationProcess.value = `${previousValue.value} ${symbol} ${currentValue.value} =`;currentValue.value = result.toString();previousValue.value = '';operation.value = null;calculationPerformed.value = true;
};
const updateCalculationProcess = () => {const symbol = operation.value === '*' ? '×' : operation.value === '/' ? '÷' : operation.value;if (!operation.value) {calculationProcess.value = currentValue.value;} else if (!currentValue.value) {calculationProcess.value = `${previousValue.value} ${symbol}`;} else {calculationProcess.value = `${previousValue.value} ${symbol} ${currentValue.value}`;}
};
const changeSign = () => {currentValue.value = currentValue.value.charAt(0) === '-' ? currentValue.value.slice(1) : `-${currentValue.value}`;updateCalculationProcess();
};
const percentage = () => {currentValue.value = (parseFloat(currentValue.value) / 100).toString();updateCalculationProcess();
};
</script><style scoped>
.calculator-wrapper { position: relative; width: v-bind('width'); margin: 0 auto; }
.custom-input-container { position: relative; width: 100%; margin-bottom: 0.5rem; }
.custom-input { width: 100%; padding: 8px 30px 8px 10px; border: 1px solid #dcdfe6; border-radius: 4px; font-size: 14px; transition: border-color 0.2s; box-sizing: border-box; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); }
.custom-input:focus { outline: none; border-color: #409eff; box-shadow: 0 0 5px rgba(64, 158, 255, 0.3); }
.calculator-icon { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); cursor: pointer; padding: 3px; font-size: 16px; color: #606266; transition: color 0.2s; }
.calculator-icon:hover { color: #409eff; }
.calculator-container { position: absolute; top: 100%; left: 0; width: 100%; z-index: 100; margin-top: 5px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15); border-radius: 8px; }
.calculator { width: 100%; background-color: #f0f0f0; border-radius: 8px; overflow: hidden; }
.display { background-color: #333; color: white; padding: 10px; text-align: right; font-family: monospace; }
.calculation-process { font-size: 0.8rem; color: #aaa; min-height: 16px; margin-bottom: 4px; }
.current-value { font-size: 1.2rem; min-height: 24px; }
.buttons { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background-color: #999; }
.button { background-color: #e0e0e0; padding: 10px; text-align: center; cursor: pointer; font-size: 0.9rem; transition: background-color 0.2s; }
.button:hover { background-color: #d0d0d0; }
.operator { background-color: #f8a51b; color: white; }
.operator:hover { background-color: #e09016; }
.zero { grid-column: span 2; }
</style>

🧠 二、组件结构与逻辑全解析

🎯 目标:理解该组件的 UI 组成、逻辑功能、数据响应过程,便于拓展与优化。


📦 1. 组件功能概览

模块功能说明
自定义输入框输入数值,展示已计算结果
弹出计算器模拟标准四则运算计算器,支持连算与过程展示
状态响应双向绑定 v-model,每次计算结果自动同步外部状态
UI 样式视觉风格轻盈,适配大多数表单/工具栏场景

🔍 2. 模板结构解析 (<template>)

<div class="custom-input-container"><input ... /> <!-- 用户输入框 --><div class="calculator-icon"><span>🧮</span></div> <!-- 弹出按钮 -->
</div><div v-show="showCalculator" class="calculator-container"><div class="calculator"> <!-- 主计算器界面 --><div class="display"> <!-- 显示屏:过程 + 当前值 --><div class="calculation-process">{{ calculationProcess }}</div><div class="current-value">{{ currentValue }}</div></div><div class="buttons"> <!-- 20 个按键布局 --><!-- 数字、运算符、等号等 --></div></div>
</div>

📌 重点说明:

  • v-show="showCalculator" 控制计算器显示/隐藏;
  • custom-input-container 提供了清晰的输入交互入口;
  • 所有按钮通过事件绑定实现行为触发;

🧩 3. 脚本逻辑详解 (<script setup>)

📌 Props & Emits
const props = defineProps({modelValue: String,placeholder: String,width: String
});
const emit = defineEmits(['update:modelValue']);
  • 使用 v-model 实现组件外部与内部状态联动;
  • 支持自定义宽度与占位符提示。
⚙️ 核心响应式变量
const currentValue = ref('');         // 当前输入
const previousValue = ref('');        // 上一步数值
const operation = ref(null);          // 当前运算符
const calculationProcess = ref('');   // 顶部过程显示
const calculationPerformed = ref(false); // 是否已计算
const showCalculator = ref(false);    // 显示控制
🧠 主要功能函数
  • appendNumber(n) / appendDecimal():输入数字与小数点;
  • setOperation(op):设置运算符;
  • calculate():计算结果并展示过程;
  • clear():清空一切;
  • changeSign() / percentage():正负号、百分比扩展;
  • updateCalculationProcess():更新顶部计算过程字符串。
🖱️ 弹出交互逻辑
const handleClickOutside = (e) => {if (!calculatorRef.value.contains(e.target)) {showCalculator.value = false;}
};
  • 点击外部关闭;
  • 按下 ESC 键关闭;
  • 使用 ref + document.addEventListener 实现监听。

🎨 4. 样式美化亮点 (<style scoped>)

  • 采用浅灰 + 橙色运算符按钮突出视觉分组;
  • display 区域使用 monospace 字体,模拟真实计算器屏幕;
  • grid-template-columns: repeat(4, 1fr) 布局整齐 4 列按钮;
  • .zero 使用 grid-column: span 2 让 0 按钮宽两倍更真实;
  • 自定义输入框视觉统一,内嵌按钮轻巧无干扰。

🧩 三、拓展建议与实战应用

🚀 用好这组件,不止复制粘贴,更可以轻松定制与集成。

✅ 推荐使用场景

  • 💼 表单辅助数值输入(如商品价格、折扣设置)
  • 📊 表格单元格弹出式计算(配合 Element Plus 的表格)
  • 📱 移动端小屏工具栏简易计算

🔧 自定义拓展建议

功能需求实现方式
保留历史记录增加 historyList,每次计算后 push 新记录
支持快捷键输入监听键盘按键映射按钮功能
控制显示位置增加 placement props,实现顶部/右侧弹出方向切换
改为暗黑模式.calculator.display 的背景与字体颜色即可

✅ 总结:一个轻巧实用的 Vue 计算器组件

🔁 回顾一下:

  • ✅ 支持标准四则运算 + 输入框联动;
  • ✅ 样式美观、结构清晰、逻辑独立;
  • ✅ 适合复制粘贴,嵌入任何 Vue 3 项目中。

📌 如果这对你有帮助,欢迎收藏、点赞、关注!
📩 有问题评论区见,我会持续优化、发布更多实用组件 🚀

http://www.xdnf.cn/news/868429.html

相关文章:

  • SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动
  • Go语言学习-->go的跨平台编译
  • 基于C++实现(WinForm) LAN 的即时通信软件
  • 【笔记】PyCharm 使用问题反馈与官方进展速览
  • 开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B-function_tool(二)
  • IDEA中微服务指定端口启动
  • java31
  • Spring Boot 从Socket 到Netty网络编程(下):Netty基本开发与改进【心跳、粘包与拆包、闲置连接】
  • React组件基础
  • Python 2.7 退役始末:代码架构缺陷与社区演进路线图
  • Linux-linux和windows创建新进程的区别以及posix_spawn
  • 爬虫学习记录day1
  • Git Github Gitee GitLab
  • [特殊字符] 深度剖析 n8n 与 Dify:使用场景、优劣势及技术选型建议
  • 常用的Docker命令
  • 得物GO面试题及参考答案
  • Quick UI 组件加载到 Axure
  • [杰理]蓝牙状态机设计与实现详解
  • Android 3D球形水平圆形旋转,旋转动态更换图片
  • (2025)Windows修改JupyterNotebook的字体,使用JetBrains Mono
  • 【计算机网络】第3章:传输层—TCP 拥塞控制
  • MaskSearch:提升智能体搜索能力的新框架
  • Qwen3与MCP协议:重塑大气科学的智能研究范式
  • 文献分析指令
  • SSM spring Bean基础配置
  • 代理ip的原理,代理ip的类型有哪些?
  • Vue全局事件总线
  • 【Cursor】开发chrome插件,实现网页tab根据域名分组插件
  • 区块链+AI融合实战:智能合约如何结合机器学习优化DeFi风控?
  • 使用 React Native 开发鸿蒙(HarmonyOS)运动健康类应用的系统化准备工作