1+ # 92-95每日油价桌面小组件
2+ # 数据来源:20121212.cn 油价接口
3+ # 城市可自定义设置
4+ # 注意:每次刷新都会从网络获取最新数据,无缓存
5+
6+ from widget import Widget , family , SMALL , MEDIUM , LARGE
7+ import datetime
8+ import json
9+
10+ # ═══════════════════════════════════════════════════════════
11+ # 配置区 - 设置你所在的城市
12+ # 支持城市:北京、上海、广州、深圳、杭州、南京、武汉、成都、重庆、西安、天津 等
13+ # ═══════════════════════════════════════════════════════════
14+ CITY_NAME = "北京" # ← 在这里修改城市名称
15+
16+ # ═══════════════════════════════════════════════════════════
17+ # 油价API接口
18+ # ═══════════════════════════════════════════════════════════
19+ OIL_PRICE_API = "https://20121212.cn/ci/index.php/iol/like/"
20+
21+
22+ def get_oil_price_from_api (city_name ):
23+ """
24+ 从油价API获取数据
25+ 接口:https://20121212.cn/ci/index.php/iol/like/{城市名}
26+ """
27+ try :
28+ import requests
29+ except ImportError :
30+ return None
31+
32+ url = f"{ OIL_PRICE_API } { city_name } "
33+
34+ try :
35+ resp = requests .get (url , timeout = 10 )
36+ text = resp .text
37+ resp .close ()
38+
39+ # 解析JSON数据
40+ data = json .loads (text )
41+
42+ # 检查是否成功返回数据
43+ if isinstance (data , list ) and len (data ) > 0 :
44+ item = data [0 ]
45+
46+ return {
47+ "price92" : float (item .get ("iol92" , 0 )),
48+ "price95" : float (item .get ("iol95" , 0 )),
49+ "price98" : float (item .get ("iol98" , 0 )),
50+ "price89" : float (item .get ("iol89" , 0 )),
51+ "price0" : float (item .get ("iol0" , 0 )),
52+ "price10" : float (item .get ("iol10" , 0 )),
53+ "price20" : float (item .get ("iol20" , 0 )),
54+ "price35" : float (item .get ("iol35" , 0 )),
55+ "ton92" : item .get ("ton92" , "" ),
56+ "ton95" : item .get ("ton95" , "" ),
57+ "ton98" : item .get ("ton98" , "" ),
58+ "update" : item .get ("date" , "" ),
59+ "city_raw" : item .get ("city" , "" ),
60+ }
61+ except Exception as e :
62+ print (f"API请求失败: { e } " )
63+
64+ return None
65+
66+
67+ def get_oil_price (city_name = None ):
68+ """
69+ 获取油价数据(直接请求API,不使用缓存)
70+ """
71+ # 使用配置的城市名
72+ if not city_name :
73+ city_name = CITY_NAME
74+
75+ # 直接从API获取
76+ api_data = get_oil_price_from_api (city_name )
77+ if api_data :
78+ return api_data , city_name , True
79+
80+ # API不可用,返回失败状态
81+ return None , city_name , False
82+
83+
84+ # ═══════════════════════════════════════════════════════════
85+ # 主程序 - 小组件渲染
86+ # ═══════════════════════════════════════════════════════════
87+
88+ # 获取油价数据
89+ oil_data , current_city , data_ok = get_oil_price ()
90+ now = datetime .datetime .now ()
91+
92+ # 配色方案 - 暖白主题
93+ BG_COLOR = ("#FFFBEB" , "#1C1917" )
94+ CARD_BG = ("#FEF3C7" , "#292524" )
95+ TITLE_COLOR = ("#78350F" , "#FDE68A" )
96+ TEXT_COLOR = ("#92400E" , "#D6D3D1" )
97+ SUB_COLOR = ("#B45309" , "#78716C" )
98+ ACCENT_COLOR = ("#EA580C" , "#FB923C" )
99+ ERROR_COLOR = ("#DC2626" , "#F87171" )
100+
101+ # 创建小组件
102+ w = Widget (background = BG_COLOR , padding = 14 )
103+
104+ if family == SMALL :
105+ # ═══ SMALL (127×127pt) ═══
106+ # 极简设计:单行展示核心油价,居中显示
107+ with w .vstack (spacing = 0 , align = "center" ):
108+ w .spacer () # 顶部弹性
109+
110+ # 图标 + 标题
111+ w .icon ("fuelpump.fill" , size = 16 , color = ACCENT_COLOR )
112+ w .spacer (4 )
113+ w .text ("油价" , size = 11 , weight = "medium" , color = SUB_COLOR )
114+
115+ w .spacer (6 )
116+
117+ # 核心数据
118+ if data_ok and oil_data :
119+ price92 = oil_data .get ("price92" , 0 )
120+ with w .hstack (spacing = 8 ):
121+ w .text ("92#" , size = 12 , weight = "semibold" , color = TEXT_COLOR )
122+ w .text (f"¥{ price92 :.2f} " , size = 20 , weight = "bold" ,
123+ design = "rounded" , color = TITLE_COLOR )
124+ else :
125+ w .text ("N/A" , size = 20 , weight = "bold" ,
126+ design = "rounded" , color = ERROR_COLOR )
127+
128+ w .spacer (4 )
129+
130+ # 副标题
131+ if data_ok :
132+ w .text (current_city , size = 10 , color = SUB_COLOR )
133+ else :
134+ w .text ("网络异常" , size = 10 , color = ERROR_COLOR )
135+
136+ w .spacer () # 底部弹性
137+
138+ elif family == MEDIUM :
139+ # ═══ MEDIUM (301×127pt) ═══
140+ # 整体居中:标题行居中 + 卡片组居中
141+ with w .vstack (spacing = 0 , align = "center" ):
142+ w .spacer () # 顶部弹性
143+
144+ # 顶部标题行 - 居中
145+ with w .hstack (spacing = 6 , align = "center" ):
146+ w .icon ("fuelpump.fill" , size = 14 , color = ACCENT_COLOR )
147+ w .text (f"{ current_city } 今日油价" , size = 14 , weight = "semibold" ,
148+ color = TITLE_COLOR )
149+ w .spacer ()
150+ if data_ok and oil_data and oil_data .get ("update" ):
151+ update_date = oil_data .get ("update" , "" )
152+ try :
153+ date_str = datetime .datetime .strptime (update_date [:10 ], "%Y-%m-%d" ).strftime ("%m-%d" )
154+ except :
155+ date_str = update_date [:10 ]
156+ w .text (date_str , size = 11 , color = SUB_COLOR )
157+ else :
158+ w .text ("加载中" , size = 11 , color = ERROR_COLOR )
159+
160+ w .spacer () # 弹性撑开
161+
162+ # 主数据区 - 三列卡片居中
163+ if data_ok and oil_data :
164+ price92 = oil_data .get ("price92" , 0 )
165+ price95 = oil_data .get ("price95" , 0 )
166+ price98 = oil_data .get ("price98" , 0 )
167+
168+ with w .hstack (spacing = 6 , align = "center" ):
169+ # 左侧 92号
170+ with w .card (background = CARD_BG , corner_radius = 10 ,
171+ padding = 10 , spacing = 0 ):
172+ with w .vstack (spacing = 2 , align = "center" ):
173+ w .text ("92#" , size = 11 , weight = "semibold" , color = SUB_COLOR )
174+ w .text (f"¥{ price92 :.2f} " , size = 20 , weight = "bold" ,
175+ design = "rounded" , color = TITLE_COLOR )
176+ w .text ("元/升" , size = 9 , color = SUB_COLOR )
177+
178+ # 中间 95号
179+ with w .card (background = CARD_BG , corner_radius = 10 ,
180+ padding = 10 , spacing = 0 ):
181+ with w .vstack (spacing = 2 , align = "center" ):
182+ w .text ("95#" , size = 11 , weight = "semibold" , color = SUB_COLOR )
183+ w .text (f"¥{ price95 :.2f} " , size = 20 , weight = "bold" ,
184+ design = "rounded" , color = TITLE_COLOR )
185+ w .text ("元/升" , size = 9 , color = SUB_COLOR )
186+
187+ # 右侧 98号
188+ with w .card (background = CARD_BG , corner_radius = 10 ,
189+ padding = 10 , spacing = 0 ):
190+ with w .vstack (spacing = 2 , align = "center" ):
191+ w .text ("98#" , size = 11 , weight = "semibold" , color = SUB_COLOR )
192+ w .text (f"¥{ price98 :.2f} " , size = 20 , weight = "bold" ,
193+ design = "rounded" , color = TITLE_COLOR )
194+ w .text ("元/升" , size = 9 , color = SUB_COLOR )
195+ else :
196+ # 网络异常时显示错误提示
197+ with w .card (background = CARD_BG , corner_radius = 10 ,
198+ padding = 16 , spacing = 0 ):
199+ with w .vstack (spacing = 4 , align = "center" ):
200+ w .icon ("wifi.slash" , size = 24 , color = ERROR_COLOR )
201+ w .text ("无法获取油价数据" , size = 13 , weight = "medium" ,
202+ color = TEXT_COLOR )
203+ w .text ("请检查网络连接" , size = 11 , color = SUB_COLOR )
204+
205+ w .spacer () # 底部弹性
206+
207+ else :
208+ # ═══ LARGE (301×317pt) ═══
209+ # 完整展示:头部+价格卡片+底部提示,全部居中
210+ with w .vstack (spacing = 0 , align = "center" ):
211+ w .spacer () # 顶部弹性
212+
213+ # 头部标题区 - 居中
214+ with w .hstack (spacing = 6 , align = "center" ):
215+ w .icon ("fuelpump.fill" , size = 18 , color = ACCENT_COLOR )
216+ w .text (f"{ current_city } 今日油价" , size = 18 , weight = "semibold" ,
217+ color = TITLE_COLOR )
218+ w .spacer ()
219+ if data_ok and oil_data and oil_data .get ("update" ):
220+ update_date = oil_data .get ("update" , "" )
221+ try :
222+ date_str = datetime .datetime .strptime (update_date [:10 ], "%Y-%m-%d" ).strftime ("%m月%d日" )
223+ except :
224+ date_str = update_date [:10 ]
225+ w .text (date_str , size = 12 , color = SUB_COLOR )
226+ else :
227+ w .text ("加载中" , size = 12 , color = ERROR_COLOR )
228+
229+ w .spacer (12 )
230+
231+ if data_ok and oil_data :
232+ price92 = oil_data .get ("price92" , 0 )
233+ price95 = oil_data .get ("price95" , 0 )
234+ price98 = oil_data .get ("price98" , 0 )
235+ price0 = oil_data .get ("price0" , 0 )
236+ update_date = oil_data .get ("update" , "" )
237+
238+ # 价格卡片区 - 2x2布局,全部居中
239+ with w .hstack (spacing = 8 , align = "center" ):
240+ # 92号卡片
241+ with w .card (background = CARD_BG , corner_radius = 12 , padding = 12 ):
242+ with w .vstack (spacing = 2 , align = "center" ):
243+ w .text ("92# 汽油" , size = 12 , weight = "medium" , color = SUB_COLOR )
244+ w .text (f"¥{ price92 :.2f} " , size = 26 , weight = "bold" ,
245+ design = "rounded" , color = TITLE_COLOR )
246+ w .text ("元/升" , size = 10 , color = SUB_COLOR )
247+
248+ # 95号卡片
249+ with w .card (background = CARD_BG , corner_radius = 12 , padding = 12 ):
250+ with w .vstack (spacing = 2 , align = "center" ):
251+ w .text ("95# 汽油" , size = 12 , weight = "medium" , color = SUB_COLOR )
252+ w .text (f"¥{ price95 :.2f} " , size = 26 , weight = "bold" ,
253+ design = "rounded" , color = TITLE_COLOR )
254+ w .text ("元/升" , size = 10 , color = SUB_COLOR )
255+
256+ w .spacer (8 )
257+
258+ with w .hstack (spacing = 8 , align = "center" ):
259+ # 98号卡片
260+ with w .card (background = CARD_BG , corner_radius = 12 , padding = 12 ):
261+ with w .vstack (spacing = 2 , align = "center" ):
262+ w .text ("98# 汽油" , size = 12 , weight = "medium" , color = SUB_COLOR )
263+ w .text (f"¥{ price98 :.2f} " , size = 26 , weight = "bold" ,
264+ design = "rounded" , color = TITLE_COLOR )
265+ w .text ("元/升" , size = 10 , color = SUB_COLOR )
266+
267+ # 0号柴油卡片
268+ with w .card (background = CARD_BG , corner_radius = 12 , padding = 12 ):
269+ with w .vstack (spacing = 2 , align = "center" ):
270+ w .text ("0# 柴油" , size = 12 , weight = "medium" , color = SUB_COLOR )
271+ w .text (f"¥{ price0 :.2f} " , size = 26 , weight = "bold" ,
272+ design = "rounded" , color = TITLE_COLOR )
273+ w .text ("元/升" , size = 10 , color = SUB_COLOR )
274+
275+ w .spacer () # 弹性撑开
276+
277+ # 底部提示区 - 居中
278+ with w .card (background = CARD_BG , corner_radius = 10 , padding = 10 ):
279+ with w .hstack (spacing = 8 , align = "center" ):
280+ w .icon ("info.circle.fill" , size = 14 , color = ACCENT_COLOR )
281+ with w .vstack (spacing = 1 ):
282+ w .text ("数据来源:油价查询接口" , size = 10 , color = SUB_COLOR )
283+ if update_date :
284+ w .text (f"更新时间 { update_date } ,实际价格以加油站为准" ,
285+ size = 10 , color = SUB_COLOR )
286+ else :
287+ w .text ("实际价格以加油站为准" ,
288+ size = 10 , color = SUB_COLOR )
289+ else :
290+ # 网络异常时显示错误提示
291+ with w .card (background = CARD_BG , corner_radius = 12 , padding = 20 ):
292+ with w .vstack (spacing = 8 , align = "center" ):
293+ w .icon ("wifi.slash" , size = 36 , color = ERROR_COLOR )
294+ w .text ("无法获取油价数据" , size = 16 , weight = "semibold" ,
295+ color = TEXT_COLOR )
296+ w .text ("请检查网络连接后刷新" , size = 12 , color = SUB_COLOR )
297+
298+ w .spacer ()
299+
300+ # 底部提示区
301+ with w .card (background = CARD_BG , corner_radius = 10 , padding = 10 ):
302+ with w .hstack (spacing = 8 , align = "center" ):
303+ w .icon ("exclamationmark.triangle.fill" , size = 14 , color = ERROR_COLOR )
304+ with w .vstack (spacing = 1 ):
305+ w .text ("提示" , size = 10 , weight = "medium" , color = TEXT_COLOR )
306+ w .text ("确保网络连接正常,小组件将在下次刷新时重试" ,
307+ size = 10 , color = SUB_COLOR )
308+
309+ w .spacer () # 底部弹性
310+
311+ # 渲染小组件
312+ w .render ()
0 commit comments