【VUE|CSS】实现一个定制化公式计算器(grid-area网格布局)

唠唠叨叨

对不起我有罪!!!!! 在草稿箱发现好多没填好的坑,一篇也发不了,我竟然断更都这么长时间了!! 【跪】俺有罪......

所以先整这个比较好整的先输出一篇~(整整六个月没更新,我的良心也痛!)

HTML代码

JavaScript 复制代码
<div class="ml-50 calculator">
 <div class="result" style="grid-area: result">{{ state.equation }}</div>
  <el-button style="grid-area: ac" @click="clear">AC</el-button>
  <el-button style="grid-area: plus-minus" @click="calculateToggle">±</el-button>
  <el-button style="grid-area: percent" @click="calculatePercentage">%</el-button>
  <el-button style="grid-area: add" @click="append('+')">+</el-button>
  <el-button style="grid-area: subtract" @click="append('-')">-</el-button>
  <el-button style="grid-area: multiply" @click="append('×')">×</el-button>
  <el-button style="grid-area: divide" @click="append('÷')">÷</el-button>
  <el-button style="grid-area: equal" @click="calculate">=</el-button>
  <el-button style="grid-area: number-1" @click="append(1)">1</el-button>
  <el-button style="grid-area: number-2" @click="append(2)">2</el-button>
  <el-button style="grid-area: number-3" @click="append(3)">3</el-button>
  <el-button style="grid-area: number-4" @click="append(4)">4</el-button>
  <el-button style="grid-area: number-5" @click="append(5)">5</el-button>
  <el-button style="grid-area: number-6" @click="append(6)">6</el-button>
  <el-button style="grid-area: number-7" @click="append(7)">7</el-button>
  <el-button style="grid-area: number-8" @click="append(8)">8</el-button>
  <el-button style="grid-area: number-9" @click="append(9)">9</el-button>
  <el-button style="grid-area: number-0" @click="append(0)">0</el-button>
  <el-button style="grid-area: dot" @click="append('.')">.</el-button>
  <el-button style="grid-area: del" @click="onDelete"><el-icon><Delete /></el-icon></el-button>
</div>

在页面中自定义一块区域,放上你需要在计算器中展示的按钮以及计算器的显示屏。 此处我们放置的是最基础的正负号、四则运算符号,以及AC清除、百分号、小数点和单个删除键,显示屏则是定义一个div。

实现效果

初始完成效果:

如图所示,我们在html中编写的按钮以及显示屏初步展示出来了,但是我们此时并无法通过它们完成任何操作,显示屏也只是我们最初默认设置的0。

(我知道很简陋啦)

因此接下来我们要在script中创建每个按钮的点击事件,把显示屏中的结果在state中定义一下,初始状态为0。 这里我还定义了方法中会用到的三个判断boolean值,初始状态均为false。

JS代码

定义响应式参数

javascript 复制代码
const state = reactive({
  equation: "0",
  isOperatorAdded: false,
  isDecimalAdded: false,
  isStarted: false,
})

1.判断输入为四则运算符:

javascript 复制代码
//输入+ - x /
  const isOperator = (character) => {
    return ["+", "-", "×", "÷"].indexOf(character) > -1;
  }

2.运算符和数字放入显示屏:

javascript 复制代码
const append = (character) => {
    if (state.equation.length >= 10) {
      alert("超出长度限制");
      clear();
      return;
    }
    //首次输入时不为操作符
    if (state.equation === "0" && !isOperator(character)) {
      if (character === ".") {
        state.equation += "" + character; //引号的作用将其转换成字符串
        state.isDecimalAdded = true;
      } else {
        state.equation = "" + character;
      }
      state.isStarted = true;
      return;
    }

    //if NUmber
    if (!isOperator(character)) {
      //防止连续输入.
      if (character === "." && state.isDecimalAdded) {
        return;
      }
      if (character === ".") {
        state.isDecimalAdded = true;
        state.isOperatorAdded = true;
      } else {
        state.isOperatorAdded = false;
      }
      state.equation += "" + character;
    }
    //Add Operator
    if (isOperator(character) && !state.isOperatorAdded) {
      state.equation += "" + character;
      state.isDecimalAdded = false;
      state.isOperatorAdded = true;
    }
  }

