HistoryTable.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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. // 而此组件可以将这些数据分类,例如:表格只有 温度 一列,但可以根据所选的子设备展示不同的数据;
  23. // 综上所述,此组件在基础的历史数据组件上添加了子设备下拉框(由字典驱动),用户选择子设备后表头将动态调整以适配数据展示;
  24. //
  25. // 使用方法如下:
  26. // 设有历史数据2条,[{ name, forcFan1Temp, forcFan2Temp }, { name, forcFan1Temp, forcFan2Temp }]。
  27. //
  28. // 1、配置设备字段(参考公司端综合设备管理-设备字段管理)
  29. // 以压风机为例,设压风机设备的历史数据编码为forcFan_history。
  30. // 那么字段code中需要把所有字段悉数配置,例子如下:
  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. import { onMounted, ref, shallowRef } from 'vue';
  54. import { BasicColumn, PaginationProps, BasicTable } from '/@/components/Table';
  55. import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
  56. import { defaultFormProps, defaultPaginationProps, getDefaultSchemas, defaultTableProps } from './history.data';
  57. import { adaptFormData, getDeviceList, getExportUrl, list } from './history.api';
  58. import { useListPage } from '/@/hooks/system/useListPage';
  59. import { initDictOptions } from '/@/utils/dict';
  60. const props = withDefaults(
  61. defineProps<{
  62. /** 表格项配置,默认由deviceCode获取且联动dictCode,可以覆写,覆写后将不支持联动,参考BaiscTable */
  63. // columns?: BasicColumn[];
  64. /** 表格操作项配置,默认为空,可以覆写 */
  65. // actionColumns?: BasicColumn;
  66. /** 查询表单项配置,默认联动dictCode,可以覆写,覆写后将不支持联动,提供formProps时此项无效,参考BaiscTable */
  67. // schemas?: FormSchema[];
  68. /** 表格分页配置,可以覆写,参考BaiscTable */
  69. pagination?: PaginationProps;
  70. /** 设备编码,该编码用于请求设备信息,示例:forcFan */
  71. deviceCode: string;
  72. /** 字典编码,该编码用于从字典配置中读出设备项,示例:forcFan_dict */
  73. dictCode: string;
  74. /** 字段编码,该编码用于从设备字段配置中读取默认表头信息,示例:forcFan_history */
  75. columnsCode: string;
  76. scroll: { x: number | true; y: number };
  77. /** 表格配置,参考BaiscTable,该值会与默认的配置进行浅合并,这里提供的任何配置都是优先的 */
  78. // tableProps?: BasicTableProps;
  79. /** 查询表单配置,参考BaiscTable */
  80. // formProps?: FormProps;
  81. }>(),
  82. {
  83. deviceCode: '',
  84. dictCode: '',
  85. }
  86. );
  87. // 创建表格,此表格目前不具备常用功能,需要初始化后使用(props指定表格配置时除外)
  88. let originColumns: BasicColumn[] = [];
  89. const { tableContext, onExportXls, onExportXlsPost } = useListPage({
  90. tableProps: defaultTableProps,
  91. exportConfig: {
  92. name: '设备历史列表',
  93. url: () => getExportUrl(deviceInfo.value),
  94. },
  95. });
  96. const [register, { getForm, setLoading, getPaginationRef, setPagination, setProps, setColumns }] = tableContext;
  97. /**
  98. * 初始化表格,该方法将根据参数设定新的表头、表单,如果提供了自定义的表头、表单配置则不作操作。
  99. *
  100. * 之所以将设备相关的信息作参数传入,是因为这样可以确认依赖关系,即需要有设备信息之后再初始化表格。
  101. *
  102. * @param deviceCodes 获取表头所用的编码,从左到右依次尝试,直到找到第一个有表头信息的为止
  103. * @param deviceOptions 设备下拉框对应的选项
  104. */
  105. function initTable(deviceCodes: string[], deviceOptions: any[], dictOptions: any[]) {
  106. const defaultSchemas = getDefaultSchemas(dictOptions, deviceOptions);
  107. for (const code of deviceCodes) {
  108. const cols = getTableHeaderColumns(code);
  109. if (cols.length) {
  110. originColumns = cols;
  111. break;
  112. }
  113. }
  114. setProps({
  115. formConfig: {
  116. ...defaultFormProps,
  117. schemas: defaultSchemas,
  118. },
  119. pagination: props.pagination || defaultPaginationProps,
  120. });
  121. }
  122. /**
  123. * 更新表头,表头默认情况下需要和子设备联动
  124. * @param prefix 子设备的值,即为表头取数据时字段的前缀
  125. */
  126. function updateColumns(prefix?: string) {
  127. if (!prefix) return setColumns(originColumns);
  128. // 如果有子设备信息,筛选符合规范的表头
  129. const cols = originColumns.map((col) => {
  130. const dataIndex = col.dataIndex as string;
  131. // 获取到子设备编码的前缀及编码,正则例子:forcFan1 => [forcFan1, forcFan, 1]
  132. const [_, pfx] = prefix.match(/([A-Za-z]+)([0-9]+)/) || [];
  133. // 同时,如若已经在前缀后配置了编号则不要更改
  134. const reg = new RegExp(`${pfx}[0-9]`);
  135. if (dataIndex.search(reg) !== -1) return col;
  136. if (dataIndex.includes(pfx)) {
  137. return {
  138. ...col,
  139. dataIndex: dataIndex.replace(pfx, prefix),
  140. };
  141. }
  142. // 默认直接放行
  143. return col;
  144. });
  145. setColumns(cols);
  146. }
  147. // 表格数据相关的字段
  148. const data = shallowRef([]);
  149. /**
  150. * 获取列表的数据
  151. *
  152. * 这些参数确认了依赖关系,即需要有表单数据、设备信息之后再尝试获取表格数据。
  153. *
  154. * @param formData 表格上方的表单数据
  155. * @param deviceCode 设备编码
  156. * @param deviceInfo 设备信息
  157. */
  158. function fetchData(formData: Record<string, unknown>, deviceCode: string, deviceInfo: any) {
  159. setLoading(true);
  160. const pagination = getPaginationRef() as PaginationProps;
  161. return list(deviceCode, deviceInfo, formData, pagination)
  162. .then(({ records, total, current }) => {
  163. setPagination({
  164. current,
  165. total,
  166. });
  167. records.forEach((item) => {
  168. Object.assign(item, item.readData);
  169. });
  170. data.value = records;
  171. })
  172. .finally(() => {
  173. setLoading(false);
  174. });
  175. }
  176. // 设备信息相关的字段
  177. const deviceInfo = ref<Record<string, unknown>>({});
  178. const deviceOptions = ref<Record<string, unknown>[]>([]);
  179. const dictOptions = ref<Record<string, unknown>[]>([]); // 子设备下拉框选项
  180. /**
  181. * 获取设备信息列表,初始化设备信息及设备可选项
  182. */
  183. async function fetchDevice() {
  184. const results = await getDeviceList({ devicetype: props.deviceCode, pageSize: 10000 });
  185. const dicts = await initDictOptions(props.dictCode);
  186. const options = results.map((item) => {
  187. return {
  188. label: item.strinstallpos,
  189. value: item.id || item.deviceID,
  190. deviceType: item.strtype || item.deviceType,
  191. devicekind: item.devicekind,
  192. stationtype: item.stationtype,
  193. };
  194. });
  195. deviceOptions.value = options;
  196. deviceInfo.value = results[0] || {};
  197. dictOptions.value = dicts;
  198. }
  199. /**
  200. * 搜索,核心方法
  201. *
  202. * 该方法获取表单、处理表单数据后尝试获取数据并设置表头
  203. */
  204. async function search() {
  205. const form = getForm();
  206. await form.validate();
  207. const formData = form.getFieldsValue();
  208. const info = deviceOptions.value.find((opt) => {
  209. return opt.value === formData.gdeviceids.split(',')[0];
  210. });
  211. if (!info) return;
  212. deviceInfo.value = info;
  213. const code = (deviceInfo.value.deviceType || props.deviceCode.concat('*')) as string;
  214. await fetchData(formData, code, deviceInfo.value);
  215. updateColumns(formData.deviceNum);
  216. }
  217. /** 导出表格内容为excel */
  218. async function exportXls() {
  219. const form = getForm();
  220. await form.validate();
  221. const formData = form.getFieldsValue();
  222. const pagination = getPaginationRef() as PaginationProps;
  223. const params = adaptFormData(props.deviceCode, deviceInfo.value, formData, pagination);
  224. if (deviceInfo.value.stationtype === 'redis') {
  225. return onExportXlsPost(params);
  226. } else {
  227. return onExportXls(params);
  228. }
  229. }
  230. onMounted(async () => {
  231. await fetchDevice();
  232. // 生成所有需要查询的表头编码,例如 forcFan_auto 对应 forcFan_auto_history、forcFan_history 这两个
  233. const codes: string[] = [];
  234. if (deviceInfo.value && deviceInfo.value.deviceType) {
  235. const arr = (deviceInfo.value.deviceType as string).split('_');
  236. while (arr.length) {
  237. codes.push(arr.join('_').concat('_history'));
  238. arr.pop();
  239. }
  240. }
  241. // 如此,例如deviceType为forcFan_auto, 则查询表头按 forcFan_auto_history、forcFan_history、columnsCode 为顺序
  242. initTable(codes.concat(props.columnsCode), deviceOptions.value, dictOptions.value);
  243. search();
  244. });
  245. </script>
  246. <style scoped lang="less">
  247. @import '/@/design/theme.less';
  248. :deep(.@{ventSpace}-table-body) {
  249. height: auto !important;
  250. }
  251. :deep(.zxm-picker) {
  252. height: 30px !important;
  253. }
  254. :deep(.zxm-table) {
  255. .zxm-table-title {
  256. display: none !important;
  257. }
  258. }
  259. .history-table {
  260. width: 100%;
  261. :deep(.jeecg-basic-table-form-container) {
  262. .@{ventSpace}-form {
  263. padding: 0 !important;
  264. border: none !important;
  265. margin-bottom: 0 !important;
  266. .@{ventSpace}-picker,
  267. .@{ventSpace}-select-selector {
  268. width: 100% !important;
  269. background: #00000017;
  270. border: 1px solid #b7b7b7;
  271. input,
  272. .@{ventSpace}-select-selection-item,
  273. .@{ventSpace}-picker-suffix {
  274. color: #fff;
  275. }
  276. .@{ventSpace}-select-selection-placeholder {
  277. color: #ffffffaa;
  278. }
  279. }
  280. }
  281. .@{ventSpace}-table-title {
  282. min-height: 0 !important;
  283. }
  284. }
  285. .pagination-box {
  286. display: flex;
  287. justify-content: flex-end;
  288. align-items: center;
  289. .page-num {
  290. border: 1px solid #0090d8;
  291. padding: 4px 8px;
  292. margin-right: 5px;
  293. color: #0090d8;
  294. }
  295. .btn {
  296. margin-right: 10px;
  297. }
  298. }
  299. }
  300. </style>