简单计算器-计算竞对每日推广花费

需求

计算竞对花费,复制最新记录等式

比如:点击一次2元,30天访客200

则每日推广花费为:200*2/30=13.33

Python代码

import asyncio import re import flet as ft class Calculator: # 接收 Flet 的页面对象,负责初始化页面标题和成员变量 优先执行 # init 是英文 initialization(初始化) 的缩写。 # 前后的双下划线 __(俗称“魔术方法”或“dunder方法”)是 Python 的约定,表示这是系统内置的特殊方法。 # Python 看到这个写法,就知道它不能像普通函数那样随便调用,而是要在特定时机自动触发 def __init__(self, page: ft.Page): # self:代表对象自身的引用 # 带 self. 的是对象属性(永久附着在对象上) self.page = page self.page.title = "高级计算器" self.answer = "" self.full_result = "" self.create_widgets() def create_widgets(self): """构建界面控件""" # 文本输入框 self.input_field = ft.TextField( label="输入算式", hint_text="例如: (50+30)*2/2", on_submit=self.calculate, prefix_icon=ft.Icons.FUNCTIONS, ) border_side = ft.BorderSide(1, ft.Colors.OUTLINE_VARIANT) # 容器 self.result_display = ft.Container( # ft.Text 默认是不可选中的 selectable=True 可选中 content=ft.Text(value="", size=24, weight=ft.FontWeight.BOLD, selectable=True), border_radius=12, # 边框 border=ft.Border(border_side, border_side, border_side, border_side), padding=15 ) # 轮廓按钮(Outlined Button)控件 clear_btn = ft.OutlinedButton("清空", icon=ft.Icons.CLEAR, on_click=self.clear_input) # 填充按钮 calc_btn = ft.FilledTonalButton("计算结果", icon=ft.Icons.CALCULATE, on_click=self.calculate) # 列 self.history_list = ft.Column( spacing=4, scroll=ft.ScrollMode.ALWAYS ) self.page.add( ft.Column([ ft.Row( [ ft.Icon(ft.Icons.CALCULATE, size=28, color=ft.Colors.PRIMARY), ft.Text("计算竞对花费", size=20, weight=ft.FontWeight.BOLD), ], spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER), ft.Text("计算结果自动复制到剪贴板", size=12, color=ft.Colors.OUTLINE), ft.Divider(height=8, color=ft.Colors.TRANSPARENT), self.input_field, ft.Divider(height=8, color=ft.Colors.TRANSPARENT), # ft.Row([calc_btn], alignment=ft.MainAxisAlignment.CENTER), ft.Row( [calc_btn, clear_btn], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, vertical_alignment=ft.CrossAxisAlignment.CENTER ), ft.Divider(height=8, color=ft.Colors.TRANSPARENT), ft.Text("计算结果:", size=12, weight=ft.FontWeight.W_600), ft.Divider(height=3, color=ft.Colors.TRANSPARENT), self.result_display, ft.Divider(height=8, color=ft.Colors.TRANSPARENT), ft.Row( [ ft.Text("历史记录:", size=12, weight=ft.FontWeight.W_600), ft.TextButton("复制最新记录", icon=ft.Icons.COPY, on_click=self.copy_history), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, ), ft.Container( content=self.history_list, border=ft.Border(border_side, border_side, border_side, border_side), border_radius=8, padding=8, expand=True ) ], spacing=0, expand=True, horizontal_alignment=ft.CrossAxisAlignment.STRETCH) ) def _snack(self, text): # 创建一个 SnackBar(消息条)控件。它通常出现在屏幕底部(移动端)或左下角(桌面端),几秒后自动消失,不会打断用户当前的操作 # 这个 SnackBar 一旦被添加到页面上,立刻自动弹出来,不用我再手动去点‘打开’按钮 s = ft.SnackBar(ft.Text(text), open=True) self.page.overlay.append(s) self.page.update() # 清空 def clear_input(self, e=None): self.input_field.value = "" # 在清空输入框后,把键盘光标(焦点)重新放回输入框里,让用户不用点鼠标,直接就能敲键盘输入新内容 asyncio.get_running_loop().create_task(self.input_field.focus()) self.page.update() async def calculate(self, e=None): """处理用户输入的算式并计算结果""" # 去空格 expr = self.input_field.value.strip() if not expr: self._snack("请输入计算公式") return try: # 安全检查:只允许数字和基本运算符 # 0-9 → 数字 # + → 加号 # \- → 减号(特别注意:减号在方括号里有特殊含义,用来表示范围如 a-z,所以必须用反斜杠转义成 \- 才能表示“减号”本身) # * → 乘号 # / → 除号 # % → 百分号 # () → 左右 括号 # . → 小数点(在方括号里不需要转义,就表示点本身) # \s → 空格(空白字符) # r'...'(原始字符串) if re.search(r'[^0-9+\-*/%().\s]', expr): raise ValueError("输入包含非法字符") # 算出一个数学结果 result = eval(expr) # 检查 eval 算出来的结果是不是浮点数(小数) if isinstance(result, float): # 四舍五入 将浮点数保留两位小数 result = round(result, 2) # 检查四舍五入后的数字是不是一个整数(比如 2.0、3.00),如果是,就把它强制转成 int(整数)类型,去掉小数点 if result.is_integer(): result = int(result) # 转字符串 self.answer = str(result) # f"..."(f-string,格式化字符串) # {expr.strip()} # 去掉 expr 字符串首尾的空白字符(空格、换行等) self.full_result = f"{expr.strip()}={result}" # 赋值 self.result_display.content.value = self.answer # 异步调用 _copy_result 方法,并将计算出的结果复制到系统剪贴板 await self._copy_result() # 追加到历史记录 self.history_list.controls.append(ft.Text(self.full_result, size=13, selectable=True)) # 更新页面 self.page.update() except ZeroDivisionError: self._snack("除数不能为零") except SyntaxError: self._snack("语法错误,请检查输入") # 传入了无效的值,值错误 except ValueError as ve: self._snack(str(ve)) # “万能兜底捕获”语句 没想到的错误 except Exception as ex: self._snack(f"计算错误: {ex}") # 复制 async def _copy_result(self): """将计算结果复制到剪贴板""" if self.answer: # ft.Clipboard() 是 Flet 框架提供的剪贴板操作客户端(系统粘贴板接口) await ft.Clipboard().set(self.answer) self._snack("答案已复制") # 异步函数 async def copy_history(self, e=None): """将历史记录全部复制到剪贴板""" # 生成一个列表,赋值给变量 lines。这个列表里装的都是字符串(历史记录的文本) # 遍历 self.history_list 这个容器里的所有子控件 # ft.Text 管“显示啥” # 查看控件列表是否为空 if not self.history_list: self._snack("没有历史记录") # 列表转字符串 return else: last_history = self.history_list.controls[-1] if isinstance(last_history, ft.Text): await ft.Clipboard().set(last_history.value) self._snack("已复制最新记录") async def main(page: ft.Page): # 窗口大小 page.window.width = 450 page.window.min_width = 400 page.window.height = 500 page.window.min_height = 460 page.theme = ft.Theme( color_scheme=ft.ColorScheme( primary=ft.Colors.GREEN_700 ) ) Calculator(page) await page.window.center() page.window.visible = True page.update() if __name__ == "__main__": ft.run(main, view=ft.AppView.FLET_APP_HIDDEN)

运行界面