3.点击=号时的算式计算:

javascript 复制代码
//=
  const calculate = () => {
    let result = state.equation
      .replace(new RegExp("×", "g"), "*")
      .replace(new RegExp("÷", "g"), "/");
    state.equation = parseFloat(eval(result).toFixed(9)).toString();
    state.isDecimalAdded = false;
    state.isOperatorAdded = false;
  }

4.点击正负号后的运算:

javascript 复制代码
/正负号+/-
  const calculateToggle = () => {
    if (state.isOperatorAdded || !state.isStarted) {
      return;
    }
    state.equation = state.equation + "* -1";
    calculate();
  }

5.点击百分号后的运算:

javascript 复制代码
//%
  const calculatePercentage = () => {
    if (state.isOperatorAdded || !state.isStarted) {
      return;
    }
    state.equation = state.equation + "* 0.01";
    calculate();
  }

6..清除按钮:

javascript 复制代码
//AC
  const clear = () => {
    state.equation = "0"
    state.isDecimalAdded = false
    state.isOperatorAdded = false
    state.isStarted = false
  }

7.单个字符删除的运算:

javascript 复制代码
/Del退格
  const onDelete = () => {
    state.equation = state.equation.substring(0,state.equation.length-1);
    if (state.equation.length == 0) {
      state.equation = 0
    }
  }

那么经过这些方法的运算,一个有着基本功能的计算器就初具雏形了,我们还可以根据自己的需要定制我们需要的符号,比如"("、")"、e、log、ln等。 我这边是由于项目需求定制了"("、")"和tag列表。

界面展示

算式:

结果:

百分号:

AC清除:

CSS实现

但是看起来是不是不太对劲,虽然看着功能实现了,但是样子看起来确实是差强人意,太丑了! 因此我们需要运用到css的样式和布局来给我们的计算器装修一番。我们在html中给显示屏以及各个按钮放上了style绑定了独一无二的grid-area属性。 在外部容器和显示屏的div中也绑定了样式类名。 grid-area 属性指定网格元素在网格布局中的大小和位置。 比如以下的例子: 以下实例 item1 命名为 "myArea", 并跨越五列: 实例

JavaScript 复制代码
.item1 { grid-area: myArea; }
.grid-container { grid-template-areas: 'myArea myArea myArea myArea myArea'; }

实现代码

可以看到item1所占的列数为5列。

JavaScript 复制代码
.calculator {
  --el-button-width: 80px;
  --el-button-height: 60px;
  display: grid; //网格布局
  grid-template-areas:
    "result result result result"
    "ac plus-minus percent divide"
    "number-7 number-8 number-9 multiply"
    "number-4 number-5 number-6 subtract"
    "number-1 number-2 number-3 add"
    "number-0 dot equal del";
  grid-template-columns: repeat(4, var(--el-button-width)); //repeat向xx方向重复数量
  grid-template-rows: repeat(5, var(--el-button-height)); 
  box-shadow: -8px -8px 16px -10px rgb(71, 66, 66),
    8px 8px 16px -10px rgba(0, 0, 0, 0.15);
  padding: 24px;
  border-radius: 20px; //圆角边框弧度
  .el-button {
    margin: 8px;
    padding: 0;
    border: 0;
    display: block;
    outline: none;
    border-radius: calc(var(--el-button-height) / 2);
    font-size: 24px;
    font-family: Helvetica;
    color: #999;
    background: linear-gradient( //linear-gradient()函数:渐变颜色
      135deg,
      rgba(230, 230, 230, 1) 0%,
      rgba(246, 246, 246, 1) 100%
    );
    box-shadow: -4px -4px 10px -8px rgba(255, 255, 255, 1), //阴影
      4px 4px 10px -8px rgba(0, 0, 0, 0.3);
  }
  .el-button:active {
    box-shadow: -4px -4px 10px -8px rgb(255, 255, 255, 1) inset,
      4px 4px 10px -8px rgba(0, 0, 0, 0.3) inset;
  }
}
.result {
  text-align: right;
  line-height: 60px;
  font-size: 48px;
  font-family: Arial, Helvetica, sans-serif;
  padding: 0 20px; //填充边距:上下0 左右20
  color: rgb(194, 190, 190);
  background: linear-gradient(
    135deg,
    rgb(156, 131, 131) 0%,
    rgb(96, 100, 143) 100%
  );
}

