HistoryTable.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <template>
  2. <div class="history-table">
  3. <BasicTable ref="historyTable" @register="register" :data-source="data" :scroll="scroll" @change="search">
  4. <template #bodyCell="{ column, record }">
  5. <a-tag v-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == '0' ? 'green' : 'red'">
  6. {{ record.warnFlag == '0' ? '正常' : '报警' }}
  7. </a-tag>
  8. <a-tag v-if="column.dataIndex === 'netStatus'" :color="record.netStatus == '0' ? '#f00' : 'green'">
  9. {{ record.netStatus == '0' ? '断开' : '连接' }}
  10. </a-tag>
  11. </template>
  12. <template #form-submitBefore>
  13. <a-button type="primary" preIcon="ant-design:search-outlined" @click="search">查询</a-button>
  14. <a-button type="primary" preIcon="ant-design:export-outlined" @click="exportXls"> 导出</a-button>
  15. </template>
  16. </BasicTable>
  17. </div>
  18. </template>
  19. <script lang="ts" setup>
  20. // 场景类历史数据公共组件!
  21. // 用于服务带子设备的设备历史数据业务,这类数据通常由一条数据返回多个子设备的信息,例如:{ forcFan1Temp, forcFan2Temp };
  22. // 而此组件可以将这些数据分类,例如:表格只有 温度 一列,但可以根据所选的子设备展示子设备1的数据:forcFan1Temp;
  23. // 综上所述,此组件在基础的历史数据组件上添加了子设备下拉框(由字典驱动),用户选择子设备后表头将动态调整以适配数据展示;
  24. //
  25. // 使用方法如下:
  26. // 设有历史数据2条,[{ name, forcFan1Temp, forcFan2Temp }, { name, forcFan1Temp, forcFan2Temp }]。
  27. //
  28. // 1、配置设备字段(参考公司端综合设备管理-设备字段管理)
  29. // 以压风机为例,设压风机设备的历史数据编码为forcFan_history。
  30. // 那么需要把所有字段悉数配置,例子如下:
  31. // 显示字段 字段code
  32. // 温度 forcFanTemp
  33. // 安装位置 name
  34. //
  35. // 2、配置数据字典(参考系统管理-数据字典),为上述设备配置子设备
  36. // 同以压风机为例,设压风机子设备字典编码为forcFan_dict,且已经新增到系统中。
  37. // 则字典配置的例子如下:
  38. // 名称 数据值
  39. // 压风机1 forcFan1
  40. // 压风机2 forcFan2
  41. //
  42. // 3、运维人员应配合前端开发人员,使用指定的编码配置内容。
  43. // 同以压风机为例,需使用device-code(forcFan)、dict-code(forcFan_dict)。
  44. //
  45. // 4、其他内容说明
  46. // 同以压风机为例,当子设备没有数据时,不进行过滤,此时展示的数据是:
  47. // 温度 安装位置
  48. // 取forcFanTemp 取name
  49. // 同以压风机为例,当子设备选择压风机1时,过滤压风机1相关的表头,此时展示的数据是:
  50. // 温度 安装位置
  51. // 取forcFan1Temp 取name
  52. // 当设备字段不含数据字典关键词(forcFan)时不做处理,当设备字段包含关键词但已指定编号(即字段包含数字)时不做处理
  53. //
  54. // 5、历史数据模式选择
  55. // 历史数据的请求分为redis分站模式、其他模式,redis分站模式将不支持子设备选择,参考基础的历史数据组件逻辑(本组件已做兼容)
  56. // 历史数据查询模式分为多设备查询模式、单设备查询模式,单设备查询模式将不支持设备多选,两种模式本组件都支持
  57. import { computed, onMounted, ref, shallowRef } from 'vue';
  58. import { BasicColumn, PaginationProps, BasicTable } from '/@/components/Table';
  59. import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
  60. import { getDefaultSchemas } from './history.data';
  61. import { adaptFormData, getDeviceList, getExportUrl, list } from './history.api';
  62. import { useListPage } from '/@/hooks/system/useListPage';
  63. import { initDictOptions } from '/@/utils/dict';
  64. const props = withDefaults(
  65. defineProps<{
  66. /** 表格项配置,默认由deviceCode获取且联动dictCode,可以覆写,覆写后将不支持联动,参考BaiscTable */
  67. // columns?: BasicColumn[];
  68. /** 表格操作项配置,默认为空,可以覆写 */
  69. // actionColumns?: BasicColumn;
  70. /** 查询表单项配置,默认联动dictCode,可以覆写,覆写后将不支持联动,提供formProps时此项无效,参考BaiscTable */
  71. // schemas?: FormSchema[];
  72. /** 表格分页配置,可以覆写,参考BaiscTable */
  73. pagination?: PaginationProps;
  74. /** 设备编码,该编码用于请求设备信息,示例:forcFan */
  75. deviceCode: string;
  76. /** 字典编码,该编码用于从字典配置中读出设备项,示例:forcFan_dict */
  77. dictCode: string;
  78. /** 字段编码,该编码用于从设备字段配置中读取默认表头信息,示例:forcFan_history */
  79. columnsCode: string;
  80. scroll: { x: number | true; y: number };
  81. /** 表格配置,参考BaiscTable,该值会与默认的配置进行浅合并,这里提供的任何配置都是优先的 */
  82. // tableProps?: BasicTableProps;
  83. /** 查询表单配置,参考BaiscTable */
  84. // formProps?: FormProps;
  85. }>(),
  86. {
  87. deviceCode: '',
  88. dictCode: '',
  89. pagination: (): PaginationProps => ({
  90. current: 1,
  91. pageSize: 10,
  92. pageSizeOptions: ['10', '30', '50', '100'],
  93. showQuickJumper: false,
  94. }),
  95. }
  96. );
  97. // 未经过处理的原始表头,即项目配置的表头
  98. let originColumns: BasicColumn[] = [];
  99. // 表格数据
  100. const data = shallowRef([]);
  101. const { tableContext, onExportXls, onExportXlsPost } = useListPage({
  102. tableProps: {
  103. columns: [
  104. {
  105. align: 'center',
  106. dataIndex: 'strinstallpos',
  107. defaultHidden: false,
  108. title: '安装位置',
  109. width: 80,
  110. },
  111. ],
  112. formConfig: {
  113. labelAlign: 'left',
  114. labelWidth: 80,
  115. showAdvancedButton: false,
  116. showSubmitButton: false,
  117. showResetButton: false,
  118. actionColOptions: {
  119. xxl: 4,
  120. },
  121. },
  122. canResize: true,
  123. showTableSetting: false,
  124. showActionColumn: false,
  125. bordered: false,
  126. size: 'small',
  127. showIndexColumn: true,
  128. tableLayout: 'auto',
  129. scroll: computed(() => {
  130. return { ...props.scroll, y: props.scroll.y - 100 };
  131. }),
  132. pagination: props.pagination,
  133. },
  134. exportConfig: {
  135. name: '设备历史列表',
  136. url: () => getExportUrl(deviceInfo.value),
  137. },
  138. });
  139. const [register, { getForm, setLoading, getPaginationRef, setPagination, setColumns }] = tableContext;
  140. // 已选中的设备信息
  141. const deviceInfo = ref<Record<string, unknown>>({});
  142. // 设备下拉框选项
  143. const deviceOptions = ref<Record<string, unknown>[]>([]);
  144. // 子设备下拉框选项
  145. const dictOptions = ref<Record<string, unknown>[]>([]);
  146. /**
  147. * 获取设备信息列表、子设备字典,初始化设备可选项、子设备可选项并选中首个设备
  148. */
  149. async function fetchDevice() {
  150. const results = await getDeviceList({ devicetype: props.deviceCode, pageSize: 10000 });
  151. const dicts = await initDictOptions(props.dictCode);
  152. const options = results.map((item) => {
  153. return {
  154. label: item.strinstallpos,
  155. value: item.id || item.deviceID,
  156. deviceType: item.strtype || item.deviceType,
  157. devicekind: item.devicekind,
  158. stationtype: item.stationtype,
  159. };
  160. });
  161. deviceOptions.value = options;
  162. dictOptions.value = dicts;
  163. onDeviceChangeCallback(null, options[0]);
  164. }
  165. /**
  166. * 选择任意设备后的回调,根据设备信息刷新表格、表头及其数据
  167. */
  168. function onDeviceChangeCallback(__, option) {
  169. // 生成所有需要查询的表头编码,例如 forcFan_auto 对应 forcFan_auto_history、forcFan_history 这两个
  170. const codes: string[] = [];
  171. deviceInfo.value = option;
  172. if (deviceInfo.value && deviceInfo.value.deviceType) {
  173. const arr = (deviceInfo.value.deviceType as string).split('_');
  174. while (arr.length) {
  175. codes.push(arr.join('_').concat('_history'));
  176. arr.pop();
  177. }
  178. }
  179. // 如此,例如deviceType为forcFan_auto, 则查询表头按 forcFan_auto_history、forcFan_history、columnsCode 为顺序
  180. initTable(codes.concat(props.columnsCode));
  181. search();
  182. }
  183. /**
  184. * 初始化表格,该方法将根据参数设定新的表头、表单。
  185. *
  186. * 需要有设备信息之后再初始化表格。
  187. *
  188. * @param deviceCodes 获取表头所用的编码,从左到右依次尝试,直到找到第一个有表头信息的为止
  189. */
  190. function initTable(deviceCodes: string[]) {
  191. const defaultSchemas = getDefaultSchemas(dictOptions.value, deviceOptions.value, onDeviceChangeCallback);
  192. for (const code of deviceCodes) {
  193. const cols = getTableHeaderColumns(code);
  194. if (cols.length) {
  195. originColumns = cols;
  196. break;
  197. }
  198. }
  199. getForm().setProps({
  200. schemas: defaultSchemas,
  201. });
  202. }
  203. /**
  204. * 搜索,核心方法
  205. *
  206. * 该方法获取表单、处理表单数据后尝试获取数据并设置表头
  207. */
  208. async function search() {
  209. if (!deviceInfo.value) return;
  210. const form = getForm();
  211. await form.validate();
  212. const formData = form.getFieldsValue();
  213. setLoading(true);
  214. const pagination = getPaginationRef() as PaginationProps;
  215. const deviceCode = (deviceInfo.value.deviceType || props.deviceCode.concat('*')) as string;
  216. const { records, total, current } = await list(deviceCode, deviceInfo.value, formData, pagination).finally(() => {
  217. setLoading(false);
  218. });
  219. setPagination({
  220. current,
  221. total,
  222. });
  223. records.forEach((item) => {
  224. Object.assign(item, item.readData);
  225. });
  226. data.value = records;
  227. updateColumns(formData.deviceNum);
  228. }
  229. /**
  230. * 更新表头,表头默认情况下需要和子设备联动
  231. * @param prefix 子设备的值,即为表头取数据时字段的前缀
  232. */
  233. function updateColumns(prefix?: string) {
  234. if (!prefix) return setColumns(originColumns);
  235. // 如果有子设备信息,筛选符合规范的表头
  236. const cols = originColumns.map((col) => {
  237. const dataIndex = col.dataIndex as string;
  238. // 获取到子设备编码的前缀及编码,正则例子:forcFan1 => [forcFan1, forcFan, 1]
  239. const [_, pfx] = prefix.match(/([A-Za-z]+)([0-9]+)/) || [];
  240. // 同时,如若已经在前缀后配置了编号则不要更改
  241. const reg = new RegExp(`${pfx}[0-9]`);
  242. if (dataIndex.search(reg) !== -1) return col;
  243. if (dataIndex.includes(pfx)) {
  244. return {
  245. ...col,
  246. dataIndex: dataIndex.replace(pfx, prefix),
  247. };
  248. }
  249. // 默认直接放行
  250. return col;
  251. });
  252. setColumns(cols);
  253. }
  254. /** 导出表格内容为excel */
  255. async function exportXls() {
  256. const form = getForm();
  257. await form.validate();
  258. const formData = form.getFieldsValue();
  259. const pagination = getPaginationRef() as PaginationProps;
  260. const deviceCode = (deviceInfo.value.deviceType || props.deviceCode.concat('*')) as string;
  261. const params = adaptFormData(deviceCode, deviceInfo.value, formData, pagination);
  262. if (deviceInfo.value.stationtype === 'redis') {
  263. return onExportXlsPost(params);
  264. } else {
  265. return onExportXls(params);
  266. }
  267. }
  268. // watch(
  269. // () => props.deviceCode,
  270. // async () => {
  271. // await fetchDevice();
  272. // onDeviceChangeCallback(null, deviceInfo.value);
  273. // }
  274. // );
  275. onMounted(async () => {
  276. await fetchDevice();
  277. onDeviceChangeCallback(null, deviceInfo.value);
  278. });
  279. </script>
  280. <style scoped lang="less">
  281. @import '/@/design/theme.less';
  282. :deep(.@{ventSpace}-table-body) {
  283. height: auto !important;
  284. }
  285. :deep(.zxm-picker) {
  286. height: 30px !important;
  287. }
  288. :deep(.zxm-table) {
  289. .zxm-table-title {
  290. display: none !important;
  291. }
  292. }
  293. .history-table {
  294. width: 100%;
  295. :deep(.jeecg-basic-table-form-container) {
  296. .@{ventSpace}-form {
  297. padding: 0 !important;
  298. border: none !important;
  299. margin-bottom: 0 !important;
  300. .@{ventSpace}-picker,
  301. .@{ventSpace}-select-selector {
  302. width: 100% !important;
  303. background: #00000017;
  304. border: 1px solid #b7b7b7;
  305. input,
  306. .@{ventSpace}-select-selection-item,
  307. .@{ventSpace}-picker-suffix {
  308. color: #fff;
  309. }
  310. .@{ventSpace}-select-selection-placeholder {
  311. color: #ffffffaa;
  312. }
  313. }
  314. }
  315. .@{ventSpace}-table-title {
  316. min-height: 0 !important;
  317. }
  318. }
  319. .pagination-box {
  320. display: flex;
  321. justify-content: flex-end;
  322. align-items: center;
  323. .page-num {
  324. border: 1px solid #0090d8;
  325. padding: 4px 8px;
  326. margin-right: 5px;
  327. color: #0090d8;
  328. }
  329. .btn {
  330. margin-right: 10px;
  331. }
  332. }
  333. }
  334. </style>