前后端分离的计算器 Gin Gorm Vite React

Calculator

2301-计算机学院-软件工程https://bbs.csdn.net/forums/ssynkqtd-05
这个作业要求在哪里https://bbs.csdn.net/topics/617377308
这个作业的目标实现一个前后端分离计算器
其他参考文献各大文档

前端仓库
后端仓库
demo网站

PSP StageDescriptionEstimated Time (minutes)Actual Time (minutes)
PlanningPlan the task3020
•EstimateEstimate how much time the task will take1510
DevelopmentDevelopment phase630610
AnalysisRequirement analysis (including learning new tech)3030
Design SpecGenerate design document6075
Design ReviewDesign review3045
Coding StandardCode standard (develop appropriate standards)3020
DesignDetailed design3030
CodingActual coding320300
Code ReviewCode review6060
TestTesting (self-testing, code modification, submit modification)7050
ReportingReporting phase9060
Test ReportTest report3020
Size MeasurementCalculate work size1510
Postmortem & Process Improvement PlanPostmortem, and propose process improvement plan4530
Totalnull660630

设计实现过程

  1. 项目规划与设计:

开始前,需要仔细规划项目的结构和功能。设计数据库模型、前端组件、API端点等。
考虑用户界面的设计和用户体验。TailwindCSS提供了很大的灵活性,可以轻松创建漂亮的界面。

  1. 学习新技术

掌握React组件开发、状态管理、路由、生命周期等方面的知识。
了解如何使用Vite构建现代的前端应用。
学习Gin框架的路由、中间件和API开发。
理解GORM作为对象关系映射(ORM)库的用法。
学习如何与MySQL数据库交互。

  1. 前后端通信:

需要编写API端点,使前端能够与后端进行数据交换。
掌握数据的CRUD操作(创建、读取、更新、删除)。

  1. 数据库设计与迁移:

设计数据库表结构,选择适当的数据类型和关系。
使用GORM的数据库迁移工具来确保数据库与代码的一致性。

  1. 状态管理:

学会使用React的状态管理工具,如useState和useEffect。
实现前端的状态管理,确保数据的一致性。

  1. 部署与优化:

部署前端和后端应用到生产环境,配置服务器、域名、SSL证书等。
进行性能优化,确保应用能够高效运行。
使用工具如Webpack来进行前端代码的优化。

  1. 错误处理与调试:

开发过程中,可能会遇到各种错误和问题。学会如何调试前端和后端代码。
实现友好的错误处理,向用户提供有用的错误信息。

成品展示

演示视频

1.错误提示

在这里插入图片描述
在这里插入图片描述

2.历史记录

在这里插入图片描述

2.科学计算器

在这里插入图片描述

3.利息计算

在这里插入图片描述

代码说明

1.前端

计算利息

  const calculateInterest = () => {
    // 根据term.Name 来计算存款时间
    let depositTime = 0;
    // term中分号前面是存款时间,后面是利率
    const type = term.split(':')[0];
    const rate = term.split(': ')[1].split('%')[0] / 100;

    if (calculatorType === 'deposit') {
      switch (type) {
        case '活期存款':
          depositTime = time;
          break;
        case '三个月':
          depositTime = 0.25;
          break;
        case '半年':
          depositTime = 0.5;
          break;
        case '一年':
          depositTime = 1;
          break;
        case '二年':
          depositTime = 2;
          break;
        case '三年':
          depositTime = 3;
          break;
        case '五年"':
          depositTime = 5;
          break;
        default:
          depositTime = 0;
          break;
      }
    } else {
      switch (type) {
        case '六个月':
          depositTime = 0.5;
          break;
        case '一年':
          depositTime = 1;
          break;
        case '一至三年':
          depositTime = time;
          break;
        case '三至五年':
          depositTime = time;
          break;
        case '五年以上':
          depositTime = time;
          break;
        default:
          depositTime = 0;
          break;
      }
    }
    // 计算利息
    const interest = money * rate * depositTime;
    console.log(money, rate, depositTime, interest, term);

    setInterest(interest);
  };

