今天写个时事题,我们来查询立委议案提案
本日重点
为节省篇幅,本示例仅列出关键程序码,画面编排、按键 onclick、DataLoader、Grid显示...等使用方法,请参考本系列其他文章。
资料来源 : 立法院开放资料服务平台
立法院提供三种资料模式,分别是 csv、txt、xls (虽然立法院开放资料服务平台
上有JSON选项,但并未提供内容)
从网站资讯得知此api提供栏位如下 :
name | 委员姓名 | ename | 委员英文姓名 |
---|---|---|---|
sex | 性别 | party | 党籍 |
partyGroup | 党团 | areaName | 选区名称 |
committee | 委员会 | onboardDate | 到职日(西元年) |
degree | 学历 | tel | 电话 |
experience | 经历 | fax | 传真 |
addr | 通讯处 | picUrl | 照片位址 |
leaveFlag | 是否离职 | leaveDate | 离职日期(西元年) |
leaveReason | 离职原因 |
Legislator
,请开新档 V05__CreateLegislator.sql
create TABLE Legislator(
id bigint auto_increment PRIMARY KEY,
term VARCHAR(3),
name VARCHAR(50) NOT NULL,
sex VARCHAR(10) NOT NULL,
party VARCHAR(100),
partyGroup VARCHAR(100),
areaName VARCHAR(100),
committee VARCHAR(500),
degree VARCHAR(200),
tel VARCHAR(200),
experience VARCHAR(1000),
addr VARCHAR(500),
picUrl VARCHAR(100),
leaveFlag VARCHAR(10),
leaveDate VARCHAR(20),
leaveReason VARCHAR(1000)
);
KEntity
类data class Legislator(
override var id: Long? = null,
var term: String? = null,
var name: String? = null,
var sex: String? = null,
var party: String? = null,
var partyGroup: String? = null,
var areaName: String? = null,
var committee: String? = null,
var degree: String? = null,
var tel: String? = null,
var experience: String? = null,
var addr: String? = null,
var picUrl: String? = null,
var leaveFlag: String? = null,
var leaveDate: String? = null,
var leaveReason: String? = null
):KEntity<Long> ,Serializable{
companion object: Dao<Legislator, Long>(Legislator::class.java)
}
CSV 转成 Bean 的资料转换使用 OpenCSV 套件,请开启 build.gradle.kts
,导入opencsv 套件
implementation("com.opencsv:opencsv:5.5.2")
将下载写在download()
方法,转档後储存到资料表Legislator
fun download(){
URL("https://data.ly.gov.tw/odw/usageFile.action?id=9&type=CSV&fname=9_CSV.csv")
.readText().apply {
CsvToBeanBuilder<Legislator>(StringReader(this))
.withType(Legislator::class.java)
.withSeparator(',')
.withIgnoreLeadingWhiteSpace(true)
.withIgnoreEmptyLine(true)
.build()
.parse().forEach {
it.save()
}
}
}
第2,3行,从立法院开放资料服务平台
取得资料
第4行,使用CsvToBeanBuilder()
方法进行转换,参数型态为Reader
,所以将下载回来的字串进行转换
第5行,parser型态
第6行,分隔符号
第7行,忽略前置空白
第8行,忽略空白行
第11行,因Legislator
为KEntity
类,直接使用save()
方法储存到资料库。
设定 Grid 资料来源为 EntityDataLoader
,首次执行时因资料表 Legislator
尚无任何资料,grid为空白。
grid = grid<Legislator> {
setDataLoader(Legislator.dataLoader)
addColumnFor(Legislator::name).setHeader("委员姓名")
(略)
}
下载立委资料并储存後,更新Grid
download()
grid.dataProvider.refreshAll()
点选grid row 展开detail,希望detail显示相片、所属委员会、服务处地址等资讯。
所属委员会栏位committee
内容如下:
第10届第1会期:经济委员会;第10届第2会期:经济委员会;第10届第3会期:经济委员会;第10届第3会期:纪律委员会;第10届第4会期:经济委员会;
以;
分隔每笔资料,先将资料整理一下再放入List,最後以ListBox
显示
val committees = mutableListOf<String>()
data.committee?.let {
it.split(";").forEach { committees.add(it) }
}
val listBox = ListBox<String>()
listBox.setWidth("35%")
listBox.setItems(committees)
完整内容如下
setSelectionMode(Grid.SelectionMode.NONE)
val renderer = ComponentRenderer<HorizontalLayout, Legislator> { data: Legislator ->
val horizontalLayout = HorizontalLayout()
val image = Image()
with(image){
isExpand = false
width = "10%"
setMaxWidth(200F, Unit.PIXELS)
setMaxHeight(200F, Unit.PIXELS)
if (!data.picUrl.isNullOrEmpty())
src = data.picUrl
}
horizontalLayout.add(image)
val committees = mutableListOf<String>()
data.committee?.let {
it.split(";").forEach { committees.add(it) }
}
val listBox = ListBox<String>()
listBox.setWidth("35%")
listBox.setItems(committees)
horizontalLayout.add(listBox)
val listBox_addr = ListBox<String>()
val addrs = mutableListOf<String>()
data.addr?.let {
it.split(";").forEach { addrs.add(it) }
}
listBox_addr.setWidth("50%")
listBox_addr.setItems(addrs)
horizontalLayout.add(listBox_addr)
horizontalLayout
}
setItemDetailsRenderer(renderer)
上述程序中,ListBox 不同的资料来源,皆须将资料切分後,使用ListBox显示,程序码看起来不仅不亲民、兀长,且未能 reuse。复习一下先前曾学过的 Custom Component,Refactor 一下,将 ListBox 改为 Component。
开新档ListComponent.kt
class ListComponent(data: String, split: String, title: String) : KComposite() {
val list = mutableListOf<String>()
private val root = ui {
data.split(split).forEach {
if (!it.isNullOrEmpty()) list.add(it.trim())
}
verticalLayout {
label(title)
listBox<String> { setItems(list) }
}
}
}
fun HasComponents.listComponent(
data: String,
split: String,
title: String,
block: ListComponent.() -> kotlin.Unit = {}
) = init(ListComponent(data, split, title), block)
传入三个参数,data、split、title
上述兀长的资料转换、加入ListBox()等程序内容,只要改成这样就行了,是不是简单易读多了。
horizontalLayout.add(
ListComponent(data.committee!!, ";", "委员会" )
)
horizontalLayout.add(
ListComponent(data.addr!!, ";", "服务处")
)
提供栏位有
term | 届别 | sessionPeriod | 会期 | sessionTimes | 会次 |
---|---|---|---|---|---|
meetingTimes | 临时会会次 | billNo | 议案编号 | billName | 提案名称 |
billOrg | 提案单位/委员 | billProposer | 提案人(委员或党团) | billCosignatory | 连署人 |
billStatus | 议案状态 | pdfUrl | 关系文书pdf档案下载位置 | docUrl | 关系文书doc档案下载位置 |
selectTerm | 届别期别筛选条件 |
建立资料表 ProposalOfBills
create TABLE ProposalOfBills(
id bigint auto_increment PRIMARY KEY,
billNo VARCHAR(20),
billName VARCHAR(500),
billOrg VARCHAR(50),
billProposer VARCHAR(200),
billCosignatory VARCHAR(1000),
billStatus VARCHAR(10),
pdfUrl VARCHAR(100),
docUrl VARCHAR(100)
);
新增 KEntity
类 ProposalOfBills
data class ProposalOfBills(
override var id: Long? = null,
var billNo: String? = null,
@field:NotNull
var billName: String? = null,
@field:NotNull
var billOrg: String? = null,
var billProposer: String? = null,
var billCosignatory: String? = null,
var billStatus: String? = null,
var pdfUrl: String? = null,
var docUrl: String? = null
): KEntity<Long>, Serializable{
companion object: Dao<ProposalOfBills, Long>(ProposalOfBills::class.java)
}
下载的部份和下载委员资料大同小异,仅列出和上述不同的部份
.withThrowExceptions(false)
从前述资料可知,提供的资料栏位数远大於所需,故只储存部份栏位备用。但是这样一来,parse 过程会因为栏位对应不上而抛出 Exceptions,为免转档因此中断。所以加上这一行让parse可继续进行。
输入委员姓名後要过滤两个栏位,提案人(billProposer
)和连署人(billCosignatory
)
filter = textField {
placeholder = "委员姓名"
addValueChangeListener { event ->
var dp: DataLoader<ProposalOfBills> = ProposalOfBills.dataLoader
if (!filter.value.isBlank())
dp = dp.withFilter {
(ProposalOfBills::billProposer contains filter.value) or
(ProposalOfBills::billCosignatory contains filter.value)
}
grid.setDataLoader(dp)
}
valueChangeMode = ValueChangeMode.EAGER
isExpand = true
}
setVerticalComponentAlignment(FlexComponent.Alignment.START, filter)
第4行,取得EntityDataLoader
第5行,若栏位不为空,加过滤条件
第6行,加条件
第7,8行,我们要找的资料是委员姓名在栏位里任一位置都可以,所以使用 contains
比对
val renderer = ComponentRenderer<HorizontalLayout, ProposalOfBills> { data: ProposalOfBills ->
val horizontalLayout = HorizontalLayout()
horizontalLayout.add(
ListComponent(data.billProposer!!, " ", "提案人(委员或党团)")
)
horizontalLayout.add(
ListComponent(data.billCosignatory!!, " ", "连署人")
)
horizontalLayout
}
方才写好的 ListComponent
又派上用场了
>>: 30-23 之 Patterns of Enterprise Application Architecture 小总结
接着进行AWS RDS的实作, 以及与地端的基本差异. 在SSMS上, 资料库按右键建立 [New ...
所以我说...程序是虾饺? 程序是可以直接在电脑上执行,以完成某个目的或任务的一连串指令 换句话说,...
其实原本不是要叫这个名字的。原本要叫《官网没教你的「如何把 Vue 写好」》但是太狂了,竟然敢代替官...
前言 今天利用之前所建的主页, 建立一个可让使用者互动的原型。 预览 利用Figma的预览功能, 即...
哈罗~ 今天要来跟大家介绍扫描的小工具 在介绍工具前,我们先来review一下TCP Flags。 ...