欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Python簡單編寫一個(gè)股票監(jiān)控系統(tǒng)

 更新時(shí)間:2024年12月19日 10:31:16   作者:PieroPc  
這篇文章主要為大家詳細(xì)介紹了如何使用Python簡單編寫一個(gè)股票監(jiān)控系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

圖樣

最小化時(shí)

上代碼

import json
import logging
import threading
from typing import Dict, List
import efinance as ef
import time
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
import sys
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import queue
import requests
 
# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('stock_monitor.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
 
class StockMonitorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("股票監(jiān)控系統(tǒng)")
        self.root.geometry("1024x600")
        
        # 添加最小化事件綁定
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.bind("<Unmap>", self.on_minimize)
        
        self.float_window = None  # 懸浮窗口
        self.is_minimized = False
        
        self.monitor = StockMonitor()
        self.monitor.set_log_callback(self.add_log)
        self.running = False
        self.monitor_thread = None
        self.log_queue = queue.Queue()
        
        self.setup_gui()
        self.update_log()
        self.update_stock_table(self.get_initial_stock_data())
 
    def setup_gui(self):
        # 創(chuàng)建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 股票信息表格
        table_frame = ttk.LabelFrame(main_frame, text="股票監(jiān)控列表", padding="5")
        table_frame.grid(row=0, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        self.tree = ttk.Treeview(table_frame, columns=('代碼', '名稱', '當(dāng)前價(jià)格', '買入價(jià)', '賣出價(jià)', '狀態(tài)'), show='headings', height=8)  # 設(shè)置固定高度
        self.tree.heading('代碼', text='代碼')
        self.tree.heading('名稱', text='名稱')
        self.tree.heading('當(dāng)前價(jià)格', text='當(dāng)前價(jià)格')
        self.tree.heading('買入價(jià)', text='買入價(jià)')
        self.tree.heading('賣出價(jià)', text='賣出價(jià)')
        self.tree.heading('狀態(tài)', text='狀態(tài)')
        
        # 設(shè)置列寬
        self.tree.column('代碼', width=80)
        self.tree.column('名稱', width=100)
        self.tree.column('當(dāng)前價(jià)格', width=80)
        self.tree.column('買入價(jià)', width=80)
        self.tree.column('賣出價(jià)', width=80)
        self.tree.column('狀態(tài)', width=100)
        
        self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 添加滾動(dòng)條
        scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        # 日志顯示區(qū)域
        log_frame = ttk.LabelFrame(main_frame, text="運(yùn)行日志", padding="5")
        log_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        self.log_text = scrolledtext.ScrolledText(log_frame, height=8)  # 減小日志區(qū)域高度
        self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 控制按鈕
        button_frame = ttk.Frame(main_frame, padding="5")
        button_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E))
        
        # 使用更緊湊的按鈕布局
        self.start_button = ttk.Button(button_frame, text="開始監(jiān)控", command=self.start_monitoring, width=10)
        self.start_button.grid(row=0, column=0, padx=3)
        
        self.stop_button = ttk.Button(button_frame, text="停止監(jiān)控", command=self.stop_monitoring, state=tk.DISABLED, width=10)
        self.stop_button.grid(row=0, column=1, padx=3)
        
        self.add_button = ttk.Button(button_frame, text="添加股票", command=self.show_add_dialog, width=10)
        self.add_button.grid(row=0, column=2, padx=3)
        
        self.edit_button = ttk.Button(button_frame, text="修改股票", command=self.show_edit_dialog, width=10)
        self.edit_button.grid(row=0, column=3, padx=3)
        
        self.delete_button = ttk.Button(button_frame, text="刪除股票", command=self.delete_stock, width=10)
        self.delete_button.grid(row=0, column=4, padx=3)
        
        self.email_settings_button = ttk.Button(button_frame, text="郵件設(shè)置", command=self.show_email_settings, width=10)
        self.email_settings_button.grid(row=0, column=5, padx=3)
        
        self.qq_settings_button = ttk.Button(button_frame, text="QQ設(shè)置", command=self.show_qq_settings, width=10)
        self.qq_settings_button.grid(row=0, column=6, padx=3)
        
        self.weixin_settings_button = ttk.Button(button_frame, text="微信設(shè)置", command=self.show_weixin_settings, width=10)
        self.weixin_settings_button.grid(row=0, column=7, padx=3)
        
        # 調(diào)整窗口大小
        self.root.geometry("800x500")  # 減小窗口高度
        
        # 配置grid權(quán)重
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)
        main_frame.grid_rowconfigure(1, weight=1)  # 讓日志區(qū)域可以擴(kuò)展
        main_frame.grid_columnconfigure(0, weight=1)
 
    def update_log(self):
        while True:
            try:
                log_message = self.log_queue.get_nowait()
                self.log_text.insert(tk.END, log_message + '\n')
                self.log_text.see(tk.END)
            except queue.Empty:
                break
        self.root.after(100, self.update_log)
 
    def update_stock_table(self, stock_data):
        # 清空現(xiàn)有數(shù)據(jù)
        for item in self.tree.get_children():
            self.tree.delete(item)
            
        # 插入新數(shù)據(jù)
        for stock in stock_data:
            self.tree.insert('', tk.END, values=stock)
        
        # 更新懸浮窗口
        self.update_float_window(stock_data)
 
    def start_monitoring(self):
        self.add_log("啟動(dòng)股票監(jiān)控系統(tǒng)...")
        self.running = True
        self.start_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        self.monitor_thread = threading.Thread(target=self.monitoring_task)
        self.monitor_thread.daemon = True
        self.monitor_thread.start()
 
    def stop_monitoring(self):
        self.running = False
        self.start_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)
        self.add_log("正在停止股票監(jiān)控系統(tǒng)...")
 
    def monitoring_task(self):
        self.add_log("開始監(jiān)控股票...")
        while self.running:
            try:
                stock_data = self.monitor.get_stock_data()
                self.root.after(0, self.update_stock_table, stock_data)
                time.sleep(self.monitor.check_interval)
            except Exception as e:
                self.add_log(f"錯(cuò)誤: {str(e)}")
                time.sleep(self.monitor.check_interval)
        self.add_log("停止監(jiān)控股票...")
 
    def show_add_dialog(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("添加股票")
        dialog.geometry("300x200")
        dialog.transient(self.root)
        
        ttk.Label(dialog, text="股票代碼:").grid(row=0, column=0, padx=5, pady=5)
        code_entry = ttk.Entry(dialog)
        code_entry.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(dialog, text="股票名稱:").grid(row=1, column=0, padx=5, pady=5)
        name_entry = ttk.Entry(dialog)
        name_entry.grid(row=1, column=1, padx=5, pady=5)
        
        ttk.Label(dialog, text="買入價(jià):").grid(row=2, column=0, padx=5, pady=5)
        buy_entry = ttk.Entry(dialog)
        buy_entry.grid(row=2, column=1, padx=5, pady=5)
        
        ttk.Label(dialog, text="賣出價(jià):").grid(row=3, column=0, padx=5, pady=5)
        sell_entry = ttk.Entry(dialog)
        sell_entry.grid(row=3, column=1, padx=5, pady=5)
        
        def save_stock():
            try:
                new_stock = {
                    "code": code_entry.get(),
                    "name": name_entry.get(),
                    "buy_price": float(buy_entry.get()),
                    "sell_price": float(sell_entry.get())
                }
                
                if not new_stock["code"] or not new_stock["name"]:
                    messagebox.showerror("錯(cuò)誤", "股票代碼和名稱不能為空!")
                    return
                
                self.monitor.add_stock(new_stock)
                dialog.destroy()
                self.add_log(f"添加股票成功:{new_stock['name']}")
                self.update_stock_table(self.get_initial_stock_data())
            except ValueError:
                messagebox.showerror("錯(cuò)誤", "請(qǐng)輸入有效的價(jià)格!")
        
        ttk.Button(dialog, text="保存", command=save_stock).grid(row=4, column=0, columnspan=2, pady=20)
 
    def show_edit_dialog(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("提示", "請(qǐng)先選擇要修改的股票!")
            return
            
        item = self.tree.item(selected[0])
        values = item['values']
        
        dialog = tk.Toplevel(self.root)
        dialog.title("修改股票")
        dialog.geometry("300x200")
        dialog.transient(self.root)
        
        ttk.Label(dialog, text="股票代碼:").grid(row=0, column=0, padx=5, pady=5)
        code_entry = ttk.Entry(dialog)
        code_entry.insert(0, values[0])
        code_entry.config(state='readonly')
        code_entry.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(dialog, text="股票名稱:").grid(row=1, column=0, padx=5, pady=5)
        name_entry = ttk.Entry(dialog)
        name_entry.insert(0, values[1])
        name_entry.grid(row=1, column=1, padx=5, pady=5)
        
        ttk.Label(dialog, text="買入價(jià):").grid(row=2, column=0, padx=5, pady=5)
        buy_entry = ttk.Entry(dialog)
        buy_entry.insert(0, values[3])
        buy_entry.grid(row=2, column=1, padx=5, pady=5)
        
        ttk.Label(dialog, text="賣出價(jià):").grid(row=3, column=0, padx=5, pady=5)
        sell_entry = ttk.Entry(dialog)
        sell_entry.insert(0, values[4])
        sell_entry.grid(row=3, column=1, padx=5, pady=5)
        
        def save_changes():
            try:
                updated_stock = {
                    "code": values[0],
                    "name": name_entry.get(),
                    "buy_price": float(buy_entry.get()),
                    "sell_price": float(sell_entry.get())
                }
                
                if not updated_stock["name"]:
                    messagebox.showerror("錯(cuò)誤", "股票名稱不能為空!")
                    return
                
                self.monitor.update_stock(updated_stock)
                dialog.destroy()
                self.add_log(f"修改股票成功:{updated_stock['name']}")
                self.update_stock_table(self.get_initial_stock_data())
            except ValueError:
                messagebox.showerror("錯(cuò)誤", "請(qǐng)輸入有效的價(jià)格!")
        
        ttk.Button(dialog, text="保存", command=save_changes).grid(row=4, column=0, columnspan=2, pady=20)
 
    def delete_stock(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("提示", "請(qǐng)先選擇要?jiǎng)h除的股票!")
            return
            
        item = self.tree.item(selected[0])
        stock_code = item['values'][0]
        stock_name = item['values'][1]
        
        if messagebox.askyesno("確認(rèn)", f"確定要?jiǎng)h除股票 {stock_name} 嗎?"):
            self.monitor.delete_stock(stock_code)
            self.add_log(f"刪除股票成功:{stock_name}")
            self.update_stock_table(self.get_initial_stock_data())
 
    def show_email_settings(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("郵件服務(wù)設(shè)置")
        dialog.geometry("400x350")  # 增加一點(diǎn)高度
        dialog.transient(self.root)
        
        frame = ttk.Frame(dialog, padding="10")
        frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 添加啟用郵件通知選項(xiàng)
        enabled_var = tk.BooleanVar(value=self.monitor.email_config.get('enabled', False))
        ttk.Checkbutton(frame, text="啟用郵件通知", variable=enabled_var).grid(row=0, column=0, columnspan=2, pady=5)
        
        # SMTP服務(wù)器???置
        ttk.Label(frame, text="SMTP服務(wù)器:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        host_entry = ttk.Entry(frame, width=30)
        host_entry.insert(0, self.monitor.email_config['host'])
        host_entry.grid(row=1, column=1, padx=5, pady=5)
        
        # 端口設(shè)置
        ttk.Label(frame, text="端口:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
        port_entry = ttk.Entry(frame, width=30)
        port_entry.insert(0, "465")  # 默認(rèn)端口
        port_entry.grid(row=2, column=1, padx=5, pady=5)
        
        # 用戶名設(shè)置
        ttk.Label(frame, text="郵箱賬號(hào):").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
        user_entry = ttk.Entry(frame, width=30)
        user_entry.insert(0, self.monitor.email_config['user'])
        user_entry.grid(row=3, column=1, padx=5, pady=5)
        
        # 密碼設(shè)置
        ttk.Label(frame, text="授權(quán)密碼:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)
        pass_entry = ttk.Entry(frame, width=30, show="*")
        pass_entry.insert(0, self.monitor.email_config['password'])
        pass_entry.grid(row=4, column=1, padx=5, pady=5)
        
        # 發(fā)件人設(shè)置
        ttk.Label(frame, text="發(fā)件人:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W)
        sender_entry = ttk.Entry(frame, width=30)
        sender_entry.insert(0, self.monitor.email_config['sender'])
        sender_entry.grid(row=5, column=1, padx=5, pady=5)
        
        # 收件人設(shè)置
        ttk.Label(frame, text="收件人:").grid(row=6, column=0, padx=5, pady=5, sticky=tk.W)
        receivers_entry = ttk.Entry(frame, width=30)
        receivers_entry.insert(0, ",".join(self.monitor.email_config['receivers']))
        receivers_entry.grid(row=6, column=1, padx=5, pady=5)
        
        # 郵件主題設(shè)置
        ttk.Label(frame, text="郵件主題:").grid(row=7, column=0, padx=5, pady=5, sticky=tk.W)
        title_entry = ttk.Entry(frame, width=30)
        title_entry.insert(0, self.monitor.email_config['title'])
        title_entry.grid(row=7, column=1, padx=5, pady=5)
        
        def test_email():
            if not enabled_var.get():
                messagebox.showwarning("提示", "請(qǐng)先啟用郵件通知!")
                return
 
            # 臨時(shí)保存當(dāng)前設(shè)置
            temp_config = {
                'enabled': enabled_var.get(),
                'host': host_entry.get(),
                'user': user_entry.get(),
                'password': pass_entry.get(),
                'sender': sender_entry.get(),
                'receivers': [r.strip() for r in receivers_entry.get().split(',')],
                'title': title_entry.get()
            }
            
            try:
                with smtplib.SMTP_SSL(temp_config['host'], 465) as smtp:
                    smtp.login(temp_config['user'], temp_config['password'])
                    message = MIMEText("這是一封測試郵件,如果您收到這封郵件,說明郵件服務(wù)設(shè)置正確。", 'plain', 'utf-8')
                    message['From'] = temp_config['sender']
                    message['To'] = ",".join(temp_config['receivers'])
                    message['Subject'] = "測試郵件"
                    smtp.sendmail(
                        temp_config['sender'],
                        temp_config['receivers'],
                        message.as_string()
                    )
                messagebox.showinfo("成功", "測試郵件發(fā)送成功!")
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"測試郵件發(fā)送失?。簕str(e)}")
        
        def save_settings():
            try:
                new_config = {
                    'enabled': enabled_var.get(),
                    'host': host_entry.get(),
                    'user': user_entry.get(),
                    'password': pass_entry.get(),
                    'sender': sender_entry.get(),
                    'receivers': [r.strip() for r in receivers_entry.get().split(',')],
                    'title': title_entry.get()
                }
                
                # 驗(yàn)證必填字段
                if new_config['enabled'] and not all([new_config['host'], new_config['user'], 
                          new_config['password'], new_config['sender'],
                          new_config['receivers'], new_config['title']]):
                    messagebox.showerror("錯(cuò)誤", "啟用郵件通知時(shí)所有字段都必須填寫!")
                    return
                
                # 更新配置
                self.monitor.update_email_config(new_config)
                messagebox.showinfo("成功", "郵件設(shè)置已保存!")
                dialog.destroy()
                
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"保存設(shè)置失?。簕str(e)}")
        
        # 按鈕框架
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=8, column=0, columnspan=2, pady=20)
        
        test_button = ttk.Button(button_frame, text="測試", command=test_email)
        test_button.grid(row=0, column=0, padx=5)
        
        save_button = ttk.Button(button_frame, text="保存", command=save_settings)
        save_button.grid(row=0, column=1, padx=5)
 
    def show_qq_settings(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("QQ機(jī)器人設(shè)置")
        dialog.geometry("400x250")
        dialog.transient(self.root)
        
        frame = ttk.Frame(dialog, padding="10")
        frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 啟用QQ通知
        enabled_var = tk.BooleanVar(value=self.monitor.qq_config.get('enabled', False))
        ttk.Checkbutton(frame, text="啟用QQ通知", variable=enabled_var).grid(row=0, column=0, columnspan=2, pady=5)
        
        # API地址
        ttk.Label(frame, text="API地址:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        api_entry = ttk.Entry(frame, width=30)
        api_entry.insert(0, self.monitor.qq_config.get('api_url', 'http://127.0.0.1:5700'))
        api_entry.grid(row=1, column=1, padx=5, pady=5)
        
        # QQ號(hào)
        ttk.Label(frame, text="接收QQ:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
        qq_entry = ttk.Entry(frame, width=30)
        qq_entry.insert(0, self.monitor.qq_config.get('qq_id', ''))
        qq_entry.grid(row=2, column=1, padx=5, pady=5)
        
        # 訪問令牌
        ttk.Label(frame, text="訪問令牌:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
        token_entry = ttk.Entry(frame, width=30, show="*")
        token_entry.insert(0, self.monitor.qq_config.get('access_token', ''))
        token_entry.grid(row=3, column=1, padx=5, pady=5)
        
        def test_qq():
            config = {
                'enabled': enabled_var.get(),
                'api_url': api_entry.get(),
                'qq_id': qq_entry.get(),
                'access_token': token_entry.get()
            }
            
            try:
                url = f"{config['api_url']}/send_private_msg"
                params = {
                    'user_id': config['qq_id'],
                    'message': "這是一條測試消息,如果您收到這消息,說明QQ機(jī)器人設(shè)置正確。"
                }
                headers = {
                    'Authorization': f"Bearer {config['access_token']}"
                }
                
                response = requests.get(url, params=params, headers=headers)
                if response.status_code == 200:
                    messagebox.showinfo("成功", "測試消息發(fā)送成功!")
                else:
                    messagebox.showerror("錯(cuò)誤", f"測試消息發(fā)送失?。簕response.text}")
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"測試消息發(fā)送失?。簕str(e)}")
        
        def save_settings():
            try:
                new_config = {
                    'enabled': enabled_var.get(),
                    'api_url': api_entry.get(),
                    'qq_id': qq_entry.get(),
                    'access_token': token_entry.get()
                }
                
                if new_config['enabled'] and not all([new_config['api_url'], new_config['qq_id']]):
                    messagebox.showerror("錯(cuò)誤", "啟用QQ通知時(shí)必須填寫API地址和接收QQ!")
                    return
                
                self.monitor.qq_config = new_config
                self.monitor.config['qq'] = new_config
                self.monitor._save_config()
                messagebox.showinfo("成功", "QQ設(shè)置已保存!")
                dialog.destroy()
                
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"保存設(shè)置失?。簕str(e)}")
        
        # 按鈕框架
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=4, column=0, columnspan=2, pady=20)
        
        test_button = ttk.Button(button_frame, text="測試", command=test_qq)
        test_button.grid(row=0, column=0, padx=5)
        
        save_button = ttk.Button(button_frame, text="保存", command=save_settings)
        save_button.grid(row=0, column=1, padx=5)
 
    def show_weixin_settings(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("企業(yè)微信設(shè)置")
        dialog.geometry("400x300")
        dialog.transient(self.root)
        
        frame = ttk.Frame(dialog, padding="10")
        frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 啟用微信通知
        enabled_var = tk.BooleanVar(value=self.monitor.weixin_config.get('enabled', False))
        ttk.Checkbutton(frame, text="啟用企業(yè)微信通知", variable=enabled_var).grid(row=0, column=0, columnspan=2, pady=5)
        
        # 企業(yè)ID
        ttk.Label(frame, text="企業(yè)ID:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        corp_id_entry = ttk.Entry(frame, width=30)
        corp_id_entry.insert(0, self.monitor.weixin_config.get('corp_id', ''))
        corp_id_entry.grid(row=1, column=1, padx=5, pady=5)
        
        # 應(yīng)用ID
        ttk.Label(frame, text="應(yīng)用ID:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
        agent_id_entry = ttk.Entry(frame, width=30)
        agent_id_entry.insert(0, self.monitor.weixin_config.get('agent_id', ''))
        agent_id_entry.grid(row=2, column=1, padx=5, pady=5)
        
        # 應(yīng)用密鑰
        ttk.Label(frame, text="應(yīng)用密鑰:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
        secret_entry = ttk.Entry(frame, width=30, show="*")
        secret_entry.insert(0, self.monitor.weixin_config.get('corp_secret', ''))
        secret_entry.grid(row=3, column=1, padx=5, pady=5)
        
        # 接收用戶
        ttk.Label(frame, text="接收用戶:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)
        to_user_entry = ttk.Entry(frame, width=30)
        to_user_entry.insert(0, self.monitor.weixin_config.get('to_user', '@all'))
        to_user_entry.grid(row=4, column=1, padx=5, pady=5)
        
        def test_weixin():
            if not enabled_var.get():
                messagebox.showwarning("提示", "請(qǐng)先啟用企業(yè)微信通知!")
                return
            
            config = {
                'enabled': enabled_var.get(),
                'corp_id': corp_id_entry.get(),
                'agent_id': agent_id_entry.get(),
                'corp_secret': secret_entry.get(),
                'to_user': to_user_entry.get()
            }
            
            # 創(chuàng)建臨時(shí)的 StockMonitor 實(shí)例來測試
            temp_monitor = StockMonitor()
            temp_monitor.weixin_config = config
            temp_monitor.set_log_callback(self.add_log)
            
            temp_monitor.send_weixin_message("這是一條測試消息,如果您收到這條消息,說明企業(yè)微信設(shè)置正確。")
        
        def save_settings():
            try:
                new_config = {
                    'enabled': enabled_var.get(),
                    'corp_id': corp_id_entry.get(),
                    'agent_id': agent_id_entry.get(),
                    'corp_secret': secret_entry.get(),
                    'to_user': to_user_entry.get()
                }
                
                if new_config['enabled'] and not all([new_config['corp_id'], 
                                                    new_config['agent_id'], 
                                                    new_config['corp_secret']]):
                    messagebox.showerror("錯(cuò)誤", "啟用企業(yè)微信通知時(shí)必須填寫企業(yè)ID、應(yīng)用ID和應(yīng)用密鑰!")
                    return
                
                self.monitor.weixin_config = new_config
                self.monitor.config['weixin'] = new_config
                self.monitor._save_config()
                messagebox.showinfo("成功", "企業(yè)微信設(shè)置已保存!")
                dialog.destroy()
                
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"保存設(shè)置失?。簕str(e)}")
        
        # 按鈕框架
        button_frame = ttk.Frame(frame)
        button_frame.grid(row=5, column=0, columnspan=2, pady=20)
        
        test_button = ttk.Button(button_frame, text="測試", command=test_weixin)
        test_button.grid(row=0, column=0, padx=5)
        
        save_button = ttk.Button(button_frame, text="保存", command=save_settings)
        save_button.grid(row=0, column=1, padx=5)
 
    def add_log(self, message: str):
        """添加日志到界面"""
        self.log_text.insert(tk.END, message + '\n')
        self.log_text.see(tk.END)  # 滾動(dòng)到最新的日志
 
    def get_initial_stock_data(self):
        """獲取初始股票數(shù)據(jù)用于顯示"""
        result = []
        for stock in self.monitor.stocks:
            result.append((
                stock['code'],
                stock['name'],
                '等待更新',  # 初始顯示時(shí)還沒有實(shí)時(shí)價(jià)格
                stock['buy_price'],
                stock['sell_price'],
                '等待監(jiān)控'   # 初始狀態(tài)
            ))
        return result
 
    def create_float_window(self):
        """創(chuàng)建懸浮窗口"""
        self.float_window = tk.Toplevel()
        self.float_window.title("股票監(jiān)控")
        
        # 設(shè)置窗口屬性
        self.float_window.attributes('-topmost', True)  # 始終置頂
        self.float_window.overrideredirect(True)  # 無邊框
        self.float_window.attributes('-alpha', 0.9)  # 設(shè)置透明度
        
        # 獲取屏幕尺寸
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        
        # 設(shè)置窗口位置(右下角)
        window_width = 200
        window_height = 300
        x = screen_width - window_width - 10
        y = screen_height - window_height - 50
        self.float_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
        
        # 創(chuàng)建標(biāo)題欄
        title_frame = ttk.Frame(self.float_window)
        title_frame.pack(fill=tk.X, padx=2, pady=2)
        
        ttk.Label(title_frame, text="股票監(jiān)控").pack(side=tk.LEFT, padx=5)
        
        # 添加關(guān)閉和還原按鈕
        ttk.Button(title_frame, text="□", width=3, command=self.restore_window).pack(side=tk.RIGHT, padx=1)
        ttk.Button(title_frame, text="×", width=3, command=self.on_closing).pack(side=tk.RIGHT, padx=1)
        
        # 添加拖動(dòng)功能
        title_frame.bind("<Button-1>", self.start_move)
        title_frame.bind("<B1-Motion>", self.on_move)
        
        # 創(chuàng)建股票信息顯示區(qū)域
        self.float_tree = ttk.Treeview(self.float_window, columns=('名稱', '價(jià)格', '狀態(tài)'), show='headings', height=10)
        self.float_tree.heading('名稱', text='名稱')
        self.float_tree.heading('價(jià)格', text='價(jià)格')
        self.float_tree.heading('狀態(tài)', text='狀態(tài)')
        
        # 設(shè)置列寬
        self.float_tree.column('名稱', width=60)
        self.float_tree.column('價(jià)格', width=60)
        self.float_tree.column('狀態(tài)', width=60)
        
        self.float_tree.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
 
    def update_float_window(self, stock_data):
        """更新懸浮窗口的股票信息"""
        if self.float_window and self.is_minimized:
            for item in self.float_tree.get_children():
                self.float_tree.delete(item)
            
            for stock in stock_data:
                self.float_tree.insert('', tk.END, values=(stock[1], stock[2], stock[5]))
 
    def start_move(self, event):
        """開始拖動(dòng)窗口"""
        self.x = event.x
        self.y = event.y
 
    def on_move(self, event):
        """拖動(dòng)窗口"""
        deltax = event.x - self.x
        deltay = event.y - self.y
        x = self.float_window.winfo_x() + deltax
        y = self.float_window.winfo_y() + deltay
        self.float_window.geometry(f"+{x}+{y}")
 
    def on_minimize(self, event):
        """最小化主窗口時(shí)顯示懸浮窗"""
        if not self.float_window:
            self.create_float_window()
        self.is_minimized = True
        self.float_window.deiconify()
 
    def restore_window(self):
        """還原主窗口"""
        self.root.deiconify()
        self.is_minimized = False
        if self.float_window:
            self.float_window.withdraw()
 
    def on_closing(self):
        """關(guān)閉程序"""
        if messagebox.askokcancel("退出", "確定要退出程序嗎?"):
            if self.float_window:
                self.float_window.destroy()
            self.root.destroy()
 
class StockMonitor:
    def __init__(self, config_file: str = 'stock_config.json'):
        self.config = self._load_config(config_file)
        self.email_config = self.config['email']
        self.qq_config = self.config.get('qq', {'enabled': False})  # 添加QQ配置
        self.weixin_config = self.config.get('weixin', {'enabled': False})  # 添加微信配置
        self.stocks = self.config['stocks']
        self.check_interval = self.config['check_interval']
        self.log_callback = None  # 添加日志回調(diào)函數(shù)
 
    def set_log_callback(self, callback):
        """設(shè)置日志回調(diào)函數(shù)"""
        self.log_callback = callback
 
    def log(self, message: str, level: str = 'info'):
        """統(tǒng)一的日志處理方法"""
        if level == 'error':
            logging.error(message)
        else:
            logging.info(message)
            
        if self.log_callback:
            self.log_callback(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")
 
    def _load_config(self, config_file: str) -> Dict:
        try:
            with open(config_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        except FileNotFoundError:
            logging.error(f"配置文件 {config_file} 不存在")
            sys.exit(1)
        except json.JSONDecodeError:
            logging.error(f"配置文件 {config_file} 格式錯(cuò)誤")
            sys.exit(1)
 
    def send_email(self, content: str) -> None:
        """發(fā)送郵件"""
        if not self.email_config.get('enabled', False):  # 檢查是否啟用
            return
 
        message = MIMEText(content, 'plain', 'utf-8')
        message['From'] = self.email_config['sender']
        message['To'] = ",".join(self.email_config['receivers'])
        message['Subject'] = self.email_config['title']
 
        try:
            with smtplib.SMTP_SSL(self.email_config['host'], 465) as smtp:
                smtp.login(self.email_config['user'], self.email_config['password'])
                smtp.sendmail(
                    self.email_config['sender'],
                    self.email_config['receivers'],
                    message.as_string()
                )
            self.log("郵件發(fā)送成功!")
        except Exception as e:
            self.log(f"郵件發(fā)送失敗: {str(e)}", 'error')
 
    def get_stock_status(self, current_price: float, buy_price: float, sell_price: float) -> str:
        if current_price <= buy_price:
            return "建議買入"
        elif current_price >= sell_price:
            return "建議賣出"
        return "觀察中"
 
    def send_qq_message(self, message: str) -> None:
        """發(fā)送QQ消息"""
        if not self.qq_config.get('enabled', False):
            return
 
        try:
            url = f"{self.qq_config['api_url']}/send_private_msg"
            params = {
                'user_id': self.qq_config['qq_id'],
                'message': message
            }
            headers = {
                'Authorization': f"Bearer {self.qq_config.get('access_token', '')}"
            }
            
            response = requests.get(url, params=params, headers=headers)
            if response.status_code == 200:
                self.log("QQ消息發(fā)送成功!")
            else:
                self.log(f"QQ消息發(fā)送失敗: {response.text}", 'error')
        except Exception as e:
            self.log(f"QQ消息發(fā)送失敗: {str(e)}", 'error')
 
    def send_weixin_message(self, message: str) -> None:
        """發(fā)送企業(yè)微信消息"""
        if not self.weixin_config.get('enabled', False):
            return
 
        try:
            # 獲取訪問令牌
            token_url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken"
            token_params = {
                'corpid': self.weixin_config['corp_id'],
                'corpsecret': self.weixin_config['corp_secret']
            }
            
            token_response = requests.get(token_url, params=token_params)
            token_data = token_response.json()
            
            if token_data['errcode'] != 0:
                self.log(f"獲取微信訪問令牌失敗: {token_data['errmsg']}", 'error')
                return
            
            access_token = token_data['access_token']
            
            # 發(fā)送消息
            send_url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}"
            send_data = {
                'touser': self.weixin_config['to_user'],
                'msgtype': 'text',
                'agentid': self.weixin_config['agent_id'],
                'text': {
                    'content': message
                }
            }
            
            response = requests.post(send_url, json=send_data)
            result = response.json()
            
            if result['errcode'] == 0:
                self.log("微信消息發(fā)送成功!")
            else:
                self.log(f"微信消息發(fā)送失敗: {result['errmsg']}", 'error')
        except Exception as e:
            self.log(f"微信消息發(fā)送失敗: {str(e)}", 'error')
 
    def get_stock_data(self) -> List:
        try:
            df = ef.stock.get_realtime_quotes()
            stock_list = df.values.tolist()
            result = []
            
            for stock_info in self.stocks:
                stock_data = next(
                    (item for item in stock_list if item[0] == stock_info['code']),
                    None
                )
                
                if stock_data:
                    current_price = float(stock_data[3])
                    status = self.get_stock_status(
                        current_price,
                        stock_info['buy_price'],
                        stock_info['sell_price']
                    )
                    
                    result.append((
                        stock_info['code'],
                        stock_info['name'],
                        current_price,
                        stock_info['buy_price'],
                        stock_info['sell_price'],
                        status
                    ))
                    
                    # 檢查是否需要發(fā)送提醒
                    if status in ["建議買入", "建議賣出"]:
                        content = f'當(dāng)前{stock_info["name"]}的價(jià)格是: {current_price}, {status}'
                        self.send_email(content)
                        self.send_qq_message(content)
                        self.send_weixin_message(content)  # 添加微信消息提醒
                        self.log(content)
                    else:
                        self.log(f'當(dāng)前{stock_info["name"]}的價(jià)格是: {current_price} 耐心觀察中...')
                else:
                    self.log(f"未找到股票 {stock_info['code']} 的數(shù)據(jù)", 'error')
                    
            return result
        except Exception as e:
            self.log(f"獲取股票數(shù)據(jù)失敗: {str(e)}", 'error')
            return []
 
    def add_stock(self, stock: Dict) -> None:
        self.stocks.append(stock)
        self._save_config()
 
    def update_stock(self, updated_stock: Dict) -> None:
        for i, stock in enumerate(self.stocks):
            if stock['code'] == updated_stock['code']:
                self.stocks[i] = updated_stock
                break
        self._save_config()
 
    def delete_stock(self, stock_code: str) -> None:
        self.stocks = [s for s in self.stocks if s['code'] != stock_code]
        self._save_config()
 
    def _save_config(self) -> None:
        try:
            self.config['stocks'] = self.stocks
            with open('stock_config.json', 'w', encoding='utf-8') as f:
                json.dump(self.config, f, indent=4, ensure_ascii=False)
        except Exception as e:
            logging.error(f"保存配置文件失敗: {str(e)}")
 
    def update_email_config(self, new_config: Dict) -> None:
        self.email_config = new_config
        self.config['email'] = new_config
        self._save_config()
 
def main():
    root = tk.Tk()
    app = StockMonitorGUI(root)
    root.mainloop()
 
if __name__ == "__main__":
    main()

上配置文件:stock_config.json

{
    "email": {
        "enabled": false,
        "host": "smtp.163.com",
        "user": "i238@163.com",
        "password": "JIMRV",
        "sender": "i25@163.com",
        "receivers": [
            "123@qq.com"
        ],
        "title": "股票監(jiān)控買賣提示"
    },
    "qq": {
        "enabled": false,
        "api_url": "http://127.0.0.1:5700",
        "qq_id": "123456789",
        "access_token": "your_access_token"
    },
    "weixin": {
        "enabled": false,
        "corp_id": "your_corp_id",
        "agent_id": "your_agent_id",
        "corp_secret": "your_corp_secret",
        "to_user": "@all"
    },
    "stocks": [
        {
            "code": "000776",
            "name": "廣發(fā)證券",
            "buy_price": 11.3,
            "sell_price": 12.6
        },
        {
            "code": "002945",
            "name": "華林證券",
            "buy_price": 12.0,
            "sell_price": 16.0
        }
    ],
    "check_interval": 60
}

上文檔說明--- 股票監(jiān)控系統(tǒng)使用說明

1. 系統(tǒng)簡介

這是一個(gè)基于Python開發(fā)的股票監(jiān)控系統(tǒng),可以實(shí)時(shí)監(jiān)控多支股票的價(jià)格變動(dòng),并通過多種方式(郵件、QQ、企業(yè)微信)發(fā)送買賣提醒。

主要功能:

  • 實(shí)時(shí)監(jiān)控多支股票價(jià)格
  • 自定義買入賣出價(jià)格
  • 多種提醒方式(郵件/QQ/企業(yè)微信)
  • 懸浮窗口顯示實(shí)時(shí)行情
  • 完整的日志記錄

2. 系統(tǒng)要求

Python 3.6 或更高版本

需要安裝的依賴包:

pip install efinance requests

3. 配置文件說明

配置文件為 stock_config.json,包含以下主要配置項(xiàng):

son
{
"email": {
"enabled": false, // 是否啟用郵件通知
"host": "smtp.163.com", // SMTP服務(wù)器
"user": "xxx@163.com", // 郵箱賬號(hào)
"password": "xxx", // 郵箱授權(quán)碼
"sender": "xxx@163.com", // 發(fā)件人
"receivers": ["xxx@qq.com"],// 收件人列表
"title": "股票監(jiān)控提示" // 郵件主題
},
"qq": {
"enabled": false, // 是否啟用QQ通知
"api_url": "http://127.0.0.1:5700", // go-cqhttp服務(wù)地址
"qq_id": "123456789", // 接收消息的QQ號(hào)
"access_token": "xxx" // 訪問令牌
},
"weixin": {
"enabled": false, // 是否啟用企業(yè)微信通知
"corp_id": "xxx", // 企業(yè)ID
"agent_id": "xxx", // 應(yīng)用ID
"corp_secret": "xxx", // 應(yīng)用密鑰
"to_user": "@all" // 接收用戶
},
"stocks": [ // 監(jiān)控的股票列表
{
"code": "000776", // 股票代碼
"name": "廣發(fā)證券", // 股票名稱
"buy_price": 11.3, // 買入價(jià)
"sell_price": 12.6 // 賣出價(jià)
}
],
"check_interval": 60 // 檢查間隔(秒)
}

4. 使用說明

4.1 啟動(dòng)程序

運(yùn)行 股票監(jiān)聽器.py 文件啟動(dòng)程序。

4.2 股票管理

  • 添加股票:點(diǎn)擊"添加股票"按鈕,輸入股票代碼、名稱、買入價(jià)和賣出價(jià)
  • 修改股票:選中要修改的股票,點(diǎn)擊"修改股票"按鈕
  • 刪除股票:選中要?jiǎng)h除的股票,點(diǎn)擊"刪除股票"按鈕

4.3 通知設(shè)置

郵件設(shè)置:

  • 點(diǎn)擊"郵件設(shè)置"按鈕
  • 勾選"啟用郵件通知"
  • 填寫SMTP服務(wù)器、郵箱賬號(hào)等信息
  • 可點(diǎn)擊"測試"按鈕測試設(shè)置

QQ設(shè)置:

  • 需要先配置并運(yùn)行 go-cqhttp
  • 點(diǎn)擊"QQ設(shè)置"按鈕
  • 勾選"啟用QQ通知"
  • 填寫API地址和接收QQ號(hào)
  • 可點(diǎn)擊"測試"按鈕測試設(shè)置

企業(yè)微信設(shè)置:

  • 需要有企業(yè)微信管理員權(quán)限
  • 點(diǎn)擊"微信設(shè)置"按鈕
  • 勾選"啟用企業(yè)微信通知"
  • 填寫企業(yè)ID、應(yīng)用ID等信息
  • 可點(diǎn)擊"測試"按鈕測試設(shè)置

4.4 監(jiān)控操作

開始監(jiān)控:點(diǎn)擊"開始監(jiān)控"按鈕

停止監(jiān)控:點(diǎn)擊"停止監(jiān)控"按鈕

最小化:

  • 程序最小化后會(huì)在屏幕右下角顯示懸浮窗
  • 懸浮窗可以拖動(dòng)位置
  • 點(diǎn)擊"□"按鈕可以還原主窗口
  • 點(diǎn)擊"×"按鈕可以關(guān)閉程序

5. 提醒規(guī)則

當(dāng)股票價(jià)格低于或等于設(shè)定的買入價(jià)時(shí),發(fā)送"建議買入"提醒

當(dāng)股票價(jià)格高于或等于設(shè)定的賣出價(jià)時(shí),發(fā)送"建議賣出"提醒

提醒會(huì)同時(shí)通過所有已啟用的通知方式發(fā)送

6. 注意事項(xiàng)

郵件通知需要使用郵箱的授權(quán)碼,而不是登錄密碼

QQ通知需要正確配置并運(yùn)行 go-cqhttp

企業(yè)微信通知需要管理員在企業(yè)微信后臺(tái)創(chuàng)建應(yīng)用

所有密碼和密鑰信息都保存在本地配置文件中,請(qǐng)注意信息安全

程序會(huì)自動(dòng)保存所有設(shè)置到配置文件

7. 常見問題

郵件發(fā)送失?。?/p>

  • 檢查郵箱賬號(hào)和授權(quán)碼是否正確
  • 確認(rèn)SMTP服務(wù)器地址是否正確

QQ消息發(fā)送失?。?/p>

  • 確認(rèn)go-cqhttp是否正常運(yùn)行
  • 檢查API地址是否可以訪問

企業(yè)微信消息發(fā)送失?。?/p>

  • 確認(rèn)企業(yè)ID和應(yīng)用密鑰是否正確
  • 檢查應(yīng)用是否有發(fā)送消息權(quán)限

股票數(shù)據(jù)獲取失?。?/p>

  • 檢查網(wǎng)絡(luò)連接
  • 確認(rèn)股票代碼是否正確

到此這篇關(guān)于使用Python簡單編寫一個(gè)股票監(jiān)控系統(tǒng)的文章就介紹到這了,更多相關(guān)Python股票監(jiān)控系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • numpy 產(chǎn)生隨機(jī)數(shù)的幾種方法

    numpy 產(chǎn)生隨機(jī)數(shù)的幾種方法

    本文主要介紹了numpy 產(chǎn)生隨機(jī)數(shù)的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Python使用kombu連接信息中包含#號(hào)問題排查方式

    Python使用kombu連接信息中包含#號(hào)問題排查方式

    文章描述了在部署Python項(xiàng)目到生產(chǎn)環(huán)境時(shí)遇到的一個(gè)錯(cuò)誤,即端口號(hào)無法正確轉(zhuǎn)換為整數(shù)值,該錯(cuò)誤在測試環(huán)境和本地調(diào)試中沒有出現(xiàn),但在生產(chǎn)環(huán)境中才出現(xiàn),通過分析錯(cuò)誤信息和代碼,作者發(fā)現(xiàn)問題出在URL解析過程中,特別是在處理包含特殊字符(如#號(hào))的URL時(shí)
    2024-12-12
  • python 獲取本機(jī)ip地址的兩個(gè)方法

    python 獲取本機(jī)ip地址的兩個(gè)方法

    用python 獲取本機(jī)ip地址的多種方法,需要的朋友可以參考下
    2013-02-02
  • python?中Mixin混入類的使用方法詳解

    python?中Mixin混入類的使用方法詳解

    這篇文章主要介紹了python?中Mixin混入類的使用方法詳解,Mixin?混入也可以說是編程模式,并不是什么新的語法,用好混入類可以使自己的代碼結(jié)構(gòu)清晰,功能明了,所以以后在設(shè)計(jì)類時(shí)要多考慮使用Mixin混入類的實(shí)現(xiàn)方式
    2022-07-07
  • python爬蟲入門教程之點(diǎn)點(diǎn)美女圖片爬蟲代碼分享

    python爬蟲入門教程之點(diǎn)點(diǎn)美女圖片爬蟲代碼分享

    這篇文章主要介紹了python爬蟲入門教程之點(diǎn)點(diǎn)美女圖片爬蟲代碼分享,本文以采集抓取點(diǎn)點(diǎn)網(wǎng)美女圖片為例,需要的朋友可以參考下
    2014-09-09
  • Python?pip更換鏡像源問題及解決

    Python?pip更換鏡像源問題及解決

    文章介紹了Python的pip包管理工具更換鏡像源的方法,包括清華大學(xué)、阿里云、中國科技大學(xué)和豆瓣等常用鏡像源,文章詳細(xì)說明了臨時(shí)使用鏡像源和永久配置鏡像源的方法,并附上了針對(duì)Conda的鏡像配置示例,最后,作者推薦使用國內(nèi)鏡像源以提高下載速度
    2025-02-02
  • 基于Python中isfile函數(shù)和isdir函數(shù)使用詳解

    基于Python中isfile函數(shù)和isdir函數(shù)使用詳解

    今天小編就為大家分享一篇基于Python中isfile函數(shù)和isdir函數(shù)使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • Python中捕獲鍵盤的方式詳解

    Python中捕獲鍵盤的方式詳解

    這篇文章主要介紹了Python中捕獲鍵盤的方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Pandas排序和分組排名(sort和rank)的實(shí)現(xiàn)

    Pandas排序和分組排名(sort和rank)的實(shí)現(xiàn)

    Pandas是Python中廣泛使用的數(shù)據(jù)處理庫,提供了豐富的功能來處理和分析數(shù)據(jù),本文主要介紹了Pandas排序和分組排名(sort和rank)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-07-07
  • Python set常用操作函數(shù)集錦

    Python set常用操作函數(shù)集錦

    set是一個(gè)無序且不重復(fù)的元素集合。這篇文章主要介紹了Python set常用操作函數(shù)集錦,需要的朋友可以參考下
    2017-11-11

最新評(píng)論