表达式计算

  const equal = async () => {
    if (areParenthesesMatching(text) === false) {
      setText('ERR');
      setStatus(1);
      setValue('');
      return;
    }
    try {
      if (text !== '') {
        const equalArr = text.split('');

        equalArr[text.split('').length - 1];

        if (sign === '^') {
          setSign('');
          setText(eval(mathPow(text)));
          setResult(eval(mathPow(text)));
          setStatus(1);
          setValue('');
          if ((text + '=' + eval(mathPow(text))).length < 20) {
            axiosSave(text + '=' + eval(mathPow(text)));
          }
          let history = await axios
            .get('http://203.15.1.138:8081/list')
            .then((response) => {
              return response.data.history;
            });
          setList(history);
          return;
        } else if (
          (sign === '/' || sign === '%') &&
          equalArr[equalArr.length - 1] === '0'
        ) {
          setText('ERR');
          setStatus(1);
          setValue('');
          return;
        }
        setText(eval(text));
        setResult(eval(text));
        setStatus(1);
        setValue('');
        if ((text + '=' + eval(text)).length < 20) {
          axiosSave(text + '=' + eval(text));
        }
        let history = await axios
          .get('http://203.15.1.138:8081/list')
          .then((response) => {
            return response.data.history;
          });
        setList(history);
      }
    } catch {
      setText('ERR');
      setStatus(1);
      setValue('');
    }
  };

幂运算

  const mathPow = (inputString) => {
    let parts = inputString.split('^');

    if (parts.length === 2) {
      let x = parts[0];
      let y = parts[1];
      let resultString = `Math.pow(${x},${y})`;
      return resultString;
    } else {
      console.log('输入字符串格式不正确');
      return '';
    }
  };

回退

  const del = () => {
    if (text !== '') {
      if (typeof text === 'number') {
        // 计算结果后值为数字类型  强制转成字符串类型
        setText(text.toString());
      }
      setText(text.substring(0, text.length - 1));
      setStatus(0);
    }
  };

清零

  const clear = () => {
    setText('');
    setStatus(0);
    setValue('');
  };

获取历史记录

  const showAxios = async () => {
    setShow(!show);
    if (show) {
      let history = await axios
        .get('http://203.15.1.138:8081/list')
        .then((response) => {
          return response.data.history;
        });
      setList(history);
    }
  };

保存历史记录

function axiosSave(value) {
  const postData = {
    history: value,
  };
  axios
    .post('http://203.15.1.138:8081/save', postData)
    .then((response) => {
      // 请求成功时的处理
      console.log('成功响应:', response.data);
    })
    .catch((error) => {
      // 请求失败时的处理
      console.error('错误:', error);
    });
}

获取利率表

  useEffect(() => {
    axios.get('http://203.15.1.138:8081/deposit').then((response) => {
      setDeposit(response.data.deposit);
    });
    axios.get('http://203.15.1.138:8081/loan').then((response) => {
      setLoan(response.data.loan);
    });
  }, []);