实现效果

总结和完整代码

经过一系列进化和调整,我把之前的代码bug修复了一番并且根据需求和设计美化了一下俺滴计算器。 以下就是这个计算器组件的完整代码和展示样式。

javascript 复制代码
<template>
  <el-dialog
    v-model="popVisible"
    :title="id ? '公式编辑器(编辑)' : '公式编辑器'"
    width="900px"
    :close-on-click-modal="false"
    :before-close="onCancel"
  ><el-row>
      <el-col :span="state.span">
        <el-form ref="ruleFormRef" :rules="rules" :model="formObj" label-width="auto" class="ml-20 pr-10" label-position="left">
          <el-form-item label="label1" prop="value1">
            <el-select v-model="formObj.value1" minlength="2" maxlength="20" show-word-limit placeholder="请选择名称" style="width:240px; padding: 0 20px;" disabled>
              <el-option v-for="item in state.typeList" :key="item.id" :label="item.label" :value="item.value" />
            </el-select>id
          </el-form-item>
          <el-form-item label="label2" prop="value2">
            <el-input v-model="formObj.value2" disabled style="width:240px; padding: 0 20px;"/>
          </el-form-item>
          <el-form-item :label="state.label">
            <el-button v-if="state.label" type="primary" @click="onEdit1">编辑{{state.label}}</el-button>
          </el-form-item>
          <div class="clappp mt-5">
            {{ formObj.aniCN }}
          </div>
          <el-form-item label="label3">
            <el-button type="primary" @click="onEdit2">编辑标准公式</el-button>
          </el-form-item>
          <div class="clappp mt-5">
            {{ formObj.bniCN }}
          </div>
          <div class="ml-20 mt-5"
            style="font-size: 8px;
            font-family: PingFangSC-Medium, PingFang SC;
            font-weight: 500;
            color: #CE2E2E;
            line-height: 15px;"
          >
            <h5>*公式计算器使用说明:</h5>
            <p>点击编辑按钮=》在计算器中输入自定义公式=》点击等于号=》点击修改=》提交成功</p>
          </div>
        </el-form>
      </el-col>
      <el-col :span="state.span">
        <div class="ml-50 mr-50 calculator">
          <div class="result" style="grid-area: result">{{ state.equation }}</div>
          <el-button style="grid-area: ac" @click="clear">AC</el-button>
          <!-- <el-button style="grid-area: plus-minus" @click="calculateToggle">±</el-button> -->
          <!-- <el-button style="grid-area: percent" @click="calculatePercentage">%</el-button> -->
          <el-button style="grid-area: l-bracket" @click="append('(','(')">(</el-button>
          <el-button style="grid-area: r-bracket" @click="append(')',')')">)</el-button>
          <el-button style="grid-area: divide" @click="append('/','/')">/</el-button>
          <div class="tag-list" style="grid-area: tag-list">
            <el-tag
              v-for="tag in state.tagList"
              :key="tag.id"
              :value="tag.sign"
              class="tag-style cursor-pointer"
              :checked="state.checked"
              @click="append(tag.name,tag.sign)"
            >
              {{ tag.name }}
            </el-tag>
          </div>
          <el-button style="grid-area: add" @click="append('+','+')">+</el-button>
          <el-button style="grid-area: subtract" @click="append('-','-')">-</el-button>
          <el-button style="grid-area: multiply" @click="append('*','*')">*</el-button>
          <el-button style="grid-area: number-1" @click="append(1,1)">1</el-button>
          <el-button style="grid-area: number-2" @click="append(2,2)">2</el-button>
          <el-button style="grid-area: number-3" @click="append(3,3)">3</el-button>
          <el-button style="grid-area: number-4" @click="append(4,4)">4</el-button>
          <el-button style="grid-area: number-5" @click="append(5,5)">5</el-button>
          <el-button style="grid-area: number-6" @click="append(6,6)">6</el-button>
          <el-button style="grid-area: number-7" @click="append(7,7)">7</el-button>
          <el-button style="grid-area: number-8" @click="append(8,8)">8</el-button>
          <el-button style="grid-area: number-9" @click="append(9,9)">9</el-button>
          <el-button style="grid-area: number-0" @click="append(0,0)">0</el-button>
          <el-button style="grid-area: dot" @click="append('.','.')">.</el-button>
          <el-button style="grid-area: equal;" @click="calculate">=</el-button>
          <!-- <el-button style="grid-area: del" @click="onDelete"><el-icon><Delete /></el-icon></el-button> -->
        </div>
      </el-col>
    </el-row>
    <!-- <el-alert class="mt-5" title="公式计算器使用说明" type="warning" show-icon>
      点击编辑按钮=》在计算器中输入自定义公式=》点击等于号=》点击修改=》提交成功
    </el-alert> -->
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="onCancel">取消</el-button>
        <el-button type="primary" @click="onCommit">修改</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup>
