過本文章,你可以掌握以下內容:
1、Pyside6 Layout介紹
QtWidgets.QLayout是Qt中用于管理窗口或對話框中小部件(控件)布局的基類。它是一個抽象基類,定義了所有布局類共有的接口和行為。QLayout及其子類的主要作用是自動管理小部件的位置和大小,以便于創建整潔和靈活的圖形用戶界面(GUI),對于復雜界面布局是很重要的,主要作用包括:
Layout繼承關系圖
2、Pyside6 Layout布局解釋及示例
以下是繼承自QtWidgets.QLayout的布局,每種布局有對應的行為。
布局 | 行為 | 對應html |
QHBoxLayout | 線性水平布局 | 類似于display: flex; flex-direction: row;的Flexbox布局 |
QVBoxLayout | 線性垂直布局 | 類似于display: flex; flex-direction: column;的Flexbox布局 |
QGridLayout | 在可轉位網格 XxY 中 | 類似于html的table行和列 |
QStackedLayout | 堆疊 (z) 于彼此前面 | 類似于css的z-index,并控制隱藏顯示效果 |
QHBoxLayout是Qt中的一個布局管理器類,用于按水平方向排列小部件。它繼承自QLayout,提供了一種簡便的方式來自動管理窗口或對話框中小部件的位置和大小。使用QHBoxLayout,可以將小部件從左到右依次排列,而不需要手動指定每個小部件的具體位置。
QHBoxLayout繼承關系圖
簡單的示例如下:
import sys
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout之水平布局")
self.setMinimumSize(400,300)
layout=QHBoxLayout()
layout.addWidget(QPushButton("1"))
layout.addWidget(QPushButton("2"))
layout.addWidget(QPushButton("3"))
widget=QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
if __name__=='__main__':
app=QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec()
QHBoxLayout示例效果圖
QVBoxLayout是Qt中的一個布局管理器類,用于垂直方向排列小部件。它繼承自QLayout,提供了一種簡便的方式來自動管理窗口或對話框中小部件的位置和大小。使用QVBoxLayout,可以將小部件從上到下依次排列,而不需要手動指定每個小部件的具體位置。
QVBoxLayout繼承關系圖
簡單示例如下:
import sys
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("QVBoxLayout之垂直布局")
self.setMinimumSize(400, 300)
layout=QVBoxLayout()
layout.addWidget(QPushButton("1"))
layout.addWidget(QPushButton("2"))
layout.addWidget(QPushButton("3"))
widget=QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
if __name__=='__main__':
app=QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec()
效果圖如下:
QVBoxLayout示例效果圖
QGridLayout是Qt中一個非常強大的布局管理器,它提供一種網格式布局,這種布局由行和列組成(類似table),每個小部件占據網格中的一個或多個單元格。QGridLayout提供了靈活的方式來創建復雜的用戶界面,使得小部件的布局可以精確控制,同時也能自動適應窗口大小的變化,主要特性有:
QGridLayout繼承關系圖
示例代碼如下:
import sys
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("QGridLayout之網格布局")
self.setMinimumSize(400, 300)
layout=QGridLayout()
# 為窗口部件設置樣式表,添加邊框
self.setStyleSheet("QWidget { border: 2px solid black; }")
layout.addWidget(QLabel('第0行第0列'), 0, 0)
layout.addWidget(QLabel('第0行第1列'), 0, 1)
layout.addWidget(QLabel('第1行第0列'), 1, 0)
layout.addWidget(QLabel('第1行第1列'), 1, 1)
widget=QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
if __name__=='__main__':
app=QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec()
效果圖如下:
QGridLayout示例效果圖
QStackedLayout是Qt中的一個布局管理器,它可以在相同的空間內堆疊多個小部件,但一次只顯示一個小部件。這種布局非常適合用于實現向導、選項卡和其他需要在多個頁面之間切換的界面。
QStackedLayout繼承關系圖
主要特性:
常用方法技巧:indexOf()函數返回小部件在該列表中的索引。可以使用addWidget()函數添加小部件到列表末尾,或者使用insertWidget()函數在給定索引處插入。removeWidget()函數從布局中移除給定索引的小部件。可以使用count()函數獲取布局中包含的小部件數量。widget()函數返回給定索引位置的小部件。當前顯示在屏幕上的小部件的索引由currentIndex()給出,并且可以使用setCurrentIndex()進行更改。以類似的方式,可以使用currentWidget()函數檢索當前顯示的小部件,并使用setCurrentWidget()函數進行更改。每當布局中的當前小部件發生變化或從布局中移除小部件時,分別會發出currentChanged()和widgetRemoved()信號。
示例代碼如下:
import sys
from PySide6.QtGui import QPalette, QColor
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QStackedLayout之堆疊布局")
self.setMinimumSize(400, 300)
pagelayout=QVBoxLayout()
button_layout=QHBoxLayout()
self.stacklayout=QStackedLayout()
pagelayout.addLayout(button_layout)
pagelayout.addLayout(self.stacklayout)
btn=QPushButton("red")
btn.pressed.connect(self.activate_tab_1)
button_layout.addWidget(btn)
self.stacklayout.addWidget(Color("red"))
btn=QPushButton("green")
btn.pressed.connect(self.activate_tab_2)
button_layout.addWidget(btn)
self.stacklayout.addWidget(Color("green"))
btn=QPushButton("yellow")
btn.pressed.connect(self.activate_tab_3)
button_layout.addWidget(btn)
self.stacklayout.addWidget(Color("yellow"))
widget=QWidget()
widget.setLayout(pagelayout)
self.setCentralWidget(widget)
def activate_tab_1(self):
self.stacklayout.setCurrentIndex(0)
def activate_tab_2(self):
self.stacklayout.setCurrentIndex(1)
def activate_tab_3(self):
self.stacklayout.setCurrentIndex(2)
class Color(QWidget):
def __init__(self, color):
super(Color, self).__init__()
self.setAutoFillBackground(True)
palette=self.palette()
palette.setColor(QPalette.Window, QColor(color))
self.setPalette(palette)
if __name__=='__main__':
app=QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec()
效果圖如下:
QStackedLayout示例效果圖
使用QHBoxLayout、QVBoxLayout、QGridLayout、QStackedLayout這幾種布局組合使用,來控制界面的整體風格視角,制作精美的布局效果
示例代碼如下:
import sys
from PySide6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("混合布局")
self.setMinimumSize(400, 300)
self.setStyleSheet("QLabel { border: 1px solid blue; }")
layout1=QHBoxLayout()
layout2=QVBoxLayout()
layout3=QVBoxLayout()
layout1.setContentsMargins(0,0,0,0)
layout1.setSpacing(20)
layout2.addWidget(QLabel('hbox1-QVBoxLayout1'))
layout2.addWidget(QLabel('hbox1-QVBoxLayout2'))
layout2.addWidget(QLabel('hbox1-QVBoxLayout3'))
layout1.addLayout(layout2)
layout1.addWidget(QLabel('hbox2'))
layout3.addWidget(QLabel('hbox2-QVBoxLayout1'))
layout3.addWidget(QLabel('hbox2-QVBoxLayout2'))
layout1.addLayout(layout3)
widget=QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)
if __name__=='__main__':
app=QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec()
效果圖如下:
混合布局效果圖
注意:QLayout邊框及樣式不能通過這種方式來設置樣式效果,需要指定Widget的樣式
#無效果
self.setStyleSheet("QHBoxLayout { border: 1px solid black; }")
#有效果
self.setStyleSheet("QLabel { border: 1px solid blue; }")
由于QHBoxLayout、QVBoxLayout、QGridLayout、QStackedLayout布局放置的控件,不增加任何處理,生成的控件會固定住窗口大小,可以通過下面來動態排列控件。
示例代碼如下:
import sys
from PySide6.QtCore import QRect, QSize, QPoint, Qt
from PySide6.QtWidgets import *
class FlowLayout(QLayout):
def __init__(self, parent=None, margin=0, spacing=-1):
super(FlowLayout, self).__init__(parent)
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self.items=[]
def addItem(self, item):
self.items.append(item)
def count(self):
return len(self.items)
def itemAt(self, index):
if index >=0 and index < len(self.items):
return self.items[index]
return None
def takeAt(self, index):
if index >=0 and index < len(self.items):
return self.items.pop(index)
return None
def expandingDirections(self):
return 0
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
height=self.doLayout(QRect(0, 0, width, 0), True)
return height
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return QSize(self.doLayout(QRect(0, 0, 10000, 0), True), 10000)
def doLayout(self, rect, testOnly):
x=rect.x()
y=rect.y()
lineHeight=0
for item in self.items:
wid=item.widget()
spaceX=self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton,Qt.Horizontal)
spaceY=self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton,Qt.Vertical)
nextX=x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0:
x=rect.x()
y=y + lineHeight + spaceY
nextX=x + item.sizeHint().width() + spaceX
lineHeight=0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x=nextX
lineHeight=max(lineHeight, item.sizeHint().height())
return y + lineHeight - rect.y()
if __name__=='__main__':
app=QApplication(sys.argv)
mainWidget=QWidget()
mainWidget.setMinimumSize(300, 200)
layout=FlowLayout(mainWidget)
for i in range(50):
layout.addWidget(QPushButton(f'Button {i}'))
mainWidget.setLayout(layout)
mainWidget.show()
sys.exit(app.exec_())
效果如下:
自定義Layout效果圖
Gartner最新的對商務智能軟件的專業分析報告中,Tableau持續領跑。Microsoft因為PowerBI表現出色也處于領導者象限。而昔日的領導者像SAP,SAS,IBM,MicroStrategy等逐漸被拉開了差距。
Tableau因為其靈活,出色的數據表現已經成為BI領域里無可爭議的領頭羊。而其數據驅動的可視化和核心思想是來自于Leland Wilkinson的The Grammar Of Graphics ,同樣受到該思想影響的還有R的圖形庫ggplot。
在數據可視化開源領域里,大家對百度開發的echarts可謂耳熟能詳,echarts經過多年的發展,其功能確實非常強大,可用出色來形容。但是螞蟻金服開源的基于The Grammar Of Graphics的語法驅動的可視化庫G2,讓人眼前一亮。那我們就看看如何利用G2和500行左右的純前端代碼來實現一個的類似Tableau的數據分析功能。
第一步是加載數據:
數據加載主要用到了三個庫:
數據通過我存放在GitHub中的csv格式的文件,以REST請求的方式來加載。下面的代碼把Axios的Promise變成 async/wait方式。
// Ajax async request const request={ get: url=> { return new Promise((resolve, reject)=> { axios .get(url) .then(response=> { resolve({ data: response.data }); }) .catch(error=> { resolve({ data: error }); }); }); } };
封裝好后,我們就可以用request.get()方法發送REST請求,獲取csv文件。
let csv=await request.get(url);
這一步可能會遇到跨域請求的問題,github上的文件支持跨域。
把數據存儲在一個SQL數據庫中,這樣做的好處是為了下一步做數據準備的時候,可以方便的利用SQL來進行查詢和分析。
class SqlTable { constructor(data) { this.data=data; } async query(sql) { // following line of code does not run in full page view due to security concern. // const query_str=sql.replace(/(?<=FROM\s+)\w+/, "CSV(?)"); const query_str=sql.replace("table", "CSV(?)"); return await alasql.promise(query_str, [this.data]); } }
SqlTable是一個對數據表的封裝,把csv數據存在SQL數據庫表中,提供一個query()方法。這里要做的是把SQL查詢個從 "SELECT * FROM table" 變成 "SELECT * FROM CSV(?)" 表示查詢參數是CSV數據。因為codepen的安全性限制,運行前向查找的replace語句(這里的regex表示把前面是“FROM ”詞的替換為CSV(?)的)在full page view下是不能執行的,所以我用了一個更簡單的假定,用戶的表名就是table,這樣做有很多問題,大家如果在codepen之外的環境,可以用注釋掉的代碼。
然后把"SELECT * FROM table"的查詢結果(JSON Array)用datatable來展示。
function sanitizeData(jsonArray) { let newKey; jsonArray.forEach(function(item) { for (key in item) { newKey=key.replace(/\s/g, "").replace(/\./g, ""); if (key !=newKey) { item[newKey]=item[key]; delete item[key]; } } }); return jsonArray; } function displayData(tableId, data) { // tricky to clone array let display_data=JSON.parse(JSON.stringify(data)); display_data=sanitizeData(display_data); let columns=[]; for (let item in display_data[0]) { columns.push({ data: item, title: item }); } $("#" + tableId).DataTable({ data: display_data, columns: columns, destroy: true }); }
這一步有兩點要注意:
這里要注意,Iris數據集中在datatable中的列名都不顯示點,但實際數據并沒有改變。
數據加載完畢,我們來到第二步的數據準備階段。數據準備是數據科學項目最花時間的一步,通常需要對數據進行大量的清洗,變形,抽取等工作,使得數據變得可用。
在這一步我們做了兩件事:
一是顯示數據的一個摘要,讓我們初步了解數據的概貌,為進一步的數據變形和處理做好準備。
這個是Iris數據集的摘要:
function isString(o) { return typeof o=="string" || (typeof o=="object" && o.constructor===String); } function summaryData(data) { let summary={}; summary.count=data.length; summary.fields=[]; for (let p in data[0]) { let field={}; field.name=p; if ( isString(data[0][p]) ) { field.type="string"; } else { field.type="number"; } summary.fields.push(field); } for (let f of summary.fields) { if ( f.type=="number" ) { f.max=d3.max(data, x=> x[f.name]); f.min=d3.min(data, x=> x[f.name]); f.mean=d3.mean(data, x=> x[f.name]); f.median=d3.median(data, x=> x[f.name]); f.deviation=d3.deviation(data, x=> x[f.name]); } else { f.values=Array.from(new Set(data.map(x=> x[f.name]))); } } return summary; }
這里我們利用數據的類型判斷出每一個字段是數值型還是字符型。對于字符型的字段,我們利用JS6的Set來獲得所有的Unique數據。對于數值型,我們利用d3的max,min,mean,median,deviation方法計算出對應的最大值,最小值,平均數,中位數和偏差。
另一個就是利用SQL查詢來對數據進行進一步的加工。
上圖的例子中我們利用限制條件得到一個Iris數據的子集。
另外G2還提供了Dataset的功能:
源數據的解析,將csv, dsv,geojson 轉成標準的JSON,查看Connector加工數據,包括 filter,map,fold(補數據) 等操作,查看 Transform統計函數,匯總統計、百分比、封箱 等統計函數,查看 Transform特殊數據處理,包括 地理數據、矩形樹圖、桑基圖、文字云 的數據處理,查看 Transform
數據處理是一個比較大的話題,我們的目標是利用盡可能少的代碼完成一個數據分析的工具,所以這一步僅僅是利用alasql提供的SQL查詢來處理數據。
數據處理好后就是我們的核心內容,數據展示了。
這一步主要是利用select2提供的選擇控件構建圖形語法來驅動數據展示。如上圖所示,對應的G2代碼圖形語法為:
g2chart.facet('rect', { fields: [ 'Admit', 'Dept' ], eachView(view) { view.interval().position('Gender*Freq').color('Gender').label('Freq'); } });
圖形語法主要包含以下幾個主要的元素:
幾何標記 Geometry
幾何標記定義了使用什么樣的幾何圖形來表征數據。G2現在支持如下這些幾何標記:
geom 類型描述point點,用于繪制各種點圖。path路徑,無序的點連接而成的一條線,常用于路徑圖的繪制。line線,點按照 x 軸連接成一條線,構成線圖。area填充線圖跟坐標系之間構成區域圖,也可以指定上下范圍。interval使用矩形或者弧形,用面積來表示大小關系的圖形,一般構成柱狀圖、餅圖等圖表。polygon多邊形,可以用于構建色塊圖、地圖等圖表類型。edge兩個點之間的鏈接,用于構建樹圖和關系圖中的邊、流程圖中的連接線。schema自定義圖形,用于構建箱型圖(或者稱箱須圖)、蠟燭圖(或者稱 K 線圖、股票圖)等圖表。heatmap用于熱力圖的繪制。
這里要注意,intervalstack是官方支持的,但是文檔沒有提到,在閱讀G2的API文檔的時候,我也發現文檔講的不是很清楚,有很多地方沒有講清楚如何使用API。這也是開源軟件值得改進的地方。
圖形屬性 Attributes
圖形屬性對應視覺編碼中的不同元素,大家可以參考我的另一博客 數據可視化中的視覺屬性 。
圖形屬性主要有以下幾種。
在構建語法的時候,我們把圖形屬性綁定一個或者多個數據字段。
坐標系 Coordinates
坐標系是將兩種位置標度結合在一起組成的 2 維定位系統,描述了數據是如何映射到圖形所在的平面。
G2提供了以下幾種坐標系:
coordType說明rect直角坐標系,目前僅支持二維,由 x, y 兩個互相垂直的坐標軸構成。polar極坐標系,由角度和半徑 2 個維度構成。theta一種特殊的極坐標系,半徑長度固定,僅僅將數據映射到角度,常用于餅圖的繪制。helix螺旋坐標系,基于阿基米德螺旋線。
分面 Facet
分面,將一份數據按照某個維度分隔成若干子集,然后創建一個圖表的矩陣,將每一個數據子集繪制到圖形矩陣的窗格中。分面其實提供了兩個功能:
G2支持以下的分面類型:
分面類型說明rect默認類型,指定 2 個維度作為行列,形成圖表的矩陣。list指定一個維度,可以指定一行有幾列,超出自動換行。circle指定一個維度,沿著圓分布。tree指定多個維度,每個維度作為樹的一級,展開多層圖表。mirror指定一個維度,形成鏡像圖表。matrix指定一個維度,形成矩陣分面。
注意,在我的代碼中,為了簡化使用,只支持list和rect,當綁定一個字段的時候用list,綁定兩個字段的時候用rect。
除了上面提到的元素,當然還有許多其它的元素我們沒有包含和支持,例如:坐標軸,圖例,提示等等。
關于圖形的語法的更多內容,請參考這里。
生成圖形語法的核心代碼如下:
function getFacet(faced, grammarScript) { let facedType="list"; let facedScript="" grammarScript=grammarScript.replace(chartScriptName,"view"); if ( faced.length==2 ) { facedType="rect"; } let facedFields=faced.join("', '") facedScript=facedScript + `${ chartScriptName }.facet('${ facedType }', {\n`; facedScript=facedScript + ` fields: [ '${ facedFields }' ],\n`; facedScript=facedScript + ` eachView(view) {\n`; facedScript=facedScript + ` ${ grammarScript };\n`; facedScript=facedScript + ` }\n`; facedScript=facedScript + `});\n`; return facedScript } function getGrammar() { let grammar={}, grammarScript=chartScriptName + "."; grammar.geom=$('#geomSelect').val(); grammar.coord=$('#coordSelect').val(); grammar.faced=$('#facetSelect').val(); geom_attributes.map(function(attr){ grammar[attr]=$('#' + attr + "attr").val(); }); grammarScript=grammarScript + grammar.geom + "()"; geom_attributes.map(function(attr){ if (grammar[attr].length > 0) { grammarScript=grammarScript + "." + attr + "('" + grammar[attr].join("*") + "')"; } }); if (grammar.coord) { grammarScript=grammarScript + ";\n " + chartScriptName + "." + "coord('" + grammar.coord + "');"; } else { rammarScript=grammarScript + ";"; } if ( grammar.faced ) { if ( grammar.faced.length==1 || grammar.faced.length==2 ) { grammarScript=getFacet(grammar.faced, grammarScript); } } console.log(grammarScript) return grammarScript; }
這里有幾點要注意:
這里對于select2的多選,有一個小的提示,在缺省情況下,多選的順序是固定的順序,并不依賴選擇的順序,然而許多圖形語法和字段的順序有關,所以我們使用如下的方法來相應select的選擇事件。
function updateSelect2Order(evt) { let element=evt.params.data.element; let $element=$(element); $element.detach(); $(this).append($element); $(this).trigger("change"); }
這樣做就是每次選中后,把當前選中的項目移到數據最后的位置。
一些例子
好了,下面我們就來看一些例子,了解一下如何使用圖形語法來分析和探索數據。
Iris數據集散點圖
圖形語法:
g2chart.point().position('Sepal.Length*Petal.Length').color('Species').size('Sepal.Width')
Car數據集折線圖
圖形語法:
g2chart.line().position('id*speed');
切換到極坐標:
圖形語法:
g2chart.line().position('id*speed'); g2chart.coord('polar');
Berkeley數據柱狀圖
數據處理:
SELECT SUM(Freq) as f , Gender FROM table GROUP BY Gender
圖形語法:
g2chart.interval().position('Gender*f').color('Gender').label('f');
Berkeley數據堆疊柱狀圖
數據處理:
SELECT SUM(Freq) as f , Gender , Admit FROM table GROUP BY Gender, Admit
圖形語法:
g2chart.intervalStack().position('Gender*f').color('Admit')
Berkeley數據餅圖
數據處理:
SELECT SUM(Freq) as f , Gender FROM table GROUP BY Gender
圖形語法:
g2chart.intervalStack().position('f').color('Gender').label('f'); g2chart.coord('theta')
Berkeley數據分面的應用
圖形語法:
g2chart.facet('rect', { fields: [ 'Dept', 'Admit' ], eachView(view) { view.coord('theta'); view.intervalStack().position('Freq').color('Gender'); } });
更多的分析圖形留給大家去嘗試
本文分享了一個利用純前端技術構建一個類似Tableau的BI應用的例子,整個代碼統計:
總計474 行,用這么少的代碼就能完成一個看上去還不錯的BI工具,還算不錯吧。當然這里主要是由于開源社區提供了這么多好的前端庫以供應用,我要做的僅僅是讓它們有效的工作在一起。這個只能算是個原型,從功能和質量上來說都不成熟,但是能在瀏覽器中不借助任何的服務器來實現BI的數據分析功能,應該會有很多人想要在自己的應用中嵌一個吧?
結合我之前分享的TensorflowJS的文章,下面一步可能是加入預測功能,為數據分析加入智能,前端應用的前景,不可限量!
參考
.先上最后效果圖:
2.代碼跟上,重點在 2):
1)
//服務列表頁面動態加載服務
function ready() {
var url=base_path+"console/cfg/getBaseLayers/"+configId;
$.ajax({
url:url,
type:"get",
dataType:"json",
success:function (result) {
//生成之前先清空tr,防止AJAX異步加載重復生成
$("#lot tr").remove();
var length=result.length;
for (var i=0;i<length;i++){
var name=result[i].name; //服務名稱
var alias=result[i].alias;//服務別名
var type=result[i].type;//服務類型
var year=result[i].year;//年份
var url=result[i].url;//服務地址
var visible=result[i].visible;//是否可見
var id=result[i].id;//id
serviceIdArray[i]=id;//此處將id塞給serviceIdArray,用于判斷是否添加。
var str="";
if (visible==true){
//生成tr
str +='<tr id="';
str +=id;
str +='"';
str +=' class="lot_box"> <td>';
str +=i+1;
str +='</td> <td>';
str +=name;
str +='</td> <td>';
str +=alias;
str +='</td> <td>';
str +=type;
str +='</td> <td>';
str +=year;
str +='</td> <td>';
str +=url;
str +='</td> <td>';
str +='<input id="';
str +=id;
str +='"';
str +='type="checkbox" checked="true" onchange="modifyService(this.id);"/>';
str +='</td> <td> <input id="';
str +=id;
str +='"';
str +='type="button" value="編輯" onclick="editTd(this.id)"/>';
str +='</td> <td>';
str +='<button class="rosy" id="';
str +=id;
str +='"';
str +=' onclick="deleteService(this.id);">';
str +='<img src="static/img/del14.png"></button>';
str +='</td> </tr>';
}else {
//生成tr
str +='<tr id="';
str +=id;
str +='"';
str +=' class="lot_box"> <td>';
str +=i+1;
str +='</td> <td>';
str +=name;
str +='</td> <td>';
str +=alias;
str +='</td> <td>';
str +=type;
str +='</td> <td>';
str +=year;
str +='</td> <td>';
str +=url;
str +='</td> <td>';
str +='<input id="';
str +=id;
str +='"';
str +='type="checkbox" onchange="modifyService(this.id);"/>';
str +='</td> <td> <input id="';
str +=id;
str +='"';
str +='type="button" value="編輯" onclick="editTd(this.id)"/>';
str +='</td> <td>';
str +='<button class="rosy" id="';
str +=id;
str +='"';
str +=' onclick="deleteService(this.id);">';
str +='<img src="static/img/del14.png"></button>';
str +='</td> </tr>';
}
var $tr=$(str);
$("#lot").append($tr);
}
}
});
}
*請認真填寫需求信息,我們會在24小時內與您取得聯系。