利率UI

      <div className="content rounded-lg bg-[#c7eeff] fixed left-2 top-1/2 -translate-y-1/2">
        <div className="overflow-x-auto">
          <table className="table">
            {/* head */}
            <thead>
              <tr className="text-sky-400">
                <th>Deposit</th>
                <th>Percentage</th>
              </tr>
            </thead>
            <tbody>
              {deposit.map((item, index) => {
                return (
                  <tr key={index} className="hover:text-sky-400">
                    <td>{item.Name}</td>
                    <td>{item.Percentage}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        <div className="overflow-x-auto">
          <table className="table">
            {/* head */}
            <thead>
              <tr className="text-sky-400">
                <th>Loan</th>
                <th>Percentage</th>
              </tr>
            </thead>
            <tbody>
              {loan.map((item, index) => {
                return (
                  <tr key={index} className="hover:text-sky-400">
                    <td>{item.Name}</td>
                    <td>{item.Percentage}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

利息UI

      <div className="content p-4 max-w-md mx-auto fixed left-52 top-1/2 -translate-y-1/2 bg-[#c7eeff] rounded-lg">
        <h2 className="text-2xl font-semibold mb-4 text-sky-400">
          Percentage Calculator
        </h2>
        <div className="mb-4 hover:text-sky-400">
          <label className="block text-sm font-medium">Calculator Type:</label>
          <select
            value={calculatorType}
            onChange={(e) => setCalculatorType(e.target.value)}
            className="select select-bordered select-sm w-full max-w-xs">
            <option value="loan">loan</option>
            <option value="deposit">deposit</option>
          </select>
        </div>
        <form>
          <div className="mb-4 hover:text-sky-400">
            <label htmlFor="term" className="block text-sm font-medium">
              Time Term
            </label>
            <select
              value={term}
              onChange={(e) => setTerm(e.target.value)}
              className="select select-bordered select-sm w-full max-w-xs">
              {calculatorType === 'loan'
                ? loan.map((item, index) => (
                    <option key={index}>
                      {item.Name}: {item.Percentage}%
                    </option>
                  ))
                : deposit.map((item, index) => (
                    <option key={index}>
                      {item.Name}: {item.Percentage}%
                    </option>
                  ))}
            </select>
          </div>
          <div className="mb-4 hover:text-sky-400">
            <label htmlFor="principal" className="block text-sm font-medium">
              Money Amount:
            </label>
            <input
              type="number"
              id="principal"
              className="input input-bordered input-accent input-sm w-full max-w-xs"
              value={money}
              onChange={(e) => setMoney(e.target.value)}
            />
          </div>
          <div className="mb-4 hover:text-sky-400">
            <label htmlFor="principal" className="block text-sm font-medium">
              Extra Time(year):
            </label>
            <input
              type="number"
              id="principal"
              className="input input-bordered input-accent input-sm w-full max-w-xs"
              value={time}
              onChange={(e) => setTime(e.target.value)}
            />
          </div>
          <div
            className="tooltip tooltip-right mt-2"
            data-tip="如果Time Term是时间段, 需要额外填写时间">
            <button
              type="button"
              className="btn btn-info text-white font-bold py-2 px-4 rounded-lg"
              onClick={calculateInterest}>
              Calculate
            </button>
          </div>
        </form>
        {interest !== 0 && (
          <div className="mt-4">
            <h3 className="text-lg font-semibold">Total Interest:</h3>
            <p className="text-xl">${interest.toFixed(2)}</p>
          </div>
        )}
      </div>

计算器UI

      <div className="content rounded-lg p-6 pb-3 w-96 bg-[#c7eeff] fixed left-1/2 top-1/2 -translate-y-1/2 -translate-x-1/2">
        {/* 显示区域 */}
        <div className="display mb-4 border border-gray-200 border-solid rounded-lg bg-slate-100">
          <div className="h-16 w-66 text-right leading-normal rounded-t-lg text-5xl mr-1 text-slate-500">
            {text}
          </div>
          <div className="h-10 w-66 text-right leading-normal rounded-b-lg text-2xl mr-2 text-slate-500">
            {value}
          </div>
        </div>
        {/* 键盘区 */}
        <div className="btn-cont pt-4">
          <div className="grid gap-x-3 grid-cols-4 mb-5">
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl text-sky-600 hover:text-red-400"
              onClick={del}>
              Del
            </button>
            <button
              className="text-2xl  m-2 w-14 h-14 rounded-3xl text-sky-600 hover:text-red-400"
              onClick={clear}>
              C
            </button>
            <button
              className="text-2xl  m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              ^
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              +
            </button>
          </div>
          <div className="grid gap-x-3 grid-cols-4 mb-3">
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              7
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              8
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              9
            </button>

            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              -
            </button>
          </div>
          <div className="grid gap-x-3 grid-cols-4  mb-3">
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              4
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              5
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              6
            </button>
            <button
              className="text-2xl  m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              *
            </button>
          </div>
          <div className="grid gap-x-3 grid-cols-4  mb-3">
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:fill-sky-400 fill-slate-400 flex items-center justify-center"
              onClick={change}>
              1
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              2
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              3
            </button>
            <button
              className="text-2xl  m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              /
            </button>
          </div>
          <div className="grid gap-x-3 grid-cols-4 mb-3">
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              0
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              .
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              %
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl text-sky-600 hover:text-sky-700"
              onClick={equal}>
              =
            </button>
          </div>
          <div className="grid gap-x-3 grid-cols-4  mb-3">
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:fill-sky-400 fill-slate-400 flex items-center justify-center"
              onClick={showAxios}>
              <svg
                className="w-6 h-6"
                viewBox="0 0 1024 1024"
                xmlns="http://www.w3.org/2000/svg">
                <path
                  d="M511.998 64C264.574 64 64 264.574 64 511.998S264.574 960 511.998 960 960 759.422 960 511.998 759.422 64 511.998 64z m353.851 597.438c-82.215 194.648-306.657 285.794-501.306 203.579S78.749 558.36 160.964 363.711 467.621 77.917 662.27 160.132c168.009 70.963 262.57 250.652 225.926 429.313a383.995 383.995 0 0 1-22.347 71.993z"
                  className="fill-inherit"></path>
                <path
                  d="M543.311 498.639V256.121c0-17.657-14.314-31.97-31.97-31.97s-31.97 14.314-31.97 31.97v269.005l201.481 201.481c12.485 12.485 32.728 12.485 45.213 0s12.485-32.728 0-45.213L543.311 498.639z"
                  className="fill-inherit"></path>
              </svg>
            </button>

            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              &#40;
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={change}>
              &#41;
            </button>
            <button
              className="text-2xl m-2 w-14 h-14 rounded-3xl hover:text-sky-400"
              onClick={() => {
                setText(text + result.toString());
              }}>
              Ans
            </button>
          </div>
        </div>
      </div>

历史记录UI

      {show && list.length != 0 && (
        <div className="content rounded-lg p-6 pb-3 bg-[#c7eeff] fixed right-36 top-1/2 -translate-y-1/2 text-2xl">
          <div className="text-2xl font-semibold mb-4 text-sky-400">
            History
          </div>
          {list.map((item, index) => {
            return (
              <div
                className="rounded-lg mb-3 display hover:text-sky-400"
                key={index}>
                {index + '=>' + item.History}
              </div>
            );
          })}
        </div>
      )}

2.后端

类型包装

type CalculatorHistory struct {
	Id      uint `gorm:"primaryKey"` 
	History string
	Date    time.Time `gorm:"type:timestamp"`
}

type TempSource struct {
	Name       string
	Percentage float64
}

后端跨域

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}

		c.Next()
	}
}

连接数据库

	dsn := "xxx:xxx@tcp(siau.top:3307)/homework?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("连接数据库失败")
		return
	}

服务代码

	r := gin.Default()
	r.Use(corsMiddleware()) // 一定要有跨域
	r.POST("/save", func(c *gin.Context) {
		var requestData struct {
			History string `json:"history"`
		}
		if err := c.BindJSON(&requestData); err != nil {
			c.JSON(200, gin.H{
				"message": "error",
			})
			return
		}
		var historys []CalculatorHistory
		db.Debug().Limit(-1).Order("date").Find(&historys)
		if len(historys) >= 10 {
			db.Debug().Delete(&historys[0])
		}
		calculatorHistory := CalculatorHistory{History: requestData.History, Date: time.Now()}
		db.Debug().Create(&calculatorHistory)
		c.JSON(200, gin.H{
			"message": "success",
		})

	})
	r.GET("/list", func(c *gin.Context) {
		var historys []CalculatorHistory
		db.Debug().Limit(-1).Order("date").Find(&historys) // 用Find

		c.JSON(200, gin.H{
			"message": "success",
			"history": historys,
		})
	})
	r.GET("/deposit", func(c *gin.Context) {
		var tempSources []TempSource
		tempSources = append(tempSources, TempSource{Name: "活期存款", Percentage: 0.5})
		tempSources = append(tempSources, TempSource{Name: "三个月", Percentage: 2.85})
		tempSources = append(tempSources, TempSource{Name: "半年", Percentage: 3.05})
		tempSources = append(tempSources, TempSource{Name: "一年", Percentage: 3.25})
		tempSources = append(tempSources, TempSource{Name: "二年", Percentage: 4.15})
		tempSources = append(tempSources, TempSource{Name: "三年", Percentage: 4.75})
		tempSources = append(tempSources, TempSource{Name: "五年", Percentage: 5.25})
		c.JSON(200, gin.H{
			"message": "success",
			"deposit": tempSources,
		})
	})
	r.GET("/loan", func(c *gin.Context) {
		var tempSources []TempSource
		tempSources = append(tempSources, TempSource{Name: "六个月", Percentage: 5.85})
		tempSources = append(tempSources, TempSource{Name: "一年", Percentage: 6.31})
		tempSources = append(tempSources, TempSource{Name: "一至三年", Percentage: 6.40})
		tempSources = append(tempSources, TempSource{Name: "三至五年", Percentage: 6.65})
		tempSources = append(tempSources, TempSource{Name: "五年以上", Percentage: 6.80})
		c.JSON(200, gin.H{
			"message": "success",
			"loan":    tempSources,
		})
	})
	err = r.Run(":8081")
	if err != nil {
		return
	} // 监听并在 0.0.0.0:8080 上启动服务

3.数据库

在这里插入图片描述

心路历程与收获

在开发这个全栈Web应用的过程中,我获得了丰富的经验和收获。这个项目让我深入了解了前端和后端的工作流程,以及它们之间的协作。我学到了如何使用React构建交互式用户界面,如何创建和管理前端状态,以及如何处理用户输入。同时,我也掌握了使用Vite这一现代前端构建工具的能力,它为项目提供了快速的开发和热更新功能。

在后端方面,我学会了使用Gin框架和GORM库来创建API端点和管理数据库。我了解了RESTful API的设计原则,实现了用户认证和授权功能,以确保数据的安全性。此外,我还熟悉了数据库的设计和迁移,以及如何执行各种数据库操作。

总的来说,这个项目让我成为一个更全面的开发者,具备了前端和后端开发的技能。我学到了如何将不同的技术堆栈整合到一个完整的应用中,解决了许多挑战和问题。这个经验也使我更有信心地面对未来的全栈开发项目,同时也激发了我对不断学习和提高技能的渴望。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值