diff --git a/智能家居系统/.idea/.gitignore b/智能家居系统/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/智能家居系统/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/智能家居系统/.idea/dataSources.xml b/智能家居系统/.idea/dataSources.xml new file mode 100644 index 0000000..9540313 --- /dev/null +++ b/智能家居系统/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/智能家居系统 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/智能家居系统/.idea/inspectionProfiles/profiles_settings.xml b/智能家居系统/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/智能家居系统/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/智能家居系统/.idea/misc.xml b/智能家居系统/.idea/misc.xml new file mode 100644 index 0000000..b377d79 --- /dev/null +++ b/智能家居系统/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/智能家居系统/.idea/modules.xml b/智能家居系统/.idea/modules.xml new file mode 100644 index 0000000..05948d0 --- /dev/null +++ b/智能家居系统/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/智能家居系统/.idea/智能家居系统.iml b/智能家居系统/.idea/智能家居系统.iml new file mode 100644 index 0000000..2c80e12 --- /dev/null +++ b/智能家居系统/.idea/智能家居系统.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/智能家居系统/1.py b/智能家居系统/1.py new file mode 100644 index 0000000..d2aa9ae --- /dev/null +++ b/智能家居系统/1.py @@ -0,0 +1,182 @@ +import tkinter as tk +import pymysql +from tkinter import messagebox + + +class JiaJuPage: + def __init__(self, master): + self.root = master + self.page = tk.Frame(self.root) + self.page.pack() + self.root.geometry('600x400') + self.create_page() + + def create_page(self): + menubar = tk.Menu(self.root) + + # 数据库连接配置 + self.conn = pymysql.connect(host='localhost', user='root', password='LH20021212', db='智能家居系统', + charset='utf8mb4') + self.cursor = self.conn.cursor() + + menubar.add_command(label='录入家居', command=self.add_jiaju) + menubar.add_command(label='查询家居', command=self.query_jiaju) + menubar.add_command(label='删除家居', command=self.delete_jiaju) + menubar.add_command(label='修改家居', command=self.update_jiaju) + menubar.add_separator() # 添加分隔线使菜单更加清晰 + menubar.add_command(label='退出', command=self.exit_app) # 新增退出功能 + + self.root.config(menu=menubar) + + def exit_app(self): + """退出应用程序的函数""" + if messagebox.askyesno("退出确认", "确定要退出吗?"): + self.close_conn() # 确保关闭数据库连接 + self.root.destroy() # 关闭主窗口 + + def add_jiaju(self): + def submit(): + # 获取用户输入 + name = entry_name.get() + color = entry_color.get() + brand = entry_brand.get() + price = entry_price.get() + production_date = entry_production_date.get() + # 插入数据库操作 + sql = f"INSERT INTO jia_ju(name, color, brand, price, production_date) VALUES ('{name}', '{color}', '{brand}', {price}, '{production_date}')" + try: + self.cursor.execute(sql) + self.conn.commit() + messagebox.showinfo("成功", "家居信息录入成功!") + except Exception as e: + messagebox.showerror("错误", f"录入失败: {e}") + finally: + add_window.destroy() # 关闭对话框 + + add_window = tk.Toplevel(self.root) + add_window.title("录入家居信息") + tk.Label(add_window, text="名称:").pack() + entry_name = tk.Entry(add_window) + entry_name.pack() + tk.Label(add_window, text="颜色:").pack() + entry_color = tk.Entry(add_window) + entry_color.pack() + tk.Label(add_window, text="品牌:").pack() + entry_brand = tk.Entry(add_window) + entry_brand.pack() + tk.Label(add_window, text="价格:").pack() + entry_price = tk.Entry(add_window) + entry_price.pack() + tk.Label(add_window, text="生产日期:").pack() + entry_production_date = tk.Entry(add_window) + entry_production_date.pack() + tk.Button(add_window, text="提交", command=submit).pack() + + def query_jiaju(self): + self.cursor.execute("SELECT * FROM jia_ju") + results = self.cursor.fetchall() + if results: + result_text = "\n".join([str(row) for row in results]) + messagebox.showinfo("查询结果", result_text) + else: + messagebox.showinfo("查询结果", "无数据") + + def delete_jiaju(self): + def confirm_delete(): + item_id = entry_id.get() + # 执行删除操作 + sql = f"DELETE FROM jia_ju WHERE id={item_id}" + try: + self.cursor.execute(sql) + self.conn.commit() + messagebox.showinfo("成功", "家居信息已删除!") + except Exception as e: + messagebox.showerror("错误", f"删除失败: {e}") + finally: + delete_window.destroy() + + delete_window = tk.Toplevel(self.root) + delete_window.title("删除家居") + tk.Label(delete_window, text="请输入家居ID:").pack() + entry_id = tk.Entry(delete_window) + entry_id.pack() + tk.Button(delete_window, text="确认删除", command=confirm_delete).pack() + + def update_jiaju(self): + def fetch_jiaju_info(): + item_id = entry_id.get() + sql = f"SELECT * FROM jia_ju WHERE id={item_id}" + try: + self.cursor.execute(sql) + result = self.cursor.fetchone() + if result: + entry_name.delete(0, tk.END) + entry_name.insert(tk.END, result[1]) + entry_color.delete(0, tk.END) + entry_color.insert(tk.END, result[2]) + entry_brand.delete(0, tk.END) + entry_brand.insert(tk.END, result[3]) + entry_price.delete(0, tk.END) + entry_price.insert(tk.END, str(result[4])) + entry_production_date.delete(0, tk.END) + entry_production_date.insert(tk.END, result[5]) + else: + messagebox.showinfo("查询结果", "未找到该家居信息") + except Exception as e: + messagebox.showerror("错误", f"查询失败: {e}") + + def submit_update(): + item_id = entry_id.get() + name = entry_name.get() + color = entry_color.get() + brand = entry_brand.get() + price = entry_price.get() + production_date = entry_production_date.get() + sql = f"UPDATE jia_ju SET name='{name}', color='{color}', brand='{brand}', price={price}, production_date='{production_date}' WHERE id={item_id}" + try: + self.cursor.execute(sql) + self.conn.commit() + messagebox.showinfo("成功", "家居信息已更新!") + except Exception as e: + messagebox.showerror("错误", f"更新失败: {e}") + finally: + update_window.destroy() + + update_window = tk.Toplevel(self.root) + update_window.title("修改家居信息") + tk.Label(update_window, text="请输入家居ID:").pack() + entry_id = tk.Entry(update_window) + entry_id.pack() + tk.Button(update_window, text="查询", command=fetch_jiaju_info).pack() + + tk.Label(update_window, text="名称:").pack() + entry_name = tk.Entry(update_window) + entry_name.pack() + tk.Label(update_window, text="颜色:").pack() + entry_color = tk.Entry(update_window) + entry_color.pack() + tk.Label(update_window, text="品牌:").pack() + entry_brand = tk.Entry(update_window) + entry_brand.pack() + tk.Label(update_window, text="价格:").pack() + entry_price = tk.Entry(update_window) + entry_price.pack() + tk.Label(update_window, text="生产日期:").pack() + entry_production_date = tk.Entry(update_window) + entry_production_date.pack() + + tk.Button(update_window, text="提交修改", command=submit_update).pack() + + def close_conn(self): + # 关闭数据库连接 + self.cursor.close() + self.conn.close() + + +if __name__ == '__main__': + root = tk.Tk() + app = JiaJuPage(root) + + # 确保在程序结束时关闭数据库连接 + root.protocol("WM_DELETE_WINDOW", app.close_conn) + root.mainloop() \ No newline at end of file diff --git a/智能家居系统/2.py b/智能家居系统/2.py new file mode 100644 index 0000000..6f5404f --- /dev/null +++ b/智能家居系统/2.py @@ -0,0 +1,7 @@ +from 测试 import db, Jia + +s1 = Jia(编号=1, 名称='电脑', 品牌='联想', 颜色='黑色', 价格='5000', 生产日期='2020-01-01') +s2 = Jia(编号=2, 名称='冰箱', 品牌='联想', 颜色='白色', 价格='6000', 生产日期='2020-01-02') +s3 = Jia(编号=2, 名称='电视机', 品牌='联想', 颜色='黑色', 价格='3000', 生产日期='2020-01-02') +db.session.add(s) +db.session.commit() \ No newline at end of file diff --git a/智能家居系统/__pycache__/主页面.cpython-311.pyc b/智能家居系统/__pycache__/主页面.cpython-311.pyc new file mode 100644 index 0000000..7fc0582 Binary files /dev/null and b/智能家居系统/__pycache__/主页面.cpython-311.pyc differ diff --git a/智能家居系统/__pycache__/家居.cpython-311.pyc b/智能家居系统/__pycache__/家居.cpython-311.pyc new file mode 100644 index 0000000..6a8c86f Binary files /dev/null and b/智能家居系统/__pycache__/家居.cpython-311.pyc differ diff --git a/智能家居系统/__pycache__/家居信息.cpython-311.pyc b/智能家居系统/__pycache__/家居信息.cpython-311.pyc new file mode 100644 index 0000000..fa57ffc Binary files /dev/null and b/智能家居系统/__pycache__/家居信息.cpython-311.pyc differ diff --git a/智能家居系统/__pycache__/测试.cpython-311.pyc b/智能家居系统/__pycache__/测试.cpython-311.pyc new file mode 100644 index 0000000..33e178d Binary files /dev/null and b/智能家居系统/__pycache__/测试.cpython-311.pyc differ diff --git a/智能家居系统/user_data.json b/智能家居系统/user_data.json new file mode 100644 index 0000000..edf164e --- /dev/null +++ b/智能家居系统/user_data.json @@ -0,0 +1 @@ +{"lh": "123"} \ No newline at end of file diff --git a/智能家居系统/user_info.txt b/智能家居系统/user_info.txt new file mode 100644 index 0000000..e69de29 diff --git a/智能家居系统/主页面.py b/智能家居系统/主页面.py new file mode 100644 index 0000000..d59f0c1 --- /dev/null +++ b/智能家居系统/主页面.py @@ -0,0 +1,26 @@ +import tkinter as tk +from tkinter import messagebox + +class MainPage: + def __init__(self, master): + self.root = master + self.page = tk.Frame(self.root) + self.page.pack() + self.root.geometry('600x400') + self.create_page() + + def create_page(self): + # 创建一个标签用于展示简单的家居信息标题 + self.home_info_title = tk.Label(self.page, text="主页面区", font=("Helvetica", 16)) + self.home_info_title.pack(pady=20) + + # 创建一个按钮,点击后显示更详细的家居信息 + self.view_details_button = tk.Button(self.page, text="家居信息", command=self.view_home_details) + self.view_details_button.pack(pady=10) + + + +if __name__ == '__main__': + root = tk.Tk() + app = MainPage(root) + root.mainloop() \ No newline at end of file diff --git a/智能家居系统/增删改查窗口.html b/智能家居系统/增删改查窗口.html new file mode 100644 index 0000000..d17cd3c --- /dev/null +++ b/智能家居系统/增删改查窗口.html @@ -0,0 +1,32 @@ + + + + 家居管理 + + + + +
+ + +
+ + + + diff --git a/智能家居系统/家居信息.py b/智能家居系统/家居信息.py new file mode 100644 index 0000000..d2aa9ae --- /dev/null +++ b/智能家居系统/家居信息.py @@ -0,0 +1,182 @@ +import tkinter as tk +import pymysql +from tkinter import messagebox + + +class JiaJuPage: + def __init__(self, master): + self.root = master + self.page = tk.Frame(self.root) + self.page.pack() + self.root.geometry('600x400') + self.create_page() + + def create_page(self): + menubar = tk.Menu(self.root) + + # 数据库连接配置 + self.conn = pymysql.connect(host='localhost', user='root', password='LH20021212', db='智能家居系统', + charset='utf8mb4') + self.cursor = self.conn.cursor() + + menubar.add_command(label='录入家居', command=self.add_jiaju) + menubar.add_command(label='查询家居', command=self.query_jiaju) + menubar.add_command(label='删除家居', command=self.delete_jiaju) + menubar.add_command(label='修改家居', command=self.update_jiaju) + menubar.add_separator() # 添加分隔线使菜单更加清晰 + menubar.add_command(label='退出', command=self.exit_app) # 新增退出功能 + + self.root.config(menu=menubar) + + def exit_app(self): + """退出应用程序的函数""" + if messagebox.askyesno("退出确认", "确定要退出吗?"): + self.close_conn() # 确保关闭数据库连接 + self.root.destroy() # 关闭主窗口 + + def add_jiaju(self): + def submit(): + # 获取用户输入 + name = entry_name.get() + color = entry_color.get() + brand = entry_brand.get() + price = entry_price.get() + production_date = entry_production_date.get() + # 插入数据库操作 + sql = f"INSERT INTO jia_ju(name, color, brand, price, production_date) VALUES ('{name}', '{color}', '{brand}', {price}, '{production_date}')" + try: + self.cursor.execute(sql) + self.conn.commit() + messagebox.showinfo("成功", "家居信息录入成功!") + except Exception as e: + messagebox.showerror("错误", f"录入失败: {e}") + finally: + add_window.destroy() # 关闭对话框 + + add_window = tk.Toplevel(self.root) + add_window.title("录入家居信息") + tk.Label(add_window, text="名称:").pack() + entry_name = tk.Entry(add_window) + entry_name.pack() + tk.Label(add_window, text="颜色:").pack() + entry_color = tk.Entry(add_window) + entry_color.pack() + tk.Label(add_window, text="品牌:").pack() + entry_brand = tk.Entry(add_window) + entry_brand.pack() + tk.Label(add_window, text="价格:").pack() + entry_price = tk.Entry(add_window) + entry_price.pack() + tk.Label(add_window, text="生产日期:").pack() + entry_production_date = tk.Entry(add_window) + entry_production_date.pack() + tk.Button(add_window, text="提交", command=submit).pack() + + def query_jiaju(self): + self.cursor.execute("SELECT * FROM jia_ju") + results = self.cursor.fetchall() + if results: + result_text = "\n".join([str(row) for row in results]) + messagebox.showinfo("查询结果", result_text) + else: + messagebox.showinfo("查询结果", "无数据") + + def delete_jiaju(self): + def confirm_delete(): + item_id = entry_id.get() + # 执行删除操作 + sql = f"DELETE FROM jia_ju WHERE id={item_id}" + try: + self.cursor.execute(sql) + self.conn.commit() + messagebox.showinfo("成功", "家居信息已删除!") + except Exception as e: + messagebox.showerror("错误", f"删除失败: {e}") + finally: + delete_window.destroy() + + delete_window = tk.Toplevel(self.root) + delete_window.title("删除家居") + tk.Label(delete_window, text="请输入家居ID:").pack() + entry_id = tk.Entry(delete_window) + entry_id.pack() + tk.Button(delete_window, text="确认删除", command=confirm_delete).pack() + + def update_jiaju(self): + def fetch_jiaju_info(): + item_id = entry_id.get() + sql = f"SELECT * FROM jia_ju WHERE id={item_id}" + try: + self.cursor.execute(sql) + result = self.cursor.fetchone() + if result: + entry_name.delete(0, tk.END) + entry_name.insert(tk.END, result[1]) + entry_color.delete(0, tk.END) + entry_color.insert(tk.END, result[2]) + entry_brand.delete(0, tk.END) + entry_brand.insert(tk.END, result[3]) + entry_price.delete(0, tk.END) + entry_price.insert(tk.END, str(result[4])) + entry_production_date.delete(0, tk.END) + entry_production_date.insert(tk.END, result[5]) + else: + messagebox.showinfo("查询结果", "未找到该家居信息") + except Exception as e: + messagebox.showerror("错误", f"查询失败: {e}") + + def submit_update(): + item_id = entry_id.get() + name = entry_name.get() + color = entry_color.get() + brand = entry_brand.get() + price = entry_price.get() + production_date = entry_production_date.get() + sql = f"UPDATE jia_ju SET name='{name}', color='{color}', brand='{brand}', price={price}, production_date='{production_date}' WHERE id={item_id}" + try: + self.cursor.execute(sql) + self.conn.commit() + messagebox.showinfo("成功", "家居信息已更新!") + except Exception as e: + messagebox.showerror("错误", f"更新失败: {e}") + finally: + update_window.destroy() + + update_window = tk.Toplevel(self.root) + update_window.title("修改家居信息") + tk.Label(update_window, text="请输入家居ID:").pack() + entry_id = tk.Entry(update_window) + entry_id.pack() + tk.Button(update_window, text="查询", command=fetch_jiaju_info).pack() + + tk.Label(update_window, text="名称:").pack() + entry_name = tk.Entry(update_window) + entry_name.pack() + tk.Label(update_window, text="颜色:").pack() + entry_color = tk.Entry(update_window) + entry_color.pack() + tk.Label(update_window, text="品牌:").pack() + entry_brand = tk.Entry(update_window) + entry_brand.pack() + tk.Label(update_window, text="价格:").pack() + entry_price = tk.Entry(update_window) + entry_price.pack() + tk.Label(update_window, text="生产日期:").pack() + entry_production_date = tk.Entry(update_window) + entry_production_date.pack() + + tk.Button(update_window, text="提交修改", command=submit_update).pack() + + def close_conn(self): + # 关闭数据库连接 + self.cursor.close() + self.conn.close() + + +if __name__ == '__main__': + root = tk.Tk() + app = JiaJuPage(root) + + # 确保在程序结束时关闭数据库连接 + root.protocol("WM_DELETE_WINDOW", app.close_conn) + root.mainloop() \ No newline at end of file diff --git a/智能家居系统/智能家居数据库.py b/智能家居系统/智能家居数据库.py new file mode 100644 index 0000000..f6ccf94 --- /dev/null +++ b/智能家居系统/智能家居数据库.py @@ -0,0 +1,89 @@ +from datetime import datetime +from flask import request, jsonify + +from flask import * +from flask_sqlalchemy import SQLAlchemy +import pymysql + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:LH20021212@localhost:3306/智能家居系统' +db = SQLAlchemy(app) + +try: + with app.app_context(): + # 尝试连接数据库 + + db.create_all() + print("数据库连接成功!") + +except Exception as e: + print(f"数据库连接失败: {str(e)}") + + +class JiaJu(db.Model): + _tablename_ = 'JiaJu' # 这里应该是__tablename__ + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + color = db.Column(db.String(50)) + brand = db.Column(db.String(50)) + price = db.Column(db.Float, nullable=False) + production_date = db.Column(db.Date, nullable=False) + +#增 +@app.route('/add_jiaju', methods=['POST']) +def add_jiaju(): + data = request.get_json() # 假设前端发送的是JSON数据 + new_jiaju = JiaJu( + name=data['name'], + color=data.get('color'), # 如果color可选,则使用get + brand=data.get('brand'), + price=data['price'], + production_date=datetime.strptime(data['production_date'], '%Y-%m-%d') # 假定日期格式为YYYY-MM-DD + ) + db.session.add(new_jiaju) + db.session.commit() + return jsonify({"message": "家居添加成功"}), 201 +#查 +@app.route('/jiaju', methods=['GET']) +def get_all_jiaju(): + jiaju_list = JiaJu.query.all() + result = [{"id": item.id, "name": item.name, "color": item.color, "brand": item.brand, + "price": item.price, "production_date": item.production_date.strftime('%Y-%m-%d')} for item in jiaju_list] + return jsonify(result) +#获取 +@app.route('/jiaju/', methods=['GET']) +def get_jiaju(jiaju_id): + jiaju = JiaJu.query.get_or_404(jiaju_id) + return jsonify({ + "id": jiaju.id, + "name": jiaju.name, + "color": jiaju.color, + "brand": jiaju.brand, + "price": jiaju.price, + "production_date": jiaju.production_date.strftime('%Y-%m-%d') + }) +#更 +@app.route('/jiaju/', methods=['PUT']) +def update_jiaju(jiaju_id): + jiaju = JiaJu.query.get_or_404(jiaju_id) + data = request.get_json() + if 'name' in data: + jiaju.name = data['name'] + if 'color' in data: + jiaju.color = data['color'] + if 'brand' in data: + jiaju.brand = data['brand'] + if 'price' in data: + jiaju.price = data['price'] + if 'production_date' in data: + jiaju.production_date = datetime.strptime(data['production_date'], '%Y-%m-%d') + db.session.commit() + return jsonify({"message": "家居信息更新成功"}) +#删 +@app.route('/jiaju/', methods=['DELETE']) +def delete_jiaju(jiaju_id): + jiaju = JiaJu.query.get_or_404(jiaju_id) + db.session.delete(jiaju) + db.session.commit() + return jsonify({"message": "家居删除成功"}) + diff --git a/智能家居系统/测试.py b/智能家居系统/测试.py new file mode 100644 index 0000000..dcbfbea --- /dev/null +++ b/智能家居系统/测试.py @@ -0,0 +1,22 @@ +from flask import Flask + +import pymysql + +from flask_sqlalchemy import SQLAlchemy + + +app = Flask(__name__) + +app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:LH20021212@localhost:3306/智能家居系统' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db = SQLAlchemy(app) + +class Jia(): + __tablename__ = '家居信息' # 注意这里使用英文表名 + 编号 = db.Column(db.Integer, primary_key=True) + 名称 = db.Column(db.String(20), nullable=False) + 品牌 = db.Column(db.String(20), nullable=False) + 颜色 = db.Column(db.String(20), nullable=False) + 价格 = db.Column(db.String(20), nullable=False) + 生产日期 = db.Column(db.String(20), nullable=False) \ No newline at end of file diff --git a/智能家居系统/登录界面.py b/智能家居系统/登录界面.py new file mode 100644 index 0000000..07bc514 --- /dev/null +++ b/智能家居系统/登录界面.py @@ -0,0 +1,45 @@ +import tkinter as tk + +import tkinter.messagebox + +from 主页面 import MainPage +#from 家居信息 import JiaJuPage + + +class LoginPage: + def __init__(self,master): + self.root = master + self.page = tk.Frame(self.root) + self.page.pack() + self.root.title('智能家居系统') + self.root.geometry("300x180") + + self.username = tk.StringVar() + self.password = tk.StringVar() + + tk.Label(self.page).grid(row=0, column=0) + tk.Label(self.page, text='账户').grid(row=1,column=0) + tk.Entry(self.page, textvariable=self.username).grid(row=1, column=1) + tk.Label(self.page, text='密码').grid(row=2, column=0) + tk.Entry(self.page, textvariable=self.password).grid(row=2, column=1, pady=10) + + tk.Button(self.page, text='登录', command=self.login_check).grid(row=3, column=0, pady=10) + tk.Button(self.page, text='退出', command=root.quit).grid(row=3, column=1, pady=10,stick=tk.E) + + def login_check(self): + name = self.username.get() + pwd = self.password.get() + + if name == '123' or pwd == '123' : + tkinter.messagebox.showinfo(title='恭喜', message='登录成功') + self.page.destroy() + #JiaJuPage(self.root) + MainPage(self.root) + else: + tkinter.messagebox.showinfo(title='错误', message='账户或密码错误') + +if __name__ == '__main__': + root = tk.Tk() + root.title("智能家居系统") + LoginPage(root) + root.mainloop() \ No newline at end of file diff --git a/计科2101 雷浩 21412030125.doc b/计科2101 雷浩 21412030125.doc new file mode 100644 index 0000000..2f2b2b4 Binary files /dev/null and b/计科2101 雷浩 21412030125.doc differ