import { reactive, getCurrentInstance, watch, computed, ref, onMounted } from "vue";
import { WarningFilled, Plus, Search, Delete } from '@element-plus/icons-vue'
import { ElMessage } from "element-plus";
import { ApiConstant } from '@/constants/ApiConstant/index.js'
  const props = defineProps({
    visible: {
      type: Boolean,
      default: false,
    },
    id: {
      type: [Number, String],
      default: null,
    },
  })
  const emit = defineEmits(["close", "getData"])
  const { proxy } = getCurrentInstance();
  const state = reactive({
    checked: false,
    span: 12,
    equation: "0",
    label: '',
    isDecimalAdded: false,
    isOperatorAdded: false,
    isStarted: false,
    tagList: [],
    typeList: [],
    ani: false,
    bni: false,
    formula: "0"
  });
  const popVisible = computed(() => props.visible);
  const id = computed(() => props.id);
  const formObj = reactive({
    id: props.id,
    value1: '',
    value2: '',
    aniCN: '',
    bniCM: '',
    ani: '',
    bni: '',
  });
  const rules = reactive({
    ani: [
      {
        required: true,
        message: "请配置公式",
        trigger: "blur",
      },
    ],
    bni: [
      {
        required: true,
        message: "请配置公式",
        trigger: "change",
      },
    ],
    pointFormula: [
      {
        required: true,
        message: "请选择",
        trigger: "change",
      },
    ],
    onlyOnce: [
      {
        type: "array",
        required: true,
        message: "请勾选至少一个",
        trigger: "change",
      },
    ],
  });
  const onEdit1 = () => {
    state.ani= true
    state.bni= false
    state.equation = formObj.aniCN
    state.formula = formObj.ani
  }
  const onEdit2 = () => {
    state.bni= true
    state.ani = false
    state.equation = formObj.bniCN
    state.formula = formObj.bni
  }
  const onCancel = () => {
    proxy.$refs.ruleFormRef.resetFields();
    emit("close");
  };
  const onCommit = () => {
    console.log(formObj);
    proxy.$refs.ruleFormRef.validate((valid) => {
      if (valid) {
        if (props.id) {
          proxy.$axios.post(`${ url }/update`, formObj)
          .then((res) => {
            let resq = res.data;
            if (resq.code == 0) {
              ElMessage.success("修改成功");
              onCancel()
              emit("getData");
            } else {
              ElMessage.error(resq.msg);
            }
          });
        }
      }
    });
  };
  const resetForm = () => {
    proxy.$refs.ruleFormRef.resetFields();
  };
  //输入+ - x /
  const isOperator = (character) => {
    return ["+", "-", "*", "/"].indexOf(character) > -1;
  }
  //输入tag
  const isTags = (character) => {
    return (/[^\u4E00-\u9FA5]/g).indexOf(character) > -1;
  }
  // operators or Numbers
  const append = (character,x) => {
    console.log(character,x);
    if (state.equation.length >= 100) {
      alert("超出长度限制");
      clear();
      return;
    }
    //首次输入时不为操作符
    if (state.equation === "0" && !isOperator(character)) {
      if (character === ".") {
        state.equation += "" + character; //引号的作用将其转换成字符串
        state.formula += "" + x
        state.isDecimalAdded = true;
      } else {
        state.equation = "" + character;
        state.formula = "" + x;
      }
      state.isStarted = true;
      return;
    }
    //if NUmber
    if (!isOperator(character)) {
      //防止连续输入.
      if (character === "." && state.isDecimalAdded) {
        return;
      }
      if (character === ".") {
        state.isDecimalAdded = true;
        state.isOperatorAdded = true;
      } else {
        state.isOperatorAdded = false;
      }
      state.equation += "" + character;
      state.formula += "" + x
    }
    //Add Operator
    if (isOperator(character) && !state.isOperatorAdded) {
      state.equation += "" + character;
      state.formula += "" + character;
      state.isDecimalAdded = false;
      state.isOperatorAdded = true;
    }
  }
  //=
  const calculate = () => {
    // let result = state.equation
    //   .replace(new RegExp("*", "g"), "*")
    //   .replace(new RegExp("/", "g"), "/");

    // state.equation = parseFloat(eval(result).toFixed(9)).toString();
    // state.isDecimalAdded = false;
    // state.isOperatorAdded = false;
    if (state.ani) {
      formObj.aniCN = state.equation
      formObj.ani = state.formula
    } else if (state.bni) {
      formObj.bniCN = state.equation
      formObj.bni = state.formula
    }
  }
  //正负号+/-
  const calculateToggle = () => {
    if (state.isOperatorAdded || !state.isStarted) {
      return;
    }
    state.equation = state.equation + "* -1";
    state.formula = state.formula + "* -1";
    calculate();
  }
  //%
  const calculatePercentage = () => {
    if (state.isOperatorAdded || !state.isStarted) {
      return;
    }
    state.equation = state.equation + "* 0.01";
    state.formula = state.formula + "* 0.01";
    calculate();
  }
  //AC
  const clear = () => {
    state.equation = "0"
    state.formula = "0"
    state.isDecimalAdded = false
    state.isOperatorAdded = false
    state.isStarted = false
  }
  //Del退格
  const onDelete = () => {
    state.equation = state.equation.substring(0,state.equation.length-1);
    if (state.equation.length == 0) {
      state.equation = 0
    }
    state.formula = state.formula.substring(0,state.formula.length-1);
    if (state.formula.length == 0) {
      state.formula = 0
    }
  }
  const getSelect = () => {
    proxy.$axios.get(`${ url }/param`)
    .then((res) => {
      let resq = res.data;
      if (resq.code == 0) {
        state.tagList = resq.data
      } else {
        ElMessage.error(resq.msg);
      }
    });
    let params={
      current: 1,
      size: 9999
    }
    proxy.$axios.post(`${ url }/get-page`,params)
    .then((res) => {
      let resq = res.data
      if(resq.code == 0){
        state.typeList=resq.data.records
        state.typeList.map((item) => {
          if (item.value1== formObj.value) {
            formObj.label1 = item.label
          }
        })
      }else{
        ElMessage.error(resq.msg)
      }
    });
  }
  const tryOut = (row) => {
    var str = row;
    var arrayElement = []
    for (var i = 0; i < state.tagList.length ; i++) {
      arrayElement.push([state.tagList[i].sign, state.tagList[i].name], [0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6], [7, 7], [8, 8], [9, 9]);
    }
    var lookup = arrayElement.reduce(function(acc, arr) {
      acc[arr[0]] = arr[1];
      return acc;
    }, {});
    var res = str.replace(/[A-Za-z_]+/g, function(m) {
      return " " + lookup[m] + " ";
    }).trim();
    return res
  }
  watch(() => {
    state.equation = 0
    state.formula = 0
    return props.visible;
  },(newVal) => {
    if (newVal) {                                        
      getSelect()
      if (props.id) {
        // 根据id查询
        proxy.$axios.get(`${ url }/detail?id=${props.id}`)
        .then((res) => {
          let resq = res.data;
          if (resq.code == 0) {
            Object.assign(formObj , resq.data)
          } else {
            ElMessage.error(resq.msg);
          }
        });
      }
    }
  });
  onMounted(() => {
    getSelect()
  });
