預備知識
在之前tornado商城項目中,在開始之前需要引入一些項目設計知識,如接口,抽象方法抽象類,組合,程序設計原則等,個人理解項目的合理設計可增加其靈活性,
降低數據之間的耦合性,提高穩定性,下面介紹一些預備知識
1、接口
其實py中沒有接口這個概念。要想實現接口的功能,可以通過主動拋出異常來實現
接口作用:對派生類起到限制的作用
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
接口,python中的接口,通過在父類中主動拋出異常實現
接口的作用:起到了限制的作用
"""
class IFoo:
def fun1(self):
pass
raise Exception("錯誤提示")
class Bar(IFoo):
def fun1(self):
#方法名必須和父類中的方法名相同,不然沒辦法正常執行,會拋出異常
print("子類中如果想要調用父類中的方法,子類中必須要有父類中的方法名")
def fun2(self):
print("test")
obj = Bar()
obj.fun2()
2.抽象方法抽象類
抽象類,抽象方法是普通類和接口的綜合,即可以繼承也可以起到限制作用
由於python 本身沒有抽象類、接口的概念,所以要實現這種功能得abc.py 這個類庫,
具體實現方法如下 :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
抽象類,抽象方法
抽象類,抽象方法是普通類和接口的綜合,即可以繼承也可以起到限制作用
"""
import abc
class Foo(metaclass=abc.ABCMeta):
def fun1(self):
print("fun1")
def fun2(self):
print("fun2")
@abc.abstractclassmethod
def fun3(self):
pass
class Bar(Foo):
def fun3(self):
print("子類必須有父類的抽象方法名,不然會拋出異常")
obj = Bar()
obj.fun1()
obj.fun2()
obj.fun3()
3.組合
python中“多用組合少用繼承”,因為繼承的偶合性太強,可以把基類,當做參數傳入派生類中,用於解偶
§繼承
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#繼承
class Animals:
def eat(self):
print(self.Name + " eat")
def drink(self):
print(self.Name + " drink")
class Person(Animals):
def __init__(self, name):
self.Name = name
def think(self):
print(self.Name + " think")
obj = Person("user1")
obj.drink()
obj.eat()
obj.think()
§組合
class Animals:
def __init__(self,name):
self.Name = name
def eat(self):
print(self.Name + " eat")
def drink(self):
print(self.Name + " drink")
class Person:
def __init__(self, obj):
self.obj = obj
def eat(self):
self.obj.eat()
def think(self,name):
print(name + " think")
animals = Animals("animals")
obj = Person(animals)
obj.think("person")
obj.eat()
4.依賴注入
像上一例中,如果有多層關系時,需要傳入多個對象,為了解決這個問題就引入了依賴注入,
如上例在Person類實例化時自動傳入Animals對象
class Foo:
def __init__(self):
self.name = 111
def fun(self)
print(self.name)
obj = Foo() #obj是Foo的實例化對象
在python中一切皆對象,Foo是通過type類創建的
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class MyType(type):
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
obj.__init__(*args, **kwargs)
return obj
class Foo(metaclass=MyType):
def __init__(self, name):
self.name = name
def f1(self):
print(self.name)
解釋器解釋:
1.遇到 class Foo,執行type的__init__方法
1.Type的init的方法里做什么么呢?不知道
obj = Foo(123)
3.執行Type的 __call__方法
執行Foo類的 __new__方法
執行Foo類的 __init__ 方法
new 和 __init()和__metaclass__:
-
__new__函數是實例一個類所要調用的函數,每當我們調用obj = Foo()來實例一個類時,都是先調用__new__()
-
然后再調用__init__()函數初始化實例. __init__()在__new__()執行后執行,
-
類中還有一個屬性 __metaclass__,其用來表示該類由 誰 來實例化創建,所以,我們可以為 __metaclass__ 設置一個type類的派生類,從而查看 類 創建的過程。
那么依賴注入的實現方法,自定義一個type方法,實例化類的時候指定由自定義的type方法創建,
具體實現方法如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 依賴注入應用
#DI
class Mapper:
__mapper_relation ={}
@staticmethod
def register(cls,value):
Mapper.__mapper_relation[cls] = value
@staticmethod
def exist(cls):
if cls in Mapper.__mapper_relation:
return True
return False
@staticmethod
def value(cls):
return Mapper.__mapper_relation[cls]
class MyType(type):
def __call__(self, *args, **kwargs):
obj = self.__new__(self, *args, **kwargs)
arg_list = list(args)
if Mapper.exist(self):
value=Mapper.value(self)
arg_list.append(value)
obj.__init__(*arg_list, **kwargs)
return obj
#定義由誰來實例化
class Foo(metaclass=MyType):
def __init__(self,name):
self.name = name
def f1(self):
print(self.name)
class Bar(metaclass=MyType):
def __init__(self,name):
self.name = name
def f1(self):
print(self.name)
Mapper.register(Foo,"test1")
Mapper.register(Bar,"test12")
f=Foo()
print(f.name)
5.程序的設計原則
1. 單一責任原則(SRP)
一個對象只對一個元素負責 優點; 消除耦合,減小因需求變化引起代碼僵化
2.開放封閉原則(OCP)
例如裝飾器,可以對獨立的功能實現擴展,但對源碼不能進行修改 對擴展開放,對修改封閉 優點: 按照OCP原則設計出來的系統,降低了程序各部分之間的耦合性,其適應性、靈活性、穩定性都比較好。當已有軟件系統需要增加新的功能時, 不需要對作為系統基礎的抽象層進行修改,只需要在原有基礎上附加新的模塊就能實現所需要添加的功能。增加的新模塊對原有的模塊完全沒有影響或影響很小, 這樣就無須為原有模塊進行重新測試 如何實現 ? 在面向對象設計中,不允許更必的是系統的抽象層,面允許擴展的是系統的實現層,所以解決問題的關鍵是在於抽象化。 在面向對象編程中,通過抽象類及接口,規定具體類的特征作為抽象層,相對穩定,不需要做更改的從面可以滿足“對修改關閉”的原則;而從抽象類導出的具體 類可以 改變系統 的行為,從而滿足“對擴展開放的原則"
3.里氏替換原則(LSP)
子類可以替換父類,父類出現的地方都可以用子類替換 可以使用任何派生類(子類)替換基類 優點: 可以很容易的實現同一父類下各個子類的互換,而客戶端可以毫不察覺
4.接口分享原則(ISP)
對於接口進行分類避免一個接口的方法過多,避免”胖接口" 優點: 會使一個軟件系統功能擴展時,修改的壓力不會傳到別的對象那里 如何實現 ? 得用委托分離接口 利用多繼承分離接口
5.依賴倒置原則(DIP)
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象(理解為接口);抽象不應該依賴細節;細節應該依賴抽象 隔離關系,使用接口或抽象類代指 高層次的模塊不應該依賴於低層次的模塊,而是,都應該依賴於抽象 優點: 使用傳統過程化程序設計所創建的依賴關系,策略依賴於細節,這是糟糕的,因為策略受到細節改變的影響。 依賴倒置原則使細節和策略都依賴於抽象,抽象的穩定性決定了系統的穩定性
6.依賴注入(DI)和控制反轉原則(ICO)
使用鈎子再原來執行流程中注入其他對象
tornado項目設計實例
實例只包含登錄,寫此實例目的在於更好的理解及應用以上的內容
1、目錄規划
說明:
Infrastructure 目錄:公共組件目錄 Model:業務邏輯處理目錄 Repository: 數據倉庫及數據處理目錄 Statics:靜態文件目錄如(css,js,images等) UIAdmin: UI層 Views:模板文件目錄 Application.py : 服務啟動文件
2.業務訪問流程
介紹完目錄規划,那就來講講業務訪問流程及數據走向
啟動服務后,客戶端訪問URL,根據tornado路由找到相對的handler進行處理
找到handler后其相對方法(get/post/delete/put)中調用Model邏輯處理層方法進行處理並接收處理結果
Model邏輯處理層需
①創建接口
②建模
③創建協調層
創建完之后 ,由協調層(這里通用Services)調用數據層方法並接收處理結果返回給handler
4.數據處理層接收到Model調用后,處理數據並將數據返回給Model業務邏輯處理層
5.最終handler接收到最終結果,進行判斷處理,並將處理結果返回給用戶
3、落實實施
1.啟動文件,路由關系配置
#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web from UIAdmin.Controllers import Account from UIAdmin.Controllers import Region from UIAdmin.Controllers import Customer from UIAdmin.Controllers import Merchant from UIAdmin import mapper settings = { 'template_path': 'Views', 'static_path': 'Statics', 'static_url_prefix': '/statics/', } application = tornado.web.Application([ (r"/login", Account.LoginHandler), (r"/check", Account.CheckCodeHandler), ],**settings) if __name__ == "__main__": application.listen(8000) tornado.ioloop.IOLoop.instance().start()
說明:
settings 中指定配置,如模板文件路徑,靜態文件路徑等
application :路由配置,那個路徑由那個handler進行處理
2.handler配置
#!/usr/bin/env python # -*- coding: utf-8 -*- import io from Infrastructure.Core.HttpRequest import BaseRequestHandler from Infrastructure.utils import check_code from Model.User import UserService class LoginHandler(BaseRequestHandler): def get(self, *args, **kwargs): self.render("Admin/Account/login.html") def post(self, *args, **kwargs): username = self.get_argument("username",None) email = self.get_argument("email",None) pwd = self.get_argument("pwd",None) code = self.get_argument("checkcode",None) service = UserService() result = service.check_login(user=username,email=email,pwd=pwd) #obj封裝了所有的用戶信息,UserModel對象 if result and code.upper() == self.session["CheckCode"].upper(): self.session['username'] = result.username self.redirect("/ProvinceManager.html") else: self.write("alert('error')")
handler中主要是針對數據訪問方式的不同,給出不同的處理方法,並將結果返回給客戶端
3.Model 邏輯處理層
邏輯處理層中,着重看的有三點:
建模
接口
協調
#!/usr/bin/env python # -*- coding: utf-8 -*- #建模 from Infrastructure.DI.Meta import DIMetaClass class VipType: VIP_TYPE = ( {'nid': 1, 'caption': '銅牌'}, {'nid': 2, 'caption': '銀牌'}, {'nid': 3, 'caption': '金牌'}, {'nid': 4, 'caption': '鉑金'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in VipType.VIP_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption) class UserType: USER_TYPE = ( {'nid': 1, 'caption': '用戶'}, {'nid': 2, 'caption': '商戶'}, {'nid': 3, 'caption': '管理員'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in UserType.USER_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption) class UserModel: def __init__(self, nid, username,password, email, last_login, user_type_obj, vip_type_obj): self.nid = nid self.username = username self.email = email self.password = password self.last_login = last_login self.user_type_obj = user_type_obj self.vip_type_obj = vip_type_obj
接口 IUseRepository類:接口類,用於約束數據庫訪問類的方法
class IUserRepository: def fetch_one_by_user(self,user,pwd): """ 根據用戶名和密碼獲取對象 :param user: :param pwd: :return: """ def fetch_one_by_email(self, user, pwd): """ 根據郵箱和密碼獲取對象 :param user: :param pwd: :return: """
協調 協調作用主要是調用數據處理層的方法,並將數據處理層處理后的結果返回給它的上一層的調度者
class UserService(metaclass=DIMetaClass): def __init__(self, user_repository): """ :param user_repository: 數據倉庫對象 """ self.userRepository = user_repository def check_login(self,user,email,pwd): if user: #數據倉庫執行SQL后返回的字典 #{"nid":1,username:xxx,vip:2,usertype:1} ret = self.userRepository.fetch_one_by_user(user,pwd) else: ret = self.userRepository.fetch_one_by_email(email,pwd) return ret
4.Repository數據處理層
將處理后結果(usermodel對象)返回給上一層調度者(UserService)
#!/usr/bin/env python # -*- coding:utf-8 -*- #數據表創建 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column from sqlalchemy import Integer, Integer, CHAR, VARCHAR, ForeignKey, Index, DateTime, DECIMAL, TEXT from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/ShoppingDb?charset=utf8", max_overflow=5) Base = declarative_base() class Province(Base): """ 省 """ __tablename__ = 'province' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) class City(Base): """ 市 """ __tablename__ = 'city' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) province_id = Column(Integer, ForeignKey('province.nid')) class County(Base): """ 縣(區) """ __tablename__ = 'county' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) city_id = Column(Integer, ForeignKey('city.nid')) class UserInfo(Base): """ 用戶信息 """ __tablename__ = 'userinfo' nid = Column(Integer, primary_key=True) USER_TYPE = ( {'nid': 1, 'caption': '用戶'}, {'nid': 2, 'caption': '商戶'}, {'nid': 3, 'caption': '管理員'}, ) user_type = Column(Integer) VIP_TYPE = ( {'nid': 1, 'caption': '銅牌'}, {'nid': 2, 'caption': '銀牌'}, {'nid': 3, 'caption': '金牌'}, {'nid': 4, 'caption': '鉑金'}, ) vip = Column(Integer) username = Column(VARCHAR(32)) password = Column(VARCHAR(64)) email = Column(VARCHAR(64)) last_login = Column(DateTime) ctime = Column(DateTime) __table_args__ = ( Index('ix_user_pwd', 'username', 'password'), Index('ix_email_pwd', 'email', 'password'), )
#!/usr/bin/env python # -*- coding: utf-8 -*- from Model.User import IUserRepository from Model.User import UserModel from Model.User import UserType from Model.User import VipType from Repository.Admin.DbConnection import DbConnection class UserRepository(IUserRepository): def __init__(self): self.db_conn = DbConnection() def fetch_one_by_email(self, email, password): ret = None cursor = self.db_conn.connect() sql = """select nid,username,password,email,last_login,vip,user_type from userinfo where email=%s and password=%s""" cursor.execute(sql, (email, password)) db_result = cursor.fetchone() self.db_conn.close() print(type(db_result), db_result) if db_result: ret = UserModel(nid=db_result['nid'], username=db_result['username'], password=db_result['password'], email=db_result['email'], last_login=db_result['last_login'], user_type_obj=UserType(nid=db_result['user_type']), vip_type_obj=VipType(nid=db_result['vip']),) return ret return db_result def fetch_one_by_user(self, username, password): ret = None cursor = self.db_conn.connect() sql = """select nid,username,password,email,last_login,vip,user_type from userinfo where username=%s and password=%s""" cursor.execute(sql, (username, password)) db_result = cursor.fetchone() self.db_conn.close() if db_result: #建模,將usermodel對象返回給上一層調用者,因為要向用戶展示的user_type不可能為1,2這些數據而應該是相對的caption ret = UserModel(nid=db_result['nid'], username=db_result['username'], password=db_result['password'], email=db_result['email'], last_login=db_result['last_login'], user_type_obj=UserType(nid=db_result['user_type']), vip_type_obj=VipType(nid=db_result['vip']),) return ret return db_result
5.Handler最終處理
接收到最終處理結果后判斷,並返回數據給用戶
說明:
有沒有注意到UserService是怎么和數據處理層建立聯系的?
這里我們用到了依賴注入,具體配置如下:
#!/usr/bin/env python # -*- coding:utf-8 -*- #依賴注入 class DIMapper: __mapper_dict = {} @staticmethod def inject(cls, arg): if cls not in DIMapper.__mapper_dict: DIMapper.__mapper_dict[cls] = arg @staticmethod def get_mappers(): return DIMapper.__mapper_dict class DIMetaClass(type): def __call__(cls, *args, **kwargs): # 獲取配置的對應的對象,攜帶進入 obj = cls.__new__(cls, *args, **kwargs) mapper_dict = DIMapper.get_mappers() if cls in mapper_dict: cls.__init__(obj, mapper_dict[cls]) else: cls.__init__(obj, *args, **kwargs) return obj

