【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

相关推荐
前端老鹰6 分钟前
JavaScript Intl.RelativeTimeFormat:自动生成 “3 分钟前” 的国际化工具
前端·javascript
梦想CAD控件7 分钟前
(在线CAD插件)网页CAD实现图纸表格智能提取
前端·javascript·全栈
sorryhc28 分钟前
【AI解读源码系列】ant design mobile——Space间距
前端·javascript·react.js
页面仔Dony1 小时前
绝对路径与相对路径的区别及作用
前端·javascript
林太白1 小时前
Zustand状态库(简洁、强大、易用的React状态管理工具)
前端·javascript·react.js
YuJie2 小时前
vue3 无缝滚动
前端·javascript·vue.js
小野鲜2 小时前
前端打开新的独立标签页面,并且指定标签页的大小,管理新标签页面的打开和关闭(包含源码和使用文档)
前端·javascript
十五_在努力2 小时前
参透 JavaScript —— 解析浅拷贝、深拷贝及手写实现
前端·javascript
王六岁3 小时前
JavaScript值和引用详解:从栈堆内存到面试实战
javascript·面试
ikonan4 小时前
译:Chrome DevTools 实用技巧和窍门清单
前端·javascript