【微信小程序】 侧边栏菜单查询
原因
开发的项目在WX小程序上有个新需求
就是在用户[我的]界面里的菜单中多加一个[我的服务]
之前有提及过,服务消息被按8个消息类型拆成了8张表
对应,在小程序界面这里也应该放上对应8个菜单,按菜单去查询数据
之前的设想
在原先的设想是不拆分菜单去查询,8张表的数据使用UNIONALL合并成一张表,带给小程序
这个设想被组长直接否决,原因是预测数据量过多,影响DB性能
第二天组长和产品核对之后就认可组长的设想,要拆掉查询
但是产品就和我想的是一样的,在用户眼里就应该是一个普通列表,一列刷过去看就行了
怕产品反水又要改回去,我觉得花了一下午想出来的合并SQL有保留的价值
这里我就放上我的SQL
1、按照用户ID筛选
2、按照商家ID筛选,用户可能去A店,B店住过,在A店就只展示A店的消费记录,不可能在A店还能看B店的消费记录
3、按照用户自己提交的服务记录的创建时间降序排序,最新的服务记录放在前面
4、服务类型是一个字典,每个商家的服务类型是区分的,所以这里要子查询翻译
5、每个类型还附带了具体信息,各不一样,用JSON_OBJECT统一字段带出来
SELECT * FROM (
-- 清扫服务表
SELECT
(SELECT DMMC FROM `aisw_merchant_dict` AS DICT WHERE DICT.MERCHANT_ID = 4 AND DICT.DMBH = SERVICE_TYPE) AS `serviceName`,
JSON_OBJECT( 'cleanDate', CLEAN_DATE) AS `serviceInfo`,
ID ,
ROOM_NO ,
MERCHANT_ID ,
SERVICE_TYPE ,
USER_ID AS `SRV_USER`,
ACCEPT_STATUS ,
REMARKS ,
CREATE_DATE
FROM `aisw_e_service_clean_msg`
WHERE MERCHANT_ID = 4 AND USER_ID = 1 UNION ALL
-- 配送服务表
SELECT
(SELECT DMMC FROM `aisw_merchant_dict` AS DICT WHERE DICT.MERCHANT_ID = 4 AND DICT.DMBH = SERVICE_TYPE) AS `serviceName`,
JSON_OBJECT( 'dryTypes', DELIVERY_TYPE, 'dryNums', DELIVERY_NUMS) AS `serviceInfo`,
ID ,
ROOM_NO ,
MERCHANT_ID ,
SERVICE_TYPE ,
USER_ID AS `SRV_USER`,
ACCEPT_STATUS ,
REMARKS ,
CREATE_DATE
FROM `aisw_e_service_delivery_msg`
WHERE MERCHANT_ID = 4 AND USER_ID = 1 UNION ALL
-- 故障报修表
SELECT
(SELECT DMMC FROM `aisw_merchant_dict` AS DICT WHERE DICT.MERCHANT_ID = 4 AND DICT.DMBH = SERVICE_TYPE) AS `serviceName`,
JSON_OBJECT( 'faultTypes', FAULT_TYPE) AS `serviceInfo`,
ID ,
ROOM_NO ,
MERCHANT_ID ,
SERVICE_TYPE ,
USER_ID AS `SRV_USER`,
ACCEPT_STATUS ,
REMARKS ,
CREATE_DATE
FROM `aisw_e_service_fault_msg`
WHERE MERCHANT_ID = 4 AND USER_ID = 1 UNION ALL
-- 退房表
SELECT
(SELECT DMMC FROM `aisw_merchant_dict` AS DICT WHERE DICT.MERCHANT_ID = 4 AND DICT.DMBH = SERVICE_TYPE) AS `serviceName`,
NULL AS `serviceInfo`,
ID ,
ROOM_NO ,
MERCHANT_ID ,
SERVICE_TYPE ,
USER_ID,
ACCEPT_STATUS ,
REMARKS ,
CREATE_DATE
FROM `aisw_e_service_one_checkout_msg`
WHERE MERCHANT_ID = 4 AND USER_ID = 1 UNION ALL
-- 续住表
SELECT
(SELECT DMMC FROM `aisw_merchant_dict` AS DICT WHERE DICT.MERCHANT_ID = 4 AND DICT.DMBH = SERVICE_TYPE) AS `serviceName`,
JSON_OBJECT( 'renewalType', RENEWAL_TYPE, 'renewalDay', RENEWAL_DAY) AS `serviceInfo`,
ID ,
ROOM_NO ,
MERCHANT_ID ,
SERVICE_TYPE ,
USER_ID AS `SRV_USER`,
ACCEPT_STATUS ,
REMARKS ,
CREATE_DATE
FROM `aisw_e_service_renewal_msg`
WHERE MERCHANT_ID = 4 AND USER_ID = 1 UNION ALL
-- 叫醒服务表
SELECT
(SELECT DMMC FROM `aisw_merchant_dict` AS DICT WHERE DICT.MERCHANT_ID = 4 AND DICT.DMBH = SERVICE_TYPE) AS `serviceName`,
JSON_OBJECT( 'isKnock', IS_KNOCK, 'wakePhone', WAKE_PHONE, 'wakeDate', WAKE_DATE) AS `serviceInfo`,
ID ,
ROOM_NO ,
MERCHANT_ID ,
SERVICE_TYPE ,
USER_ID AS `SRV_USER`,
ACCEPT_STATUS ,
REMARKS ,
CREATE_DATE
FROM `aisw_e_service_wake_msg`
WHERE MERCHANT_ID = 4 AND USER_ID = 1
) AS FIANL_TABLE
WHERE CREATE_DATE >= CONVERT(DATE_SUB(NOW(), INTERVAL 1 MONTH), DATE)
ORDER BY CREATE_DATE DESC
结果预览:
+-------------+---------------------------------------+-----+---------+-------------+--------------+----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+
| serviceName | serviceInfo | ID | ROOM_NO | MERCHANT_ID | SERVICE_TYPE | SRV_USER | ACCEPT_STATUS | REMARKS | CREATE_DATE |
+-------------+---------------------------------------+-----+---------+-------------+--------------+----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+
| 清扫服务 | {"cleanDate": "立即清扫"} | 151 | 701 | 4 | 360000002 | 1 | 1 | NULL | 2022-06-20 14:30:50 |
| 一键退房 | NULL | 219 | 701 | 4 | 360000004 | 1 | 1 | NULL | 2022-06-20 14:30:34 |
| 一键退房 | NULL | 203 | 705 | 4 | 360000004 | 1 | 0 | NULL | 2022-06-15 14:27:20 |
| 清扫服务 | {"cleanDate": "立即清扫"} | 122 | 705 | 4 | 360000002 | 1 | 0 | NULL | 2022-06-15 14:25:19 |
| 一键退房 | NULL | 202 | 705 | 4 | 360000004 | 1 | 0 | NULL | 2022-06-15 14:25:02 |
| 一键退房 | NULL | 201 | 705 | 4 | 360000004 | 1 | 0 | NULL | 2022-06-15 14:24:27 |
| 一键退房 | NULL | 200 | 705 | 4 | 360000004 | 1 | 0 | NULL | 2022-06-15 14:23:46 |
| 一键退房 | NULL | 199 | 705 | 4 | 360000004 | 1 | 0 | NULL | 2022-06-14 13:55:01 |
| 在线续住 | {"renewalDay": 2, "renewalType": 1} | 43 | 705 | 4 | 360000001 | 1 | 1 | 条件啦咯啦咯啦咯考虑兔兔图突突突图兔兔他图他图吐她她她条件啦咯啦咯啦咯考虑兔兔图突突突图兔兔他图他图吐她她她条件啦咯啦咯啦咯考虑兔兔图突突突图兔兔他图他图吐她她她条件啦咯啦咯啦咯考虑兔兔图突突突图兔兔他图他图吐她她她条件啦咯啦咯啦咯考虑兔兔图突突突图兔兔他图他图吐她她她条件啦咯啦咯啦咯考虑兔兔图突突突图兔兔他图他图吐她她她 | 2022-06-13 15:38:09 |
| 故障报修 | {"faultTypes": "362000003,362000006"} | 46 | 705 | 4 | 360000007 | 1 | 1 | NULL | 2022-06-13 15:37:48 |
+-------------+---------------------------------------+-----+---------+-------------+--------------+----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+
10 rows in set (0.16 sec)
原型效果
产品这边从不给原型图,老开局一张嘴了
组长提醒我用饿了么的小程序或者APP那个就是
我截取了一张PO出来,左边是一些菜单分类,右边就是菜单项,这样
参考资料
https://blog.csdn.net/weixin_41599291/article/details/93650074
百度了好几篇文章,这篇是最符合的,能直接CV看预览效果
但是博主用的是swiper标签,如果要做翻页查询需要有上拉翻页,下拉刷新两个事件监听的钩子
这个标签是用不了的,所以后面又在WX小程序开发文档里面翻组件,用Scroll-View是符合的
Scroll-View文档地址
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html
1、要给Scroll-View设置顶宽,要直接拉满整个屏高 height: 100vh 样式属性即可
2、设置滚动方向是Y轴,scroll-y="true"
3、关于下拉刷新的相关属性
refresher-enabled boolean false 否 开启自定义下拉刷新
refresher-threshold number 45 否 设置自定义下拉刷新阈值
refresher-default-style string "black" 否 设置自定义下拉刷新默认样式,支持设置 black | white | none, none 表示不使用默认样式
refresher-triggered boolean false 否 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
bindrefresherrefresh eventhandle 否 自定义下拉刷新被触发
4、上拉翻页,只有这个属性是匹配的
bindscrolltolower eventhandle 否 滚动到底部/右边时触发
开发效果
WXML代码部分:
<!--pages/user/my-service/my-service.wxml--> <view class='productNav' wx:if="{{ phoneNum.length > 0 }}">
<!-- 左侧 -->
<view class='left'> <view
wx:if="{{menuList.length > 0}}"
wx:for="{{menuList}}"
wx:key="key"
wx:for-index="index"
wx:for-item="item"
data-id="{{item.index}}"
data-dmbh="{{item.dmbh}}"
class="{{active== item.index ?'selected':'normal'}}"
bindtap='switchNav'>
{{item.dmmc}}
</view>
</view> <!-- 右侧 --> <!--
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html refresher-enabled boolean false 否 开启自定义下拉刷新
refresher-triggered 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
refresher-threshold number 45 否 设置自定义下拉刷新阈值 -->
<scroll-view
class="right"
scroll-y="true"
scroll-x="false" refresher-enabled="true"
refresher-threshold="{{40}}"
refresher-triggered="{{refreshFlag}}" bindrefresherrefresh="refresh"
bindscrolltolower="toLowerPaging"> <!-- 一键退房 -->
<view wx-if="{{ currentTab == '360000004' && ctxList.length > 0 }}">
<view wx:for="{{ctxList}}" wx:key="idx" wx:for-item="item" class="item" >
<view>房间号:{{ item.roomNo }}</view>
<view>创建时间:{{ item.createDate }}</view>
</view>
</view> <!-- 清扫服务 -->
<view wx-if="{{ currentTab == '360000002' && ctxList.length > 0 }}">
<view wx:for="{{ctxList}}" wx:key="idx" wx:for-item="item" class="item" >
<view>房间号:{{ item.roomNo }}</view>
<view>清扫时间:{{ item.cleanDate }}</view>
<view>创建时间:{{ item.createDate }}</view>
</view>
</view> <!-- 物品配送 -->
<view wx-if="{{ currentTab == '360000000' && ctxList.length > 0 }}">
<view wx:for="{{ctxList}}" wx:key="idx" wx:for-item="item" class="item" >
<view>房间号:{{ item.roomNo }}</view>
<view>物品:{{ item.translate }}</view>
<view>创建时间:{{ item.createDate }}</view>
</view>
</view> <!-- 叫醒服务 -->
<view wx-if="{{ currentTab == '360000003' && ctxList.length > 0 }}">
<view wx:for="{{ctxList}}" wx:key="idx" wx:for-item="item" class="item" >
<view>房间号:{{ item.roomNo }}</view>
<view>是否敲门叫醒:{{ item.isKnock == 1 ? '是' : '否' }} </view>
<view>叫醒手机: {{ tools.phoneHide(item.wakePhone) }}</view>
<view>叫醒时间:{{ item.wakeDate }}</view>
<view>创建时间:{{ item.createDate }}</view>
</view>
</view> <!-- 在线续住 -->
<view wx-if="{{ currentTab == '360000001' && ctxList.length > 0 }}">
<view wx:for="{{ctxList}}" wx:key="idx" wx:for-item="item" class="item" >
<view>房间号:{{ item.roomNo }}</view>
<view>续住类型:{{ item.renewalType == 1 ? '续住此房间' : '换房续住' }}</view>
<view>续住天数:{{ item.renewalDay }}</view>
<view>创建时间:{{ item.createDate }}</view>
</view>
</view> <!-- 故障报修 -->
<view wx-if="{{ currentTab == '360000007' }}">
<view wx:for="{{ctxList}}" wx:key="idx" wx:for-item="item" class="item" >
<view>房间号:{{ item.roomNo }}</view>
<view>报修家具: {{ item.translate }}</view>
<view>创建时间:{{ item.createDate }}</view>
</view>
</view> <!-- 空数据 -->
<view wx:if="{{ ctxList.length == 0 }}" class="no-data">
暂无数据
</view>
</scroll-view>
</view> <view wx:else>
<nonelogin prop-title="{{title}}" prop-router="{{router}}" id="nonelogin" ></nonelogin>
</view> <wxs src="./../../../utils/tools.wxs" module="tools" ></wxs>
JS代码部分:
const APP = getApp()
const { wxRequest, checkIsOneLogin } = require('../../../utils/util.js'); Page({
data: {
userId:'',
refreshFlag:true,
active: 0,
currentTab: '',
menuList: [],
pageIndex: 1,
pageSize: 10,
total: 0,
ctxList: [],
merchantId: null,
placeCode: null,
phoneNum: '',
title: '我的服务',
router: 'home'
},
onLoad(options) {
let that = this
wx.setNavigationBarTitle({ title: that.data.title })
// 校验用户是否登录
let phoneNum = wx.getStorageSync('aisw_phoneNum');
if (checkIsOneLogin(phoneNum, that.data.router)) {
that.setData({ userId:wx.getStorageSync('aisw_user_id'), phoneNum: phoneNum })
that.getAvaliableMenu()
}
},
onReady() {
let phoneNum = wx.getStorageSync('aisw_phoneNum');
if (phoneNum === '') {
this.nonelogin = this.selectComponent("#nonelogin");
this.nonelogin.onload()
}
}, switchNav(e) {
const PAGE = this;
const id = e.currentTarget.dataset.id;
const type = e.currentTarget.dataset.dmbh if (PAGE.data.currentTab == id) return false
else PAGE.setData({ currentTab: type })
PAGE.setData({ active: id }) // 重置结果集列表
PAGE.setData({
ctxList: [],
pageIndex: 1,
total: 0
}) // 查询
PAGE.getMyServiceHistory()
}, // 查询可用的服务
getAvaliableMenu() {
let that = this
const DMLB = APP.globalData.dictServiceType
wxRequest({
url: `authVerify/queryMerchantDictList/${DMLB}`,
data: {
jsonParam: { 'unionId': APP.globalData.unionId }
},
method: 'GET',
}).then(res => { if (res.code !== 200 || !res.data) return console.log(`获取可用的服务菜单成功 -> ${JSON.stringify(res.data)}`) // 过滤拿到不是[WIFI扫码和预开发票]的其它服务
let menus = res.data.filter(menu => !['360000006', '360000005'].includes(menu.dmbh)) // 如果过滤之后没有数据,直接结束
if (0 == menus.length) return // 取出商家ID 和 场所代码
that.setData({
merchantId: menus[0].id,
placeCode: menus[0].placeCode
}) that.setData({ menuList: menus, currentTab: menus[0].dmbh }) that.getMyServiceHistory()
}).catch(e => {
wx.showToast({
title: e.message,
duration: 2000,
icon: 'none'
})
})
}, // 查询服务历史列表
getMyServiceHistory() {
const PAGE = this
wx.showLoading({ title: '数据加载中' })
wxRequest({
url: 'service/serviceHistory',
method: 'POST',
data: {
jsonParam: {
merchantId: PAGE.data.merchantId, // 当前商家
acceptStatus: 1, // 只查询已受理
serviceType: PAGE.data.currentTab, // 服务类型
unionId: APP.globalData.unionId,
userId: PAGE.data.userId, // 当前用户
page: {
current: PAGE.data.pageIndex,
size: PAGE.data.pageSize
}
}
}
}).then(res => {
// 关闭加载提示
wx.hideLoading()
if (res.code !== 200 || !res.data) {
PAGE.setData({ ctxList: [] })
return
} PAGE.setData({
ctxList: [ ...PAGE.data.ctxList, ...res.data ],
total: res.total
}); }).catch(e => {
// 关闭加载提示
wx.hideLoading()
that.setData({ ctxList: [] })
}) }, refresh(){
let that = this
// 调接口
that.setData({ pageIndex: 1, ctxList: [] })
that.getMyServiceHistory()
that.setData({ refreshFlag: false })
console.log("触发下拉刷新")
},
toLowerPaging() {
let that = this // 获取已经翻页得到的记录数量
const rowCount = that.data.pageIndex * that.data.pageSize
if (rowCount >= that.data.total) {
wx.showToast({
icon: 'none', // 不设置任何图标
title: '已经到底了', // 提示文本
duration: 3000, // 持续3秒
})
return
}
that.setData({ pageIndex: that.data.pageIndex + 1 })
that.getMyServiceHistory()
console.log("触发上拉翻页")
} })
WXSS 样式代码部分:
/* pages/user/my-service/my-service.wxss */ .productNav {
display: flex;
flex-direction: row;
font-family: "Microsoft YaHei";
position: relative;
}
.left {
width: 25%;
font-size: 30rpx;
background-color: #f4f4f4;
}
.left view {
text-align: center;
height: 90rpx;
line-height: 90rpx;
}
.selected {
background-color: #fff;
border-left: 2px solid #45CF8A;
font-weight: bold;
color: #45CF8A;
}
.normal {
background-color: #f4f4f4;
border-bottom: 1px solid #f2f2f2;
}
.right {
width:75%;
height: 100vh;
margin: 0;
} .right .item {
/* background-color: sandybrown; */
font-size: 24rpx; border: 1rpx solid #efefef;
border-radius: 15rpx; margin: 15rpx;
padding: 15rpx; /* 盒子阴影 */
box-shadow: 1rpx 1rpx 15rpx #ddd;
} /* 无数据提示 */
.no-data {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
注意点分析
1、菜单的内容和数量并不是固定写死的
2、菜单数据需要有index排序,页面加载时执行第一个菜单项的数据查询
3、WX小程序请求并不是同步执行的,数据加载顺序需要注意(如果不同步,需要套在异步回调里面写)
4、每次切换点击,注意参数的切换
【微信小程序】 侧边栏菜单查询的更多相关文章
- 微信小程序-全国快递查询
微信小程序-全国快递查询 摘要:WeChat.小程序.JS 开发过程 源码下载 1. GitHub 2. 百度云 链接:https://pan.baidu.com/s/1XVbtT2JsZslg4Y0 ...
- 微信小程序之媒体查询@media
微信小程序支持媒体查询,代码如下: @media screen and (min-width: 700px) { .container { padding: 30rpx; } } @media scr ...
- 微信小程序-自定义菜单导航(实现楼梯效果)
设计初衷 在开发页面时,往往需要实现,点击页面的导航菜单页面滚动到相应位置,滚动页面实现菜单选项的高亮.在html开发中,我们可以用到a标签锚点实现,jq的动画相结合实现类似效果.在框架中vant U ...
- 微信小程序分类菜单激活状态跟随列表滚动自动切换
这里主要用到微信小程序提供的SelectorQuery获取页面节点信息实现,组件用的是微信小程序的scroll-view 逻辑就是获取右侧盒子的节点信息,获取右侧子分类的节点信息,当子分类滑动到顶部的 ...
- 微信小程序侧边栏滑动特效(左右滑动)
侧边栏滑动是很常见的功能,但是小程序出来不久,很多特效还没有成熟案例,只能原生重写,所以今天为大家带来4个漂亮的侧边栏特效~~ 侧边栏特效一 先看效果: wxml: <!--page/one/i ...
- 微信小程序——自定义菜单切换栏tabbar组件
效果图: wxml代码: <view class="top_tabbar" > <block wx:for="{{itemName}}" wx ...
- 微信小程序踩坑集合
1:官方工具:https://mp.weixin.qq.com/debug/w ... tml?t=1476434678461 2:简易教程:https://mp.weixin.qq.com/debu ...
- 微信小程序 教程及示例
作者:初雪链接:https://www.zhihu.com/question/50907897/answer/128494332来源:知乎著作权归作者所有,转载请联系作者获得授权.微信小程序正式公测, ...
- 微信小程序资料集合
一:官方地址集合: 1:官方工具:https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=1476434678461 2: ...
- 近期热门微信小程序demo源码下载汇总
近期微信小程序demo源码下载汇总,乃小程序学习分析必备素材!点击标题即可下载: 即速应用首发!原创!电商商场Demo 优质微信小程序推荐 -秀人美女图 图片下载.滑动翻页 微信小程序 - 新词 GE ...
随机推荐
- H5图片预览
官方链接下载示例项目需要注册账号,似乎有点不友好,不想注册账号的可以去gitee上下载示例项目 如果你上来就是把previewImg.js 放在head中可能会出现意想不到的错误,比如下面这样子,遇到 ...
- C语言-使用malloc导致的奔溃问题
在使用malloc.memset.free的过程中,出现了程序奔溃,大致现象如下. 程序的实现大致如下: #include <stdio.h> #include <stdlib.h& ...
- 国产大模型参加高考,同写2024年高考作文,及格分(通义千问、Kimi、智谱清言、Gemini Advanced、Claude-3-Sonnet、GPT-4o)
大家好,我是章北海 今天高考,上午的语文结束,市面上又要来一场大模型参考的文章了. 我也凑凑热闹,让通义千问.Kimi.智谱清言一起来写一下高考作文. 公平起见,不加任何其他prompt,直接把题目甩 ...
- Unity3D 内存管理非代码技巧
在场景管理器新建 gameobjct 使用代码在类初始化时 NEW 普肉fai包(包)然后将相同的类NEW够挂载到 gameobjct子节点上 在操控列表中类的时候用for循环遍历操作移动还是怎么样( ...
- pyenv-win 替换国内镜像源
前情提要 今天心血来潮想学一学python 然后因为python版本众多,了解到了pyenv这个python版本管理器 在github下载好pyenv以后,打算先安装一个稳定的版本 pyenv ins ...
- Kotlin 数据类型详解:数字、字符、布尔值与类型转换指南
Kotlin 数据类型 在 Kotlin 中,变量的类型由其值决定: 示例 val myNum = 5 // Int val myDoubleNum = 5.99 // Double val myLe ...
- python logging日志没有写入到指定文件,写到其他项目的日志文件
背景: 项目A为主框架项目,使用到了项目B的方法 项目A.B均有封装好的日志方法,且均在封装好的日志文件里面,增加了logger = MyLogger().info,其他文件要使用日志时,引入logg ...
- 【换源】git命令行迁移仓库
直接git clone的话,查看本地分支,会只有默认主分支,可能是master,也可以能是设置的. 查看所有分支 git branch -a * master remotes/origin/HEAD ...
- sqoop导入导出到mysql,hbase,hive,hdfs系统,多表关联导出案例
Sqoop工具1.协助 RDBMS 与 Hadoop 之间进行高效的大数据交流.把关系型数据库的数据导入到 Hadoop 与其相关的系统 (如HBase和Hive)同时也可以把数据从 Hadoop 系 ...
- Java报表开发工具总结
Java报表工具,首先可以分成两大类:纯Java报表工具,和支持Java的报表工具. 支持Java的报表工具 支持Java的报表工具.其实就是非Java的报表工具,但是可以在Java程序中调用,这样的 ...