#!/usr/bin/env python # -*- coding: utf-8 -*- # 依賴注入綁定 from Infrastructure.DI import Meta from Model.User import UserService from Repository.Admin.UserRepository import UserRepository Meta.DIMapper.inject(UserService,UserRepository())
6.靜態文件代碼
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<title>購物商城</title>
<link href="/statics/Admin/Css/common.css" rel="stylesheet" />
<link href="/statics/Admin/Css/account.css" rel="stylesheet" />
</head>
<body>
<div class="account-container bg mt10">
<div class='header clearfix'>
<div>
<a href="/home/index">
<img src="/statics/Admin/Images/mll_logo.gif">
</a>
</div>
</div>
</div>
<div class='account-container mt30'>
<div class='body clearfix pd10' style='position: relative;'>
<div class='logo left'>
<img style='height:350px;' src="/statics/Admin/Images/login_logo.png" />
</div>
<div class='login left mt30'>
<form id='Form' action='/login' method='POST'>
<div class='group mt10'>
<label class='tip'><span class="red">*</span>用戶名:</label>
<input type='text' require='true' label='用戶名' Field='string' range='4-40' name='username' />
<i class='i-name'></i>
</div>
<div class='group'>
<label class='tip'><span class="red">*</span>密碼:</label>
<input type='password' require='true' label='密碼' min-len='6' name='pwd' />
<i class='i-pwd'></i>
</div>
<div class='group'>
<label class='tip'><span class="red">*</span>驗證碼:</label>
<input type='text' require='true' label='驗證碼' style='width:80px;' name='checkcode' />
<a style='width:125px;display:inline-block;'><img class='checkcode' onclick='ChangeCode();' id='imgCode' src='/check' /></a>
</div>
<div class='group font12 mb0'>
<label class='tip'></label>
<label style='width:246px;display: inline-block;'>
<input id='protocol' name='protocol' type='checkbox' checked='checked' />
<span>自動登錄</span>
<span class='ml10'><a href='#'>忘記密碼?</a></span>
</label>
</div>
<div class='group mt0'>
<label class='tip'></label>
<input type='submit' class='submit' value='登 錄' />
</div>
</form>
<div class='go-register'><a href='#'>免費注冊 >> </a></div>
</div>
</div>
</div>
<div class='account-container mt20' style='text-align:center;color:#555;'>
© 2004-2015 www.xxxxx.com.cn All Rights Reserved. xxxxx 版權所有
</div>
<script src="/statics/Admin/js/jquery-1.8.2.min.js"></script>
<script src="/statics/Admin/js/treebiao.js"></script>
<script type="text/javascript">
$(function(){
$.login('#Form','');
});
function ChangeCode() {
var code = document.getElementById('imgCode');
code.src += '?';
}
</script>
</body>
</html>
.header{ padding:15px 0px; } .body{ border: 1px solid #d7d7d7; padding: 40px; padding-right: 0; } .body .logo{ width:50%; } .body .login{ width:50%; color: #555; } .body .register{ width: 630px; border-right: 1px dashed #e5e5e5; color: #555; } .body .register .group,.body .login .group{ margin:15px 0px; height:38px; font-size:14px; position:relative; line-height:38px; } .body .register .group .tip,.body .login .group .tip{ width: 100px; display: inline-block; text-align: right; font-size: 14px; } .body .register .group label .red,.body .login .group label .red{ margin:0 5px; } .body .register .group input[type='text'],.body .register .group input[type='password'], .body .login .group input[type='text'],.body .login .group input[type='password']{ width:210px; height:32px; padding:0 30px 0 4px; border: 1px solid #cccccc; } .body .register .group i,.body .login .group i{ position: absolute; left: 330px; } .body .register .group .i-name,.body .login .group .i-name{ background: url(../Images/i_name.jpg) no-repeat scroll 0 0 transparent; height: 16px; top: 10px; width: 16px; } .body .register .group .i-pwd,.body .login .group .i-pwd{ background: url(../Images/i_pwd.jpg) no-repeat scroll 0 0 transparent; height: 19px; top: 10px; width: 14px; } .body .register .group .i-phone,.body .register .login .i-phone{ background: url(../Images/i_phone.jpg) no-repeat scroll 0 0 transparent; height: 21px; top: 10px; width: 14px; } .body .register .group .input-error{ font-size:12px; color: #e4393c; display: inline-block; line-height: 32px; height: 32px; width: 260px; padding: 0 5px; background: #FFEBEB; border: 1px solid #ffbdbe; } .body .login .group .input-error{ font-size:10px; position: absolute; color: #e4393c; background: #FFEBEB; border: 1px solid #ffbdbe; display: block; z-index: 10; height: 15px; width: 244px; line-height: 15px; left: 104px; } .body .register .group .checkcode,.body .login .group .checkcode{ position:absolute; margin:-20px 0 0 5px; } .body .register .group .submit,.body .login .group .submit{ background-color: #e4393c; padding:8px 20px; width:246px; color: white; text-align: center; border:1px solid #e4393c; } .body .more{ padding:20px; } .body .login .go-register{ position: absolute; right:0px; bottom:0px; } .body .login .go-register a{ line-height: 32px; text-align: center; font-size: 14px; background: #7cbe56; width: 115px; height: 32px; display: block; color: #FFF; } .pg-footer{ margin:20px 0; color: #555; }
/*公共開始*/ body { margin: 0 auto; font-family: Arial; _font-family: 宋體,Arial; font-size: 12px; } body, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, button, textarea, p, blockquote, th, td, figure, div { margin: 0; padding: 0; } ol, ul, li { list-style: none; } a{ cursor:pointer; text-decoration:none; } /*a:hover{ color: #F60 !important; text-decoration: underline; }*/ img{ border:none; border-width:0px; } table{ border-collapse: collapse; border-spacing: 0; } .red{ color: #c00 !important; } .m8{ margin:8px; } .mg20{ margin:20px; } .mt0{ margin-top:0px !important; } .mt10{ margin-top:10px; } .mt20{ margin-top:20px; } .mt30{ margin-top:30px !important; } .mr5{ margin-right:5px; } .ml5{ margin-left:5px; } .ml10{ margin-left:10px; } .mb0{ margin-bottom:0px !important; } .mb20{ margin-bottom:20px; } .mb10{ margin-bottom:10px; } .pd10{ padding:10px !important; } .pt18{ padding-top:18px; } .pt20{ padding-top:20px; } .pb20{ padding-bottom:20px; } .nbr{ border-right:0px; } .font12{ font-size:12px !important; } .font13{ font-size:13px !important; } .font14{ font-size:14px; } .font16{ font-size:16px; } .bold{ font-weight:bold; } .left{ float:left; } .right{ float:right; } .hide{ display:none; } .show{ display:table; } .clearfix{ clear:both; } .clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } * html .clearfix {zoom: 1;} .container{ width:1190px; margin-left:auto; margin-right:auto; } .narrow{ width:980px !important; margin-left:auto; margin-right:auto; } .account-container{ width:980px; margin-left:auto; margin-right:auto; } .group-box-1 .title{ height: 33px; line-height: 33px; border: 1px solid #DDD; background: #f5f5f5; padding-top: 0; padding-left: 0; } .group-box-1 .title .title-font{ display: inline-block; font-size: 14px; font-family: 'Microsoft Yahei','SimHei'; font-weight: bold; color: #333; padding-left: 10px; } .group-box-1 .body { border: 1px solid #e4e4e4; border-top: none; } .tab-menu-box1 { border: 1px solid #ddd; margin-bottom: 20px; } .tab-menu-box1 .menu { line-height: 33px; height: 33px; background-color: #f5f5f5; } .tab-menu-box1 .content { min-height: 100px; border-top: 1px solid #ddd; background-color: white; } .tab-menu-box1 .menu ul { padding: 0; margin: 0; list-style: none; /*position: absolute;*/ } .tab-menu-box1 .menu ul li { position: relative; float: left; font-size: 14px; font-family: 'Microsoft Yahei','SimHei'; text-align: center; font-size: 14px; font-weight: bold; border-right: 1px solid #ddd; padding: 0 18px; cursor: pointer; } .tab-menu-box1 .menu ul li:hover { color: #c9033b; } .tab-menu-box1 .menu .more { float: right; font-size: 12px; padding-right: 10px; font-family: "宋體"; color: #666; text-decoration: none; } .tab-menu-box1 .menu a:hover { color: #f60 !important; text-decoration: underline; } .tab-menu-box1 .menu .current { margin-top: -1px; color: #c9033b; background: #fff; height: 33px; border-top: 2px solid #c9033b; z-index: 10; } .tab-menu-box-2 .float-title { display: none; top: 0px; position: fixed; z-index: 50; } .tab-menu-box-2 .title { width: 890px; border-bottom: 2px solid #b20101; border-left: 1px solid #e1e1e1; clear: both; height: 32px; } .tab-menu-box-2 .title a { float: left; width: 107px; height: 31px; line-height: 31px; font-size: 14px; font-weight: bold; text-align: center; border-top: 1px solid #e1e1e1; border-right: 1px solid #e1e1e1; background: url(../images/bg4.png?3) 0 -308px repeat-x; text-decoration: none; color: #333; cursor: pointer; } .tab-menu-box-2 .title a:hover { background-position: -26px -271px; text-decoration: none; color: #fff; } .tab-menu-box-2 .content { min-height: 100px; background-color: white; } .tab-menu-box3 { border: 1px solid #ddd; } .tab-menu-box3 .menu { line-height: 33px; height: 33px; background-color: #f5f5f5; } .tab-menu-box3 .content { height: 214px; border-top: 1px solid #ddd; background-color: white; } .tab-menu-box3 .menu ul { padding: 0; margin: 0; list-style: none; /*position: absolute;*/ } .tab-menu-box3 .menu ul li { position: relative; float: left; font-size: 14px; font-family: 'Microsoft Yahei','SimHei'; text-align: center; font-size: 14px; width:50%; cursor: pointer; } .tab-menu-box3 .menu ul li:hover { color: #c9033b; } .tab-menu-box3 .menu .more { float: right; font-size: 12px; padding-right: 10px; font-family: "宋體"; color: #666; text-decoration: none; } .tab-menu-box3 .menu a:hover { color: #f60 !important; text-decoration: underline; font-weight: bold; } .tab-menu-box3 .menu .current { margin-top: -1px; color: #c9033b; background: #fff; height: 33px; border-top: 2px solid #c9033b; z-index: 10; font-weight: bold; } .quantity-bg{ height:20px; width: 77px; border: 1px solid #999; } .quantity-bg .minus,.quantity-bg .plus{ height:20px; width:20px; line-height:20px; text-align:center; vertical-align:middle; } .quantity-bg input{ height:20px; width:35px; border:0px; border-left:1px solid #999; border-right:1px solid #999; } /*公共結束*/
后續還有更新版本...
