station.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259
  1. import time
  2. import conf
  3. import cv2
  4. import random
  5. import traceback
  6. import abc
  7. import tkinter as tk
  8. from tkinter import ttk
  9. import datetime
  10. from PIL import Image, ImageTk
  11. from tool.type_ import *
  12. from tool.tk import set_tk_disable_from_list, make_font
  13. from core.user import User, UserNotSupportError
  14. from core.garbage import GarbageBag, GarbageType, GarbageBagNotUse
  15. from sql.db import DB
  16. from sql.user import update_user, find_user_by_id
  17. from sql.garbage import update_garbage
  18. from equipment.scan import HGSCapture, HGSQRCoder
  19. from event import TkEventMain
  20. class GarbageStationException(Exception):
  21. ...
  22. class ControlNotLogin(GarbageStationException):
  23. ...
  24. class ThrowGarbageError(GarbageStationException):
  25. ...
  26. class CheckGarbageError(GarbageStationException):
  27. ...
  28. class RankingUserError(GarbageStationException):
  29. ...
  30. class GarbageStationBase(TkEventMain, metaclass=abc.ABCMeta):
  31. status_normal = 1
  32. status_get_garbage_type = 2
  33. status_get_garbage_check = 3
  34. scan_switch_user = 1
  35. scan_throw_garbage = 2
  36. scan_check_garbage = 3
  37. scan_no_to_done = 4
  38. def __init__(self,
  39. db: DB,
  40. cap: HGSCapture,
  41. qr: HGSQRCoder,
  42. loc: location_t = conf.base_location):
  43. self._db: DB = db
  44. self._cap: HGSCapture = cap
  45. self._qr: HGSQRCoder = qr
  46. self._loc: location_t = loc
  47. self._user: Optional[User] = None # 操作者
  48. self._user_last_time: time_t = 0
  49. self._garbage: Optional[GarbageBag] = None
  50. self._flat = GarbageStationBase.status_normal
  51. self._have_easter_eggs = False
  52. self.rank = None
  53. self.rank_index = 0
  54. super(GarbageStationBase, self).__init__()
  55. def get_db(self):
  56. return self._db
  57. def update_user_time(self):
  58. if self.check_user():
  59. self._user_last_time = time.time()
  60. def get_user(self):
  61. return self._user
  62. def is_manager(self):
  63. if not self.check_user():
  64. return False
  65. return self._user.is_manager()
  66. def check_user(self):
  67. if self._user is None:
  68. return False
  69. if not self._user.is_manager() and time.time() - self._user_last_time > 20:
  70. self._user = None
  71. return False
  72. return True
  73. def __check_user(self):
  74. if not self.check_user():
  75. raise ControlNotLogin
  76. self._user_last_time = time.time()
  77. def __check_normal_user(self):
  78. self.__check_user()
  79. if self._user.is_manager():
  80. raise UserNotSupportError
  81. def __check_manager_user(self):
  82. self.__check_user()
  83. if not self._user.is_manager():
  84. raise UserNotSupportError
  85. def get_user_info(self):
  86. self.__check_user()
  87. return self._user.get_info()
  88. def get_uid_no_update(self):
  89. if not self.check_user():
  90. return ""
  91. return self._user.get_uid()
  92. def get_user_info_no_update(self) -> Dict[str, str]:
  93. if not self.check_user():
  94. return {}
  95. return self._user.get_info()
  96. def get_cap_img(self):
  97. return self._cap.get_frame()
  98. def switch_user(self, user: User) -> bool:
  99. """
  100. 切换用户: 退出/登录
  101. :param user: 新用户
  102. :return: 登录-True, 退出-False
  103. """
  104. if self._user is not None and self._user.get_uid() == user.get_uid() and self.check_user(): # 正在登陆期
  105. self._user = None # 退出登录
  106. self._user_last_time = 0
  107. return False
  108. self._user = user
  109. self._user_last_time = time.time()
  110. return True # 登录
  111. def throw_garbage_core(self, garbage: GarbageBag, garbage_type: enum):
  112. self.__check_normal_user()
  113. if not self._user.throw_rubbish(garbage, garbage_type, self._loc):
  114. raise ThrowGarbageError
  115. update_garbage(garbage, self._db)
  116. update_user(self._user, self._db)
  117. def check_garbage_core(self, garbage: GarbageBag, check_result: bool):
  118. self.__check_manager_user()
  119. user = find_user_by_id(garbage.get_user(), self._db)
  120. if user is None:
  121. raise GarbageBagNotUse
  122. if not self._user.check_rubbish(garbage, check_result, user):
  123. raise CheckGarbageError
  124. update_garbage(garbage, self._db)
  125. update_user(self._user, self._db)
  126. update_user(user, self._db)
  127. def ranking(self, limit: int = 0, order_by: str = 'DESC') -> list[Tuple[uid_t, uname_t, score_t, score_t]]:
  128. """
  129. 获取排行榜的功能
  130. :return:
  131. """
  132. if limit > 0:
  133. limit = f"LIMIT {int(limit)}"
  134. else:
  135. limit = ""
  136. if order_by != 'ASC' and order_by != 'DESC':
  137. order_by = 'DESC'
  138. cur = self._db.search((f"SELECT UserID, Name, Score, Reputation "
  139. f"FROM user "
  140. f"WHERE IsManager = 0 "
  141. f"ORDER BY Reputation {order_by}, Score {order_by} "
  142. f"{limit}"))
  143. if cur is None:
  144. raise RankingUserError
  145. return list(cur.fetchall())
  146. def to_get_garbage_type(self, garbage: GarbageBag):
  147. self._flat = GarbageStationBase.status_get_garbage_type
  148. self.set_garbage(garbage)
  149. def to_get_garbage_check(self, garbage: GarbageBag):
  150. self._flat = GarbageStationBase.status_get_garbage_check
  151. self.set_garbage(garbage)
  152. def set_garbage(self, garbage: GarbageBag):
  153. self._garbage = garbage
  154. def get_garbage(self) -> Optional[GarbageBag]:
  155. if not self.check_user():
  156. self._garbage = None
  157. return self._garbage
  158. def throw_garbage(self, garbage_type: enum):
  159. self.update_user_time()
  160. if self._flat != GarbageStationBase.status_get_garbage_type or self._garbage is None:
  161. self.show_warning("操作失败", "请先登录, 扫描垃圾袋")
  162. return
  163. event = tk_event.ThrowGarbageEvent(self).start(self._garbage, garbage_type)
  164. self.push_event(event)
  165. self._flat = GarbageStationBase.status_normal
  166. self._garbage = None
  167. def check_garbage(self, check: bool):
  168. self.update_user_time()
  169. if self._flat != GarbageStationBase.status_get_garbage_check or self._garbage is None:
  170. self.show_warning("操作失败", "请先登录, 扫描垃圾袋")
  171. return
  172. event = tk_event.CheckGarbageEvent(self).start(self._garbage, check)
  173. self.push_event(event)
  174. self._flat = GarbageStationBase.status_normal
  175. self._garbage = None
  176. def show_garbage_info(self):
  177. self.update_user_time()
  178. if self._flat != GarbageStationBase.status_get_garbage_check or self._garbage is None:
  179. self.show_warning("操作失败", "请先登录, 扫描垃圾袋")
  180. return
  181. if not self._garbage.is_use():
  182. self.show_warning("操作失败", "垃圾袋还未被使用")
  183. return
  184. info = self._garbage.get_info()
  185. garbage_type = GarbageType.GarbageTypeStrList_ch[int(info['type'])]
  186. if self._garbage.is_check():
  187. time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(float(info['use_time'])))
  188. check = f'Checker is f{info["checker"][0:conf.tk_show_uid_len]}\n'
  189. if info["check"] == '1':
  190. check += f'检查结果为 投放正确\n'
  191. else:
  192. check += f'检查结果为 投放错误\n'
  193. self.show_msg("垃圾袋信息", (f"垃圾类型为 {garbage_type}\n"
  194. f"用户是 {info['user'][0:conf.tk_show_uid_len]}\n"
  195. f"地址:\n {info['loc']}\n"
  196. f"{check}"
  197. f"使用日期:\n {time_str}"), show_time=5.0) # 遮蔽Pass和Fail按键
  198. elif self._garbage.is_use():
  199. time_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(float(info['use_time'])))
  200. self.show_msg("垃圾袋信息", (f"垃圾类型为 {garbage_type}\n"
  201. f"用户是 {info['user'][0:conf.tk_show_uid_len]}\n"
  202. f"地址:\n {info['loc']}\n"
  203. f"垃圾袋还未检查\n"
  204. f"使用日期:\n {time_str}"), big=False, show_time=5.0) # 不遮蔽Pass和Fail按键
  205. else:
  206. self.show_msg("垃圾袋信息", f"垃圾袋还未使用", show_time=5.0) # 遮蔽Pass和Fail按键
  207. def show_user_info(self):
  208. self.update_user_time()
  209. if not self.check_user():
  210. self.show_warning("操作失败", "请先登录")
  211. return
  212. info = self.get_user_info()
  213. if info.get('manager', '0') == '1':
  214. self.show_msg("About User", (f"管理者用户\n"
  215. f"用户名: {info['name']}\n"
  216. f"用户ID:\n {info['uid']}"), show_time=5.0)
  217. else:
  218. self.show_msg("About User", (f"普通用户\n"
  219. f"用户名: {info['name']}\n"
  220. f"用户ID:\n {info['uid']}\n"
  221. f"评分: {info['score']}\n"
  222. f"垃圾分类信用: {info['reputation']}\n"
  223. f"垃圾量(7天): {info['rubbish']}"), show_time=5.0)
  224. def show_help_info(self):
  225. self.update_user_time()
  226. self.show_msg("帮助", f'''
  227. 所有用户: 离开前请确保账户退出(再次扫描用户二维码退出登录)
  228. 投放垃圾使用说明(普通用户):
  229. 1) 扫码用户二维码登录
  230. 2) 扫码垃圾袋二维码
  231. 3) 选择垃圾类型
  232. 4) 投放垃圾到垃圾桶 (投放完成, 可以离开或继续投放)
  233. 5) 等待结果反馈 (可能需要数日)
  234. 检测垃圾使用说明(管理用户):
  235. 1) 扫码用户二维码登录
  236. 2) 扫码垃圾袋二维码
  237. 3) 检查垃圾是否投放正确
  238. 4) 操作完成 (投放完成, 可以离开或继续投放)
  239. '''.strip(), show_time=60.0)
  240. def show_about_info(self):
  241. self.update_user_time()
  242. self.show_msg("关于", conf.about_info, show_time=5.0)
  243. def show_exit(self):
  244. self.update_user_time()
  245. if self.is_manager():
  246. self.exit_win()
  247. return
  248. self.show_warning("退出", f'用户不具有退出权限')
  249. def easter_eggs(self):
  250. self.update_user_time()
  251. if (not self._have_easter_eggs) and random.randint(0, 10) != 1: # 10% 概率触发
  252. return
  253. self._have_easter_eggs = True
  254. self.show_msg("Easter Agg", f'''
  255. 恭喜触发彩蛋
  256. 尝试一下新的编程语言: aFunlang.
  257. 来自: github
  258. [斯人若彩虹, 遇上方知有]
  259. [期待再次与你相遇]
  260. '''.strip(), show_time=15.0)
  261. def show_search_info(self):
  262. self.update_user_time()
  263. self.show_msg("搜索", f'''
  264. 搜索功能将根据摄像头获取物品信息, 反馈该物品垃圾类型。
  265. 该功能尚未开放, 敬请期待
  266. '''.strip(), show_time=5.0)
  267. def thread_show_rank(self, rank_list):
  268. self.rank = [[]]
  269. for i, r in enumerate(rank_list):
  270. if len(self.rank[-1]) == 5:
  271. self.rank.append([])
  272. color = None
  273. if i == 0:
  274. color = "#eaff56"
  275. elif i == 1:
  276. color = "#ffa631"
  277. elif i == 2:
  278. color = "#ff7500"
  279. elif r[0] == self.get_uid_no_update():
  280. color = "#b0a4e3"
  281. self.rank[-1].append((i + 1, r[1], r[0], r[2], r[3], color))
  282. if len(self.rank[0]) == 0:
  283. self.rank = None
  284. self.show_warning("排行榜错误", f'无法获取排行榜信息')
  285. return
  286. self.rank_index = 0
  287. self.get_rank(0)
  288. def get_rank(self, n: int):
  289. self.update_user_time()
  290. self.rank_index += n
  291. if self.rank_index < 0 or self.rank_index >= len(self.rank):
  292. self.show_warning("排行榜错误", f'无法获取排行榜信息')
  293. return
  294. self.show_rank(self.rank_index + 1, len(self.rank), self.rank[self.rank_index])
  295. def scan(self):
  296. """
  297. 处理扫码事务
  298. 二维码扫描的任务包括: 登录, 扔垃圾, 标记垃圾
  299. :return:
  300. """
  301. self._cap.get_image()
  302. qr_code = self._qr.get_qr_code()
  303. if qr_code is None:
  304. return GarbageStationBase.scan_no_to_done, None
  305. user_scan_event = tk_event.ScanUserEvent(self).start(qr_code)
  306. self.push_event(user_scan_event)
  307. def get_show_rank(self):
  308. event = tk_event.RankingEvent(self)
  309. self.push_event(event)
  310. @abc.abstractmethod
  311. def show_msg(self, title, info, msg_type='info', big: bool = True, show_time: float = 10.0):
  312. ...
  313. @abc.abstractmethod
  314. def show_warning(self, title, info, show_time: float = 15.0):
  315. ...
  316. @abc.abstractmethod
  317. def show_rank(self, page: int, page_c: int,
  318. rank_info: List[Tuple[int, uname_t, uid_t, score_t, score_t, Optional[str]]],
  319. title: str = '排行榜'):
  320. ...
  321. @abc.abstractmethod
  322. def hide_msg_rank(self, update: bool = False):
  323. ...
  324. @abc.abstractmethod
  325. def show_loading(self, title: str):
  326. ...
  327. @abc.abstractmethod
  328. def stop_loading(self):
  329. ...
  330. @abc.abstractmethod
  331. def set_after_run(self, ms, func, *args):
  332. ...
  333. @abc.abstractmethod
  334. def update_control(self):
  335. ...
  336. @abc.abstractmethod
  337. def update_scan(self):
  338. ...
  339. @abc.abstractmethod
  340. def update_msg(self):
  341. ...
  342. @abc.abstractmethod
  343. def mainloop(self):
  344. ...
  345. @abc.abstractmethod
  346. def exit_win(self):
  347. ...
  348. import station_event as tk_event
  349. class GarbageStation(GarbageStationBase):
  350. """
  351. GarbageStation 垃圾站系统
  352. 使用tkinter作为GUI
  353. """
  354. def set_after_run(self, ms, func, *args): # 正常运行前设置定时任务 super.__init__可能会调用
  355. self.init_after_run_list.append((ms, func, args))
  356. def __conf_set_after_run(self): # 配合 set_after_run 使用
  357. for ms, func, args in self.init_after_run_list:
  358. self.set_after_run_now(ms, func, *args)
  359. def set_after_run_now(self, ms, func, *args): # 正常运行时设置定时任务
  360. self._window.after(ms, func, *args)
  361. def __init__(self,
  362. db: DB,
  363. cap: HGSCapture,
  364. qr: HGSQRCoder,
  365. loc: location_t = conf.base_location,
  366. refresh_delay: int = conf.tk_refresh_delay):
  367. self.init_after_run_list: List[Tuple[int, Callable, Tuple]] = []
  368. super(GarbageStation, self).__init__(db, cap, qr, loc)
  369. self.refresh_delay = refresh_delay
  370. self._window = tk.Tk() # 系统窗口
  371. self._sys_height = self._window.winfo_screenheight()
  372. self._sys_width = self._window.winfo_screenwidth()
  373. self._win_height = int(self._sys_height * (2 / 3)) # 窗口高度
  374. self._win_width = int(self._sys_width * (2 / 3)) # 窗口宽度
  375. self._full_screen = False # 是否全屏模式
  376. self.__conf_windows()
  377. self._cap_img = None # 存储 PIL.image 的变量 防止gc释放
  378. self._user_im = None
  379. self._msg_time: Optional[float] = None # msg 显示时间累计
  380. self._msg_show_time: float = 10.0
  381. self._disable_all_btn: bool = False # 禁用所有按钮和操作
  382. self.__conf_font_size() # 配置字体大小
  383. self.__create_tk() # 创建组件
  384. self.__conf_tk() # 配置组件
  385. self.__conf_after()
  386. self.__conf_set_after_run()
  387. self.__switch_to_no_user()
  388. def __create_tk(self):
  389. """
  390. 创建tkinter组件
  391. 该函数只被 __init__ 调用
  392. """
  393. self._title_label = tk.Label(self._window) # 标题
  394. # 窗口操纵
  395. self._win_ctrl_button: List[tk.Button, tk.Button, tk.Button] = [tk.Button(self._window),
  396. tk.Button(self._window),
  397. tk.Button(self._window)]
  398. # 用户信息显示框
  399. win_info_type = Tuple[tk.Label, tk.Label, tk.Variable, str]
  400. self._user_frame = tk.Frame(self._window)
  401. self._user_name: win_info_type = (tk.Label(self._user_frame), tk.Label(self._user_frame),
  402. tk.StringVar(), "用户名")
  403. self._user_uid: win_info_type = (tk.Label(self._user_frame), tk.Label(self._user_frame),
  404. tk.StringVar(), "用户ID")
  405. self._user_score: win_info_type = (tk.Label(self._user_frame), tk.Label(self._user_frame),
  406. tk.StringVar(), "积分")
  407. self._user_rubbish: win_info_type = (tk.Label(self._user_frame), tk.Label(self._user_frame),
  408. tk.StringVar(), "垃圾量(7天)")
  409. self._user_eval: win_info_type = (tk.Label(self._user_frame), tk.Label(self._user_frame),
  410. tk.StringVar(), "垃圾分类信用")
  411. self._user_img = tk.Label(self._user_frame)
  412. # 扔垃圾相关按钮 (可回收, 不可回收, 有害垃圾, 厨余垃圾)
  413. self._throw_ctrl_frame = tk.Frame(self._window)
  414. self._throw_ctrl_btn: List[tk.Button] = [tk.Button(self._throw_ctrl_frame), tk.Button(self._throw_ctrl_frame),
  415. tk.Button(self._throw_ctrl_frame), tk.Button(self._throw_ctrl_frame)]
  416. # 确认垃圾相关按钮 (通过, 不通过)
  417. self._check_ctrl_frame = tk.Frame(self._window)
  418. self._check_ctrl_btn: List[tk.Button] = [tk.Button(self._check_ctrl_frame), tk.Button(self._check_ctrl_frame)]
  419. # 系统信息显示框 (垃圾袋ID和当前时间)
  420. self._sys_info_frame = tk.Frame(self._window)
  421. self._garbage_id: win_info_type = (tk.Label(self._sys_info_frame), tk.Label(self._sys_info_frame),
  422. tk.StringVar(), "垃圾袋ID")
  423. self._sys_date: win_info_type = (tk.Label(self._sys_info_frame), tk.Label(self._sys_info_frame),
  424. tk.StringVar(), "系统时间")
  425. # 摄像头显示
  426. self._cap_label = tk.Label(self._window)
  427. # 用户操纵按钮
  428. self._user_btn_frame = tk.Frame(self._window)
  429. self._user_btn: List[tk.Button] = [tk.Button(self._user_btn_frame), tk.Button(self._user_btn_frame),
  430. tk.Button(self._user_btn_frame)]
  431. # 消息提示框
  432. self._msg_frame = tk.Frame(self._window)
  433. self._msg_line = tk.Label(self._msg_frame)
  434. self._msg_label: Tuple[tk.Label, tk.Text, tk.Variable] = (tk.Label(self._msg_frame),
  435. tk.Text(self._msg_frame),
  436. tk.StringVar())
  437. self._msg_y_scroll = tk.Scrollbar(self._msg_frame)
  438. self._msg_x_scroll = tk.Scrollbar(self._msg_frame)
  439. self._msg_hide = tk.Button(self._msg_frame)
  440. # 排行榜
  441. self._rank_frame = tk.Frame(self._window)
  442. self._rank_label = [tk.Label(self._rank_frame),
  443. tk.Label(self._rank_frame),
  444. tk.Label(self._rank_frame),
  445. tk.Label(self._rank_frame),
  446. tk.Label(self._rank_frame),
  447. tk.Label(self._rank_frame)]
  448. self._rank_var = [tk.StringVar(),
  449. tk.StringVar(),
  450. tk.StringVar(),
  451. tk.StringVar(),
  452. tk.StringVar(),
  453. tk.StringVar()]
  454. self._rank_btn = [tk.Button(self._rank_frame), tk.Button(self._rank_frame), tk.Button(self._rank_frame)]
  455. # 进度条
  456. self._loading_frame = tk.Frame(self._window)
  457. self._loading_line = tk.Label(self._loading_frame)
  458. self._loading_title: Tuple[tk.Label, tk.Variable] = tk.Label(self._loading_frame), tk.StringVar()
  459. self._loading_pro = ttk.Progressbar(self._loading_frame)
  460. def __conf_font_size(self, n: Union[int, float] = 1):
  461. """
  462. 设置字体大小
  463. :param n: 缩放因子, 1为不缩放
  464. :return:
  465. """
  466. self._title_font_size = int(27 * n) # 标题
  467. self._win_ctrl_font_size = int(15 * n) # 控制按键
  468. self._win_info_font_size = int(18 * n) # 用户信息
  469. self._throw_ctrl_btn_font_size = int(20 * n) # 扔垃圾相关按钮
  470. self._check_ctrl_btn_font_size = int(20 * n) # 检查垃圾相关按钮
  471. self._sys_info_font_size = int(18 * n) # 系统信息
  472. self._user_btn_font_size = int(20 * n) # 用户按键
  473. self._msg_font_size = int(20 * n) # 消息提示框
  474. self._rank_font_title_size = int(24 * n) # 排行榜标题
  475. self._rank_font_size = int(16 * n) # 排行榜内容
  476. self._loading_tile_font = int(20 * n) # 进度条
  477. def __conf_tk(self):
  478. """
  479. 配置(绘制)窗口
  480. 当切换全屏或启动界面时需要调用
  481. :return:
  482. """
  483. self.__conf_title_label()
  484. self.__conf_win_ctrl_button()
  485. self.__conf_user_info_label()
  486. self.__conf_throw_btn()
  487. self.__conf_check_btn()
  488. self.__conf_sys_info_label()
  489. self.__conf_cap_label()
  490. self.__conf_user_btn()
  491. self.__conf_msg()
  492. self.__conf_rank()
  493. self.__conf_loading()
  494. self.hide_msg_rank() # 隐藏消息
  495. def __conf_windows(self):
  496. self._window.title('HGSSystem: Garbage Station')
  497. self._window.geometry(f'{self._win_width}x{self._win_height}')
  498. self._window['bg'] = conf.tk_win_bg
  499. self._window.resizable(False, False) # 禁止缩放
  500. self._window.protocol("WM_DELETE_WINDOW", lambda: self.show_exit()) # 设置标题栏[x]按钮
  501. self._window.overrideredirect(False) # 显示标题栏
  502. # 快捷键相关配置
  503. def lock_windows(_):
  504. if self._disable_all_btn:
  505. return
  506. self.__set_windows_overrideredirect(True)
  507. def unlock_windows(_):
  508. if self._disable_all_btn:
  509. return
  510. if self.is_manager():
  511. self.__set_windows_overrideredirect(False)
  512. return
  513. self.show_warning("解锁窗口失败", f'用户不具备解锁窗口权限'.strip())
  514. def full_screen_windows(_):
  515. if self._disable_all_btn:
  516. return
  517. if not self._full_screen or self.is_manager():
  518. self.__full_screen(not self._full_screen)
  519. return
  520. self.show_warning("切换全屏失败", f'用户不具备切换全屏权限'.strip())
  521. def easter_eggs(_):
  522. if self._disable_all_btn:
  523. return
  524. self.easter_eggs()
  525. self._window.bind("<Alt-Control-KeyPress-s>", lock_windows) # 锁定窗口
  526. self._window.bind("<Alt-Control-KeyPress-e>", unlock_windows) # 解锁窗口
  527. self._window.bind("<F11>", full_screen_windows) # 切换全屏
  528. self._window.bind("<F5>", easter_eggs) # 触发彩蛋
  529. def __full_screen(self, full: bool = True):
  530. """
  531. 切换全屏或非全屏
  532. 计算缩放因子(窗口变大倍数)
  533. 调用函数重绘窗口
  534. self.__conf_font_size
  535. self.__conf_tk
  536. :param full: True为切换至全屏, 否则切换至非全屏
  537. """
  538. self._window.attributes("-fullscreen", full)
  539. self._full_screen = full
  540. width = self._sys_width * (2 / 3)
  541. height = self._sys_height * (2 / 3)
  542. self._win_width = self._window.winfo_width()
  543. self._win_height = self._window.winfo_height()
  544. n = ((self._win_height / height) + (self._win_width / width)) / 2 # 平均放大倍数
  545. self.__conf_font_size(n)
  546. self.__conf_tk()
  547. def __set_windows_overrideredirect(self, show: bool = False):
  548. """
  549. :param show: True则显示标题栏, 否则不显示
  550. :return:
  551. """
  552. self._window.overrideredirect(show)
  553. def __conf_title_label(self):
  554. title_font = make_font(size=self._title_font_size, weight="bold")
  555. self._title_label['font'] = title_font
  556. self._title_label['bg'] = conf.tk_win_bg # 蜜瓜绿
  557. self._title_label['text'] = "HGSSystem: Garbage Station Control Center" # 使用英语标题在GUI更美观
  558. self._title_label['anchor'] = 'w'
  559. self._title_label.place(relx=0.02, rely=0.01, relwidth=0.7, relheight=0.07)
  560. def __conf_win_ctrl_button(self):
  561. title_font = make_font(size=self._win_ctrl_font_size)
  562. for bt in self._win_ctrl_button:
  563. bt: tk.Button
  564. bt['font'] = title_font
  565. bt['bg'] = conf.tk_btn_bg
  566. rely = 0.02
  567. relwidth = 0.05
  568. relheight = 0.05
  569. bt_help: tk.Button = self._win_ctrl_button[0]
  570. bt_help['text'] = '帮助'
  571. bt_help['command'] = lambda: self.show_help_info()
  572. bt_help.place(relx=0.81, rely=rely, relwidth=relwidth, relheight=relheight)
  573. bt_about: tk.Button = self._win_ctrl_button[1]
  574. bt_about['text'] = '关于'
  575. bt_about['command'] = lambda: self.show_about_info()
  576. bt_about.place(relx=0.87, rely=rely, relwidth=relwidth, relheight=relheight)
  577. bt_exit: tk.Button = self._win_ctrl_button[2]
  578. bt_exit['text'] = '退出'
  579. bt_exit['command'] = lambda: self.show_exit()
  580. bt_exit.place(relx=0.93, rely=rely, relwidth=relwidth, relheight=relheight)
  581. def __conf_user_info_label(self):
  582. title_font = make_font(size=self._win_info_font_size - 1, weight="bold")
  583. info_font = make_font(size=self._win_info_font_size + 1)
  584. frame_width = self._win_width * 0.4 # frame宽度 (像素)
  585. frame_height = self._win_height * 0.4 # frame高度 (像素)
  586. bg_color = "#FA8072" # 背景颜色
  587. self._user_frame['bg'] = bg_color
  588. self._user_frame['bd'] = 5
  589. self._user_frame['relief'] = "ridge"
  590. self._user_frame.place(relx=0.02, rely=0.1, relwidth=0.4, relheight=0.40)
  591. h_label = 5
  592. h_label_s = 1
  593. h_top = 2
  594. height_count = h_label * 5 + h_label_s * 4 + h_top * 2
  595. height_label = h_label / height_count
  596. height = h_top / height_count
  597. for lb_list in [self._user_score, self._user_rubbish, self._user_eval, self._user_name, self._user_uid]:
  598. lb_list[0]['font'] = title_font
  599. lb_list[0]['bg'] = bg_color
  600. lb_list[0]['fg'] = "#fffbf0" # 字体颜色使用象牙白
  601. lb_list[0]['text'] = lb_list[3] + ":"
  602. lb_list[0]['anchor'] = 'e'
  603. lb_list[0].place(relx=0.0, rely=height, relwidth=0.35, relheight=height_label)
  604. height += height_label + h_label_s / height_count
  605. for lb_list in [self._user_score, self._user_rubbish, self._user_eval, self._user_name, self._user_uid]:
  606. lb_list[1]['font'] = info_font
  607. lb_list[1]['bg'] = bg_color
  608. lb_list[1]['fg'] = "#000000" # 字体颜色使用纯黑
  609. lb_list[1]['textvariable'] = lb_list[2]
  610. lb_list[1]['anchor'] = 'w'
  611. lb_list[2].set('test')
  612. height = h_top / height_count
  613. for lb_list in [self._user_score, self._user_rubbish, self._user_eval]:
  614. lb_list[1].place(relx=0.36, rely=height, relwidth=0.19, relheight=height_label)
  615. height += height_label + h_label_s / height_count
  616. for lb_list in [self._user_name, self._user_uid]:
  617. lb_list[1].place(relx=0.36, rely=height, relwidth=0.63, relheight=height_label)
  618. height += height_label + h_label_s / height_count
  619. # 显示一张图片 (GUI更美观)
  620. img_relwidth = 0.30
  621. img_relheight = height_label * 3 + (h_label_s / height_count) * 2
  622. img = (Image.open(conf.pic_d['head']).
  623. resize((int(img_relwidth * frame_width), int(img_relheight * frame_height))))
  624. self._user_im = ImageTk.PhotoImage(image=img)
  625. self._user_img['image'] = self._user_im
  626. self._user_img.place(relx=1 - img_relwidth - 0.06, rely=0.09,
  627. relwidth=img_relwidth, relheight=img_relheight)
  628. self._user_img['bd'] = 5
  629. self._user_img['relief'] = "ridge"
  630. def __conf_throw_btn(self):
  631. btn_font = make_font(size=self._throw_ctrl_btn_font_size, weight="bold")
  632. btn_info: List[Tuple[str, str]] = [("可回收垃圾", "#00BFFF"), # 按钮文字及其颜色
  633. ("其他垃圾", "#A9A9A9"),
  634. ("有害垃圾", "#DC143C"),
  635. ("厨余垃圾", "#32CD32")]
  636. self.__show_throw_frame()
  637. for btn, info in zip(self._throw_ctrl_btn, btn_info):
  638. btn['font'] = btn_font
  639. btn['bg'] = info[1]
  640. btn['text'] = info[0]
  641. self._throw_ctrl_btn[0]['command'] = lambda: self.throw_garbage(GarbageType.recyclable)
  642. self._throw_ctrl_btn[1]['command'] = lambda: self.throw_garbage(GarbageType.other)
  643. self._throw_ctrl_btn[2]['command'] = lambda: self.throw_garbage(GarbageType.hazardous)
  644. self._throw_ctrl_btn[3]['command'] = lambda: self.throw_garbage(GarbageType.kitchen)
  645. self._throw_ctrl_btn[0].place(relx=0.000, rely=0.000, relwidth=0.495, relheight=0.495)
  646. self._throw_ctrl_btn[1].place(relx=0.505, rely=0.000, relwidth=0.495, relheight=0.495)
  647. self._throw_ctrl_btn[2].place(relx=0.000, rely=0.505, relwidth=0.495, relheight=0.495)
  648. self._throw_ctrl_btn[3].place(relx=0.505, rely=0.505, relwidth=0.495, relheight=0.495)
  649. def __conf_check_btn(self):
  650. btn_font = make_font(size=self._check_ctrl_btn_font_size, weight="bold")
  651. btn_info: List[Tuple[str, str]] = [("投放错误", "#ef7a82"), # 按钮文字及其颜色
  652. ("投放正确", "#70f3ff")]
  653. self.__show_check_frame()
  654. for btn, info in zip(self._check_ctrl_btn, btn_info):
  655. btn['font'] = btn_font
  656. btn['text'] = info[0]
  657. btn['bg'] = info[1]
  658. self._check_ctrl_btn[0]['command'] = lambda: self.check_garbage(False)
  659. self._check_ctrl_btn[1]['command'] = lambda: self.check_garbage(True)
  660. self._check_ctrl_btn[0].place(relx=0.000, rely=0.000, relwidth=0.495, relheight=1)
  661. self._check_ctrl_btn[1].place(relx=0.505, rely=0.000, relwidth=0.495, relheight=1)
  662. def __conf_sys_info_label(self):
  663. title_font = make_font(size=self._sys_info_font_size - 1, weight="bold")
  664. info_font = make_font(size=self._sys_info_font_size + 1)
  665. bg_color = "#F0F8FF"
  666. self._sys_info_frame['bg'] = bg_color
  667. self._sys_info_frame['bd'] = 5
  668. self._sys_info_frame['relief'] = "ridge"
  669. self._sys_info_frame.place(relx=0.02, rely=0.51, relwidth=0.4, relheight=0.14)
  670. h_label = 5
  671. h_label_s = 1
  672. h_top = 2
  673. height_count = h_label * 2 + h_label_s * 1 + h_top * 2
  674. height_label = h_label / height_count
  675. height = h_top / height_count
  676. for info_list in [self._garbage_id, self._sys_date]:
  677. info_list[0]['font'] = title_font
  678. info_list[0]['bg'] = bg_color
  679. info_list[0]['anchor'] = 'e'
  680. info_list[0]['text'] = info_list[3] + ":"
  681. info_list[0].place(relx=0.0, rely=height, relwidth=0.35, relheight=height_label)
  682. height += height_label + h_label_s / height_count
  683. height = h_top / height_count
  684. for info_list in [self._garbage_id, self._sys_date]:
  685. info_list[1]['font'] = info_font
  686. info_list[1]['bg'] = bg_color
  687. info_list[1]['textvariable'] = info_list[2]
  688. info_list[1]['anchor'] = 'w'
  689. info_list[2].set('test')
  690. info_list[1].place(relx=0.36, rely=height, relwidth=0.63, relheight=height_label)
  691. height += height_label + h_label_s / height_count
  692. def __conf_user_btn(self):
  693. btn_font = make_font(size=self._user_btn_font_size, weight="bold")
  694. btn_info: List[Tuple[str, str]] = [("详细信息", "#DDA0DD"),
  695. ("排行榜", "#B0C4DE"),
  696. ("搜索", "#F4A460")]
  697. self._user_btn_frame.place(relx=0.02, rely=0.66, relwidth=0.19, relheight=0.32)
  698. self._user_btn_frame['bg'] = conf.tk_win_bg
  699. """
  700. 计算标签和间隔的大小比例(相对于Frame)
  701. 标签和间隔的比例为5:1
  702. 3个标签和2个间隔
  703. (h_label * 3 + h_label_s * 2) 表示 Frame 总大小
  704. height_label 标签相对于 Frame 的大小
  705. height_sep 间隔相对于 Frame 的大小
  706. """
  707. h_label = 5
  708. h_label_s = 1
  709. height_label = h_label / (h_label * 3 + h_label_s * 2)
  710. height_sep = h_label_s / (h_label * 3 + h_label_s * 2)
  711. height = 0
  712. for btn, info in zip(self._user_btn, btn_info):
  713. btn['font'] = btn_font
  714. btn['text'] = info[0]
  715. btn['bg'] = info[1]
  716. btn.place(relx=0.0, rely=height, relwidth=1.00, relheight=height_label)
  717. height += height_label + height_sep
  718. self._user_btn[0]['state'] = 'disable' # 第一个按键默认为disable且点击无效果
  719. self._user_btn[1]['command'] = lambda: self.get_show_rank()
  720. self._user_btn[2]['command'] = lambda: self.show_search_info()
  721. def __conf_cap_label(self):
  722. self._cap_label['bg'] = "#000000"
  723. self._cap_label['bd'] = 5
  724. self._cap_label['relief'] = "ridge"
  725. self._cap_label.place(relx=0.22, rely=0.66, relwidth=0.2, relheight=0.32)
  726. def __conf_msg(self):
  727. title_font = make_font(size=self._msg_font_size + 1, weight="bold")
  728. info_font = make_font(size=self._msg_font_size - 1)
  729. bg_color = "#F5FFFA"
  730. self._msg_frame['bg'] = bg_color
  731. self._msg_frame['bd'] = 5
  732. self._msg_frame['relief'] = "ridge"
  733. # frame 不会立即显示
  734. self._msg_label[0]['font'] = title_font
  735. self._msg_label[0]['bg'] = bg_color
  736. self._msg_label[0]['anchor'] = 'w'
  737. self._msg_label[0]['textvariable'] = self._msg_label[2]
  738. self._msg_line['bg'] = '#000000' # 分割线
  739. self._msg_label[1]['font'] = info_font
  740. self._msg_label[1]['bg'] = bg_color
  741. self._msg_label[1]['fg'] = '#000000'
  742. self._msg_label[1]['state'] = 'disable'
  743. self._msg_label[1]['bd'] = 0
  744. self._msg_label[1]['exportselection'] = False
  745. self._msg_label[1]['selectbackground'] = bg_color # 被选中背景颜色和未选中颜色相同, 不启用选中
  746. self._msg_label[1]['selectforeground'] = '#000000'
  747. self._msg_label[1]['wrap'] = 'none'
  748. self._msg_y_scroll['orient'] = 'vertical'
  749. self._msg_y_scroll['command'] = lambda *args: (self.set_msg_time_now(), self._msg_label[1].yview(*args))
  750. self._msg_label[1]['yscrollcommand'] = lambda *args: (self.set_msg_time_now(), self._msg_y_scroll.set(*args))
  751. self._msg_x_scroll['orient'] = 'horizontal'
  752. self._msg_x_scroll['command'] = lambda *args: (self.set_msg_time_now(), self._msg_label[1].xview(*args))
  753. self._msg_label[1]['xscrollcommand'] = lambda *args: (self.set_msg_time_now(), self._msg_x_scroll.set(*args))
  754. self._msg_label[0].place(relx=0.05, rely=0.05, relwidth=0.9, relheight=0.1)
  755. self._msg_line.place(relx=0.05, rely=0.15, relwidth=0.9, height=1) # 一个像素的高度即可
  756. x_scroll = 0.03
  757. y_scroll = 0.02
  758. self._msg_label[1].place(relx=0.05, rely=0.20, relwidth=0.90 - y_scroll, relheight=0.64 - x_scroll)
  759. self._msg_x_scroll.place(relx=0.05, rely=0.84 - x_scroll, relwidth=0.90 - y_scroll, relheight=x_scroll)
  760. self._msg_y_scroll.place(relx=0.95 - y_scroll, rely=0.20, relwidth=y_scroll, relheight=0.64)
  761. self._msg_hide['font'] = info_font
  762. self._msg_hide['bg'] = conf.tk_btn_bg
  763. self._msg_hide['text'] = '关闭'
  764. self._msg_hide['command'] = lambda: self.hide_msg_rank(True)
  765. self._msg_hide.place(relx=0.42, rely=0.85, relwidth=0.16, relheight=0.09)
  766. def set_msg_time_now(self, show_time: float = 10.0):
  767. self._msg_time = time.time()
  768. self._msg_show_time = show_time
  769. def set_msg_time_none(self):
  770. self._msg_time = None
  771. def show_msg(self, title, info: str, msg_type='提示', big: bool = True, show_time: float = 10.0):
  772. if self._disable_all_btn:
  773. return
  774. self._msg_label[2].set(f'{msg_type}: {title}')
  775. self._msg_label[1]['state'] = 'normal'
  776. self._msg_label[1].delete(1.0, 'end') # 删除所有元素
  777. for i in info.split('\n'):
  778. self._msg_label[1].insert('end', i + '\n')
  779. self._msg_label[1]['state'] = 'disable'
  780. if big:
  781. self._msg_frame.place(relx=0.45, rely=0.15, relwidth=0.53, relheight=0.70)
  782. self._check_ctrl_frame.place_forget()
  783. self._throw_ctrl_frame.place_forget()
  784. self._rank_frame.place_forget()
  785. else:
  786. self._msg_frame.place(relx=0.45, rely=0.20, relwidth=0.53, relheight=0.50)
  787. self.__show_check_frame()
  788. self._throw_ctrl_frame.place_forget()
  789. self._rank_frame.place_forget()
  790. self.set_msg_time_now(show_time)
  791. def show_warning(self, title, info, show_time: float = 15.0):
  792. self.show_msg(title, info, msg_type='警告', show_time=show_time)
  793. def __conf_rank(self):
  794. title_font = make_font(size=self._rank_font_title_size, weight="bold")
  795. info_font = make_font(size=self._rank_font_size)
  796. btn_font = make_font(size=self._msg_font_size - 1)
  797. bg_color = "#F5F5DC"
  798. self._rank_frame['bg'] = bg_color
  799. self._rank_frame['relief'] = "ridge"
  800. self._rank_frame['bd'] = 5
  801. # frame 不会立即显示
  802. self._rank_label[0]['font'] = title_font
  803. self._rank_label[0]['bg'] = bg_color
  804. self._rank_label[0]['textvariable'] = self._rank_var[0]
  805. self._rank_label[0].place(relx=0.02, rely=0.00, relwidth=0.96, relheight=0.1)
  806. for lb, v in zip(self._rank_label[1:], self._rank_var[1:]):
  807. lb['font'] = info_font
  808. lb['bg'] = "#F5FFFA"
  809. lb['textvariable'] = v
  810. lb['relief'] = "ridge"
  811. lb['bd'] = 2
  812. # 标签结束的高度为 0.12 + 0.15 * 5 = 0.87
  813. for btn, text in zip(self._rank_btn, ("上一页", "关闭", "下一页")):
  814. btn['font'] = btn_font
  815. btn['bg'] = conf.tk_btn_bg
  816. btn['text'] = text
  817. self._rank_btn[0].place(relx=0.050, rely=0.88, relwidth=0.25, relheight=0.08)
  818. self._rank_btn[0]['command'] = lambda: self.get_rank(-1)
  819. self._rank_btn[1].place(relx=0.375, rely=0.88, relwidth=0.25, relheight=0.08)
  820. self._rank_btn[1]['command'] = lambda: self.hide_msg_rank(True)
  821. self._rank_btn[2].place(relx=0.700, rely=0.88, relwidth=0.25, relheight=0.08)
  822. self._rank_btn[2]['command'] = lambda: self.get_rank(+1)
  823. def __set_rank_info(self, rank_info: List[Tuple[int, uname_t, uid_t, score_t, score_t, Optional[str]]]):
  824. """
  825. 显示排行榜信息
  826. :param rank_info: 排行榜信息, 共计五个元组, 元组包含: 排名, uid, name, 分数, 评分
  827. :return:
  828. """
  829. if len(rank_info) > 5:
  830. rank_info = rank_info[:5]
  831. for lb in self._rank_label[1:]: # 隐藏全部标签
  832. lb.place_forget()
  833. height = 0.12
  834. for i, info in enumerate(rank_info):
  835. no, name, uid, score, eval_, color = info
  836. self._rank_var[i + 1].set(f"NO.{no} {name}\n\n" # 中间空一行 否则中文字体显得很窄
  837. f"ID: {uid[0:conf.ranking_tk_show_uid_len]} "
  838. f"信用: {eval_} 积分: {score}")
  839. if color is None:
  840. self._rank_label[i + 1]['bg'] = "#F5FFFA"
  841. else:
  842. self._rank_label[i + 1]['bg'] = color
  843. self._rank_label[i + 1].place(relx=0.04, rely=height, relwidth=0.92, relheight=0.13)
  844. height += 0.15
  845. def show_rank(self, page: int, page_c: int,
  846. rank_info: List[Tuple[int, uname_t, uid_t, score_t, score_t, Optional[str]]],
  847. title: str = '排行榜'):
  848. if self._disable_all_btn:
  849. return
  850. self._rank_var[0].set(f'{title} ({page}/{page_c})')
  851. self._rank_frame.place(relx=0.47, rely=0.15, relwidth=0.47, relheight=0.80)
  852. frame_width = self._win_width * 0.53
  853. for lb in self._rank_label[1:]:
  854. lb['wraplength'] = frame_width * 0.85 - 5 # 设定自动换行的像素
  855. if page == 1:
  856. self._rank_btn[0]['state'] = 'disable'
  857. else:
  858. self._rank_btn[0]['state'] = 'normal'
  859. if page == page_c:
  860. self._rank_btn[2]['state'] = 'disable'
  861. else:
  862. self._rank_btn[2]['state'] = 'normal'
  863. self.__set_rank_info(rank_info)
  864. self._throw_ctrl_frame.place_forget()
  865. self._check_ctrl_frame.place_forget()
  866. self._msg_frame.place_forget()
  867. self.set_msg_time_none()
  868. def hide_msg_rank(self, update: bool = False):
  869. self.__show_check_frame() # rank会令此消失
  870. self.__show_throw_frame() # rank和msg令此消失
  871. self._msg_frame.place_forget()
  872. self._rank_frame.place_forget()
  873. self.set_msg_time_none()
  874. if update:
  875. self.update_user_time()
  876. def __conf_loading(self):
  877. title_font = make_font(size=self._loading_tile_font, weight="bold")
  878. self._loading_frame['bg'] = conf.tk_win_bg
  879. self._loading_frame['bd'] = 5
  880. self._loading_frame['relief'] = "ridge"
  881. # frame 不会立即显示
  882. self._loading_title[0]['font'] = title_font
  883. self._loading_title[0]['bg'] = conf.tk_win_bg
  884. self._loading_title[0]['anchor'] = 'w'
  885. self._loading_title[0]['textvariable'] = self._loading_title[1]
  886. self._loading_title[0].place(relx=0.02, rely=0.00, relwidth=0.96, relheight=0.6)
  887. self._loading_line['bg'] = '#000000'
  888. self._loading_line.place(relx=0.02, rely=0.6, relwidth=0.96, height=1) # 一个像素的高度即可
  889. self._loading_pro['mode'] = 'indeterminate' # 来回显示
  890. self._loading_pro['orient'] = 'horizontal' # 横向进度条
  891. self._loading_pro['maximum'] = 100
  892. self._loading_pro.place(relx=0.02, rely=0.73, relwidth=0.96, relheight=0.22)
  893. def show_loading(self, title: str):
  894. self.set_all_btn_disable()
  895. self._loading_title[1].set(f"加载: {title}")
  896. self._loading_pro['value'] = 0
  897. self._loading_frame.place(relx=0.30, rely=0.40, relwidth=0.40, relheight=0.15)
  898. self._loading_pro.start(50)
  899. def stop_loading(self):
  900. self._loading_frame.place_forget()
  901. self._loading_pro.stop()
  902. self.set_reset_all_btn()
  903. def __show_check_frame(self):
  904. self._check_ctrl_frame.place(relx=0.45, rely=0.82, relwidth=0.53, relheight=0.16)
  905. def __show_throw_frame(self):
  906. self._throw_ctrl_frame.place(relx=0.45, rely=0.1, relwidth=0.53, relheight=0.70)
  907. def __define_after(self, ms, func, *args):
  908. self._window.after(ms, self.__after_func_maker(func), *args)
  909. def __conf_after(self):
  910. self.__define_after(self.refresh_delay, self.update_time)
  911. self.__define_after(self.refresh_delay, self.update_control)
  912. self.__define_after(self.refresh_delay, self.update_scan)
  913. self.__define_after(self.refresh_delay, self.update_msg)
  914. def __after_func_maker(self, func):
  915. def new_func(*args, **kwargs):
  916. try:
  917. func(*args, **kwargs)
  918. except: # 捕获未考虑的错误
  919. self.show_msg("系统错误", "运行时产生了错误...", "错误", show_time=3.0)
  920. traceback.print_exc()
  921. finally:
  922. self._window.after(self.refresh_delay, new_func)
  923. return new_func
  924. def update_time(self):
  925. var: tk.Variable = self._sys_date[2]
  926. t = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  927. var.set(f"{t}")
  928. def update_control(self):
  929. name: tk.Variable = self._user_name[2]
  930. uid: tk.Variable = self._user_uid[2]
  931. score: tk.Variable = self._user_score[2]
  932. rubbish: tk.Variable = self._user_rubbish[2]
  933. eval_: tk.Variable = self._user_eval[2]
  934. user_info: Dict[str, str] = self.get_user_info_no_update()
  935. if user_info.get('uid') is None:
  936. name.set('未登录')
  937. uid.set('未登录')
  938. eval_.set('---')
  939. rubbish.set('---')
  940. score.set('---')
  941. self.__switch_to_no_user()
  942. elif user_info.get('manager', '0') == '1':
  943. name.set(user_info.get('name'))
  944. uid_get = user_info.get('uid', None)
  945. if uid_get is None or len(uid_get) < 32:
  946. uid.set('error')
  947. else:
  948. uid.set(uid_get[0:21])
  949. eval_.set('无(管理员)')
  950. rubbish.set('无(管理员)')
  951. score.set('无(管理员)')
  952. self.__switch_to_manager_user()
  953. else:
  954. name.set(user_info.get('name'))
  955. uid_get = user_info.get('uid', None)
  956. if uid_get is None or len(uid_get) < 32:
  957. uid.set('error')
  958. else:
  959. uid.set(uid_get[0:conf.tk_show_uid_len])
  960. eval_.set(user_info.get('reputation'))
  961. rubbish.set(user_info.get('rubbish'))
  962. score.set(user_info.get('score'))
  963. self.__switch_to_normal_user()
  964. garbage = self.get_garbage()
  965. if garbage is None:
  966. self._garbage_id[2].set('---')
  967. else:
  968. gid = garbage.get_gid()
  969. if len(gid) > 20:
  970. gid = gid[-20:]
  971. self._garbage_id[2].set(gid)
  972. def update_scan(self):
  973. self.scan()
  974. # 需要存储一些数据 谨防被gc释放
  975. _cap_img_info = (Image.fromarray(cv2.cvtColor(self.get_cap_img(), cv2.COLOR_BGR2RGB)).
  976. transpose(Image.FLIP_LEFT_RIGHT))
  977. self._cap_img = ImageTk.PhotoImage(image=_cap_img_info)
  978. self._cap_label['image'] = self._cap_img
  979. def update_msg(self):
  980. if self._msg_time is None:
  981. return
  982. if time.time() - self._msg_time > self._msg_show_time: # 自动关闭消息
  983. self.hide_msg_rank()
  984. def __switch_to_normal_user(self):
  985. if self._disable_all_btn:
  986. return
  987. self.normal_user_disable()
  988. self.normal_user_able()
  989. def __switch_to_manager_user(self):
  990. if self._disable_all_btn:
  991. return
  992. self.manager_user_disable()
  993. self.manager_user_able()
  994. def __switch_to_no_user(self):
  995. self.manager_user_disable()
  996. self.normal_user_disable()
  997. self._user_btn[0]['state'] = 'disable'
  998. def normal_user_disable(self):
  999. set_tk_disable_from_list(self._check_ctrl_btn, flat='disable')
  1000. self._user_btn[0]['state'] = 'normal'
  1001. self._user_btn[0]['command'] = lambda: self.show_user_info()
  1002. def manager_user_disable(self):
  1003. set_tk_disable_from_list(self._throw_ctrl_btn, flat='disable')
  1004. self._user_btn[0]['state'] = 'normal'
  1005. self._user_btn[0]['command'] = lambda: self.show_garbage_info()
  1006. def normal_user_able(self):
  1007. set_tk_disable_from_list(self._throw_ctrl_btn, flat='normal')
  1008. def manager_user_able(self):
  1009. set_tk_disable_from_list(self._check_ctrl_btn, flat='normal')
  1010. def set_all_btn_disable(self):
  1011. self.__switch_to_no_user() # 禁用所有操作性按钮
  1012. self.hide_msg_rank()
  1013. set_tk_disable_from_list(self._user_btn, flat='disable')
  1014. set_tk_disable_from_list(self._win_ctrl_button, flat='disable')
  1015. self._disable_all_btn = True
  1016. def set_reset_all_btn(self):
  1017. set_tk_disable_from_list(self._user_btn, flat='normal')
  1018. set_tk_disable_from_list(self._win_ctrl_button, flat='normal')
  1019. self.update_control() # 位于_user_btn之后, 会自动设定detail按钮
  1020. self._disable_all_btn = False
  1021. def mainloop(self):
  1022. self._window.mainloop()
  1023. def exit_win(self):
  1024. self._window.destroy()
  1025. if __name__ == '__main__':
  1026. mysql_db = DB()
  1027. capture = HGSCapture()
  1028. qr_capture = HGSQRCoder(capture)
  1029. station_ = GarbageStation(mysql_db, capture, qr_capture)
  1030. station_.mainloop()