</script>

<style lang="scss" scoped>
.calculator {
  --el-button-width: 70px;
  --el-button-height: 45px;

  display: grid;
  grid-template-areas:
    "result result result result"
    "result result result result"
    "tag-list tag-list tag-list tag-list"
    "tag-list tag-list tag-list tag-list"
    "tag-list tag-list tag-list tag-list"
    "ac l-bracket r-bracket divide"
    "number-7 number-8 number-9 multiply"
    "number-4 number-5 number-6 subtract"
    "number-1 number-2 number-3 add"
    "number-0 dot equal equal";

  grid-template-columns: repeat(4, var(--el-button-width));
  grid-template-rows: repeat(6, var(--el-button-height));
  padding: 20px;
  border-radius: 11px;
  border: 2px solid #3C5EB3;

  .el-button {
    margin: 8px;
    padding: 0;
    border: 0;
    display: block;
    outline: none;
    border-radius: 3px;
    font-size: 15px;
    font-family: PingFangSC-Medium, PingFang SC;
    font-weight: 500;
    color: #333333;
    line-height: 20px;
    background: #F0F0F0;
    border-radius: 3px;
    box-shadow: -4px -4px 10px -8px rgba(255, 255, 255, 1),
      4px 4px 10px -8px rgba(0, 0, 0, 0.3);
  }
  .tag-list{
    .tag-style{
      margin: 5px;
      width: 83px;
      height: 24px;
      font-size: 9px;
      font-size: 9px;
      justify-content: center;
      font-family: PingFangSC-Medium, PingFang SC;
      font-weight: 500;
      color: #3C5EB3;
      line-height: 13px;
    }
  }
  .el-button:active {
    box-shadow: -4px -4px 10px -8px rgb(255, 255, 255, 1) inset,
      4px 4px 10px -8px rgba(0, 0, 0, 0.3) inset;
  }
}
.clappp{
  margin: 20px;
  padding: 10px;
  width: 90%;
  height: 94px;
  text-align: right;
  font-size: 8px;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #3C5EB3;
  line-height: 16px;
  background: #EFEFEF;
}
.result {
  text-align: right;
  font-size: 20px;
  margin-bottom: 5px;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #3C5EB3;
  line-height: 28px;
  padding: 0 20px;
  background: #F1F5FF;
  border-radius: 3px;
}
</style>

调整后完成效果:

参考资料

grid-area-CSS:叠层样式表 | MDN

相关推荐
栈老师不回家1 小时前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙1 小时前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~2 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai3 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨3 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
王哲晓4 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js