From 227117205cbb62e9febf96870d2fbb9ce056eb43 Mon Sep 17 00:00:00 2001 From: houzhongjian <houzhongyi@126.com> Date: 星期三, 06 十一月 2024 14:09:54 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- src/views/data/plan/category/CategoryForm.vue | 141 ++ src/views/model/chart/param/index.vue | 183 +++ src/api/data/plan/item/index.ts | 64 + src/views/data/channel/common/tag/TagImportForm.vue | 147 ++ src/views/data/plan/item/ItemForm.vue | 177 +++ src/views/data/point/index.vue | 1 src/api/model/mpk/chart.ts | 26 src/views/data/channel/opcda/tag/TagForm.vue | 10 src/views/data/channel/kio/tag/TagForm.vue | 10 src/views/model/mpk/file/MpkRun.vue | 2 src/views/data/channel/http/api/index.vue | 6 src/api/data/plan/data/index.ts | 40 src/api/data/channel/modbus/tag.ts | 10 src/views/data/channel/modbus/tag/index.vue | 109 ++ src/views/data/plan/item/ItemChart.vue | 225 ++++ src/views/model/chart/index.vue | 177 +++ src/api/data/channel/opcda/tag.ts | 10 src/views/data/channel/modbus/tag/TagForm.vue | 6 src/views/model/chart/ChartForm.vue | 111 ++ src/views/data/channel/opcda/index.vue | 6 src/views/data/plan/category/index.vue | 159 +++ src/api/data/channel/http/tag.ts | 10 src/views/model/mpk/file/MpkGenerator.vue | 16 src/views/data/plan/data/index.vue | 158 +++ src/views/data/channel/kio/index.vue | 4 src/views/data/plan/item/index.vue | 178 +++ src/api/data/channel/kio/tag.ts | 10 src/api/data/plan/category/index.ts | 50 + src/views/data/channel/http/api/tag/index.vue | 249 +++- src/views/data/plan/data/DataSetForm.vue | 129 ++ src/api/model/mpk/chartParam.ts | 26 src/views/data/channel/opcua/tag/TagForm.vue | 10 src/views/model/chart/param/ChartParamForm.vue | 130 ++ src/views/data/channel/opcua/tag/index.vue | 72 + src/views/data/channel/modbus/index.vue | 6 src/api/data/channel/opcua/tag.ts | 10 src/views/data/channel/http/api/tag/TagForm.vue | 10 src/views/model/pre/item/MmPredictItemForm.vue | 2 src/views/data/channel/opcda/tag/index.vue | 106 + src/views/data/channel/kio/tag/index.vue | 94 + src/views/data/point/DaPointChart.vue | 86 - src/views/data/point/DaPointForm.vue | 2 42 files changed, 2,768 insertions(+), 210 deletions(-) diff --git a/src/api/data/channel/http/tag.ts b/src/api/data/channel/http/tag.ts index dfead66..281ab32 100644 --- a/src/api/data/channel/http/tag.ts +++ b/src/api/data/channel/http/tag.ts @@ -37,3 +37,13 @@ export const deleteHttpTag = (id: number) => { return request.delete({ url: '/data/channel/http/tag/delete?id=' + id }) } + +//导出HttpTag +export const exportHttpTag = (params) => { + return request.download({ url: '/data/channel/http/tag/export', params }) +} + +// 下载用户导入模板 +export const importHttpTagTemplate = () => { + return request.download({ url: '/data/channel/http/tag/get-import-template' }) +} diff --git a/src/api/data/channel/kio/tag.ts b/src/api/data/channel/kio/tag.ts index 32a714e..b1ab25f 100644 --- a/src/api/data/channel/kio/tag.ts +++ b/src/api/data/channel/kio/tag.ts @@ -40,3 +40,13 @@ export const deleteKioTag = (id: number) => { return request.delete({ url: '/data/channel/kio/tag/delete?id=' + id }) } + +//导出KioTag +export const exportKioTag = (params) => { + return request.download({ url: '/data/channel/kio/tag/export', params }) +} + +// 下载用户导入模板 +export const importKioTagTemplate = () => { + return request.download({ url: '/data/channel/kio/tag/get-import-template' }) +} diff --git a/src/api/data/channel/modbus/tag.ts b/src/api/data/channel/modbus/tag.ts index a6299df..5f49c61 100644 --- a/src/api/data/channel/modbus/tag.ts +++ b/src/api/data/channel/modbus/tag.ts @@ -41,3 +41,13 @@ export const deleteModBusTag = (id: number) => { return request.delete({ url: '/data/channel/modbus/tag/delete?id=' + id }) } + +//导出ModBusTag +export const exportModBusTag = (params) => { + return request.download({ url: '/data/channel/modbus/tag/export', params }) +} + +// 下载用户导入模板 +export const importModBusTagTemplate = () => { + return request.download({ url: '/data/channel/modbus/tag/get-import-template' }) +} diff --git a/src/api/data/channel/opcda/tag.ts b/src/api/data/channel/opcda/tag.ts index 4f90eb1..089f2a8 100644 --- a/src/api/data/channel/opcda/tag.ts +++ b/src/api/data/channel/opcda/tag.ts @@ -38,3 +38,13 @@ export const deleteOpcdaTag = (id: number) => { return request.delete({ url: '/data/channel/opcda/tag/delete?id=' + id }) } + +//导出OpcdaTag +export const exportOpcDaTag = (params) => { + return request.download({ url: '/data/channel/opcda/tag/export', params }) +} + +// 下载用户导入模板 +export const importOpcDaTagTemplate = () => { + return request.download({ url: '/data/channel/opcda/tag/get-import-template' }) +} diff --git a/src/api/data/channel/opcua/tag.ts b/src/api/data/channel/opcua/tag.ts index e1373fb..6f21a1d 100644 --- a/src/api/data/channel/opcua/tag.ts +++ b/src/api/data/channel/opcua/tag.ts @@ -39,3 +39,13 @@ export const deleteOpcuaTag = (id: number) => { return request.delete({ url: '/data/channel/opcua/tag/delete?id=' + id }) } + +//导出OpcdaTag +export const exportOpcUaTag = (params) => { + return request.download({ url: '/data/channel/opcua/tag/export', params }) +} + +// 下载用户导入模板 +export const importOpcUaTagTemplate = () => { + return request.download({ url: '/data/channel/opcua/tag/get-import-template' }) +} diff --git a/src/api/data/plan/category/index.ts b/src/api/data/plan/category/index.ts new file mode 100644 index 0000000..f8faecd --- /dev/null +++ b/src/api/data/plan/category/index.ts @@ -0,0 +1,50 @@ +import request from '@/config/axios' + +export interface IndItemCategoryVO { + id: string + label: string + pid: string + sort: number +} + +export interface ItemCategoryReqVO { + label?: string +} + +export const defaultProps = { + children: 'children', + label: 'label', + value: 'id', + isLeaf: 'leaf', + emitPath: false // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值 +} + +// 查询列表 +export const getCategoryList = (params) => { + return request.get({ url: '/data/plan/category/list', params}) +} + +// 查询列表 +export const getCategoryListAllSimple = () => { + return request.get({ url: '/data/plan/category/list-all-simple'}) +} + +// 查询详情 +export const getCategory = (id: number) => { + return request.get({ url: '/data/plan/category/get?id=' + id}) +} + +// 新增 +export const createCategory = (data: ScheduleModelVO) => { + return request.post({ url: '/data/plan/category/create', data }) +} + +// 修改 +export const updateCategory = (data: ScheduleModelVO) => { + return request.put({ url: '/data/plan/category/update', data }) +} + +// 删除 +export const deleteCategory = (id: number) => { + return request.delete({ url: '/data/plan/category/delete?id=' + id }) +} diff --git a/src/api/data/plan/data/index.ts b/src/api/data/plan/data/index.ts new file mode 100644 index 0000000..f04478a --- /dev/null +++ b/src/api/data/plan/data/index.ts @@ -0,0 +1,40 @@ +import request from '@/config/axios' + +export type DataSetVO = { + id: number | undefined + name: string + dataSource: string + querySql: string + remark: string + sort: number +} + +// 查询列表 +export const getDataSetList = () => { + return request.get({ url: '/data/plan/data-set/list-all-simple' }) +} + +// 查询列表 +export const getDataSetPage = (params: PageParam) => { + return request.get({ url: '/data/plan/data-set/page', params }) +} + +// 查询详情 +export const getDataSet = (id: number) => { + return request.get({ url: '/data/plan/data-set/get?id=' + id }) +} + +// 新增 +export const createDataSet = (data: DataSetVO) => { + return request.post({ url: '/data/plan/data-set/create', data }) +} + +// 修改 +export const updateDataSet = (data: DataSetVO) => { + return request.put({ url: '/data/plan/data-set/update', data }) +} + +// 删除 +export const deleteDataSet = (id: number) => { + return request.delete({ url: '/data/plan/data-set/delete?id=' + id }) +} diff --git a/src/api/data/plan/item/index.ts b/src/api/data/plan/item/index.ts new file mode 100644 index 0000000..e42e999 --- /dev/null +++ b/src/api/data/plan/item/index.ts @@ -0,0 +1,64 @@ +import request from '@/config/axios' + +export type ItemVO = { + id: string | undefined + itemNo: string + itemName: string + itemCategory: string + timeGranularity: string + dataSource: string + remark: string + status: string +} + +export type PageParam = { + itemNo: string + itemName: string + itemCategory: string +} + +export interface PlanChartReqVO { + itemNos?: [], + start?: Date, + end?: Date, +} + +// 查询列表 +export const getItemPage = (params: PageParam) => { + return request.get({ url: '/data/plan-item/page', params }) +} + +// 查询详情 +export const getItem = (id: string) => { + return request.get({ url: '/data/plan-item/get?id=' + id }) +} + +// 新增 +export const createItem = (data: ItemVO) => { + return request.post({ url: '/data/plan-item/create', data }) +} + +// 修改 +export const updateItem = (data: ItemVO) => { + return request.put({ url: '/data/plan-item/update', data }) +} + +// 删除 +export const deleteItem = (id: number) => { + return request.delete({ url: '/data/plan-item/delete?id=' + id }) +} + +//获取下拉集合 +export const getItemList = (params: PageParam) => { + return request.get({ url: '/data/plan-item/getList', params}) +} + +// 查询Plan图表 +export const getPlanChart = (data: PlanChartReqVO) => { + return request.post({ url: '/data/api/query-plans/chart', data }) +} + +// 导出Plan值 +export const exportPlanValue = (data: PlanChartReqVO) => { + return request.post({ url: '/data/api/export-plan/history-value', data }) +} diff --git a/src/api/model/mpk/chart.ts b/src/api/model/mpk/chart.ts new file mode 100644 index 0000000..64daacc --- /dev/null +++ b/src/api/model/mpk/chart.ts @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// 查询列表 +export const getPage = (params) => { + return request.get({ url: '/model/chart/page', params}) +} + +// 获得 +export const get = (id) => { + return request.get({ url: '/model/chart/get?id=' + id }) +} + +// 新增 +export const create = (data) => { + return request.post({ url: '/model/chart/create', data }) +} + +// 修改 +export const update = (data) => { + return request.put({ url: '/model/chart/update', data }) +} + +// 删除 +export const del = (id) => { + return request.delete({ url: '/model/chart/delete?id=' + id }) +} diff --git a/src/api/model/mpk/chartParam.ts b/src/api/model/mpk/chartParam.ts new file mode 100644 index 0000000..7a87452 --- /dev/null +++ b/src/api/model/mpk/chartParam.ts @@ -0,0 +1,26 @@ +import request from '@/config/axios' + +// 查询列表 +export const getPage = (params) => { + return request.get({ url: '/model/chart/param/page', params}) +} + +// 获得 +export const get = (id) => { + return request.get({ url: '/model/chart/param/get?id=' + id }) +} + +// 新增 +export const create = (data) => { + return request.post({ url: '/model/chart/param/create', data }) +} + +// 修改 +export const update = (data) => { + return request.put({ url: '/model/chart/param/update', data }) +} + +// 删除 +export const del = (id) => { + return request.delete({ url: '/model/chart/param/delete?id=' + id }) +} diff --git a/src/views/data/channel/common/tag/TagImportForm.vue b/src/views/data/channel/common/tag/TagImportForm.vue new file mode 100644 index 0000000..20baf6c --- /dev/null +++ b/src/views/data/channel/common/tag/TagImportForm.vue @@ -0,0 +1,147 @@ +<template> + <Dialog v-model="dialogVisible" title="Tag导入" width="400"> + <el-upload + ref="uploadRef" + v-model:file-list="fileList" + :action="importUrl + '?updateSupport=' + updateSupport + '&device=' + parameter" + :auto-upload="false" + :disabled="formLoading" + :headers="uploadHeaders" + :limit="1" + :on-error="submitFormError" + :on-exceed="handleExceed" + :on-success="submitFormSuccess" + accept=".xlsx, .xls" + drag + > + <Icon icon="ep:upload" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip text-center"> + <div class="el-upload__tip"> + <el-checkbox v-model="updateSupport" /> + 是否更新已经存在的Tag数据 + </div> + <span>仅允许导入 xls、xlsx 格式文件。</span> + <el-link + :underline="false" + style="font-size: 12px; vertical-align: baseline" + type="primary" + @click="importTemplate" + > + 下载模板 + </el-link> + </div> + </template> + </el-upload> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + +import { getAccessToken, getTenantId } from '@/utils/auth' +import download from '@/utils/download' +import * as ModBusTagApi from "@/api/data/channel/modbus/tag" + +defineOptions({ name: 'PointImportForm' }) + +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中 +const uploadRef = ref() +const importUrl = ref() +const uploadHeaders = ref() // 上传 Header 头 +const fileList = ref([]) // 文件列表 +const updateSupport = ref(0) // 是否更新已经存在的测点数据 +const decName = ref() +const typeName = ref() +const parameter = ref() +let importTemplateApi = reactive() +/** 打开弹窗 */ +const open = (name?: string, url?:string, tagApi?:Object, type?:string, params?:string) => { + importTemplateApi = tagApi + dialogVisible.value = true + updateSupport.value = 0 + fileList.value = [] + decName.value = name + typeName.value = type + parameter.value = params + importUrl.value = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + url + resetForm() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + if (fileList.value.length == 0) { + message.error('请上传文件') + return + } + // 提交请求 + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + formLoading.value = true + uploadRef.value!.submit() +} + +/** 文件上传成功 */ +const emits = defineEmits(['success']) +const submitFormSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + formLoading.value = false + return + } + // 拼接提示语 + const data = response.data + let text = '上传成功数量:' + data.createTagNames.length + ';' + for (let tagName of data.createTagNames) { + text += '< ' + tagName + ' >' + } + text += '更新成功数量:' + data.updateTagNames.length + ';' + for (const tagName of data.updateTagNames) { + text += '< ' + tagName + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureTagNames).length + ';' + for (const tagName in data.failureTagNames) { + text += '< ' + tagName + ': ' + data.failureTagNames[tagName] + ' >' + } + message.alert(text) + formLoading.value = false + dialogVisible.value = false + // 发送操作成功的事件 + emits('success') +} + +/** 上传错误提示 */ +const submitFormError = (): void => { + message.error('上传失败,请您重新上传!') + formLoading.value = false +} + +/** 重置表单 */ +const resetForm = async (): Promise<void> => { + // 重置上传状态和文件 + formLoading.value = false + await nextTick() + uploadRef.value?.clearFiles() +} + +/** 文件数超出提示 */ +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} + +/** 下载模板操作 */ +const importTemplate = async () => { + const res = await importTemplateApi + const excelName = typeName.value + '_' + decName.value + '_' + 'Tag导入模板.xlsx' + download.excel(res, excelName) +} +</script> diff --git a/src/views/data/channel/http/api/index.vue b/src/views/data/channel/http/api/index.vue index f3a7c9d..e3b06be 100644 --- a/src/views/data/channel/http/api/index.vue +++ b/src/views/data/channel/http/api/index.vue @@ -61,7 +61,7 @@ <el-button link type="primary" - @click="openTagList(scope.row.id)" + @click="openTagList(scope.row.id, scope.row.name)" v-hasPermi="['data:channel-http:update']" > TAG @@ -146,8 +146,8 @@ /** TAG操作 */ const tagRef = ref() -const openTagList = (id?: string) => { - tagRef.value.open(id) +const openTagList = (id?: string,name?: string) => { + tagRef.value.open(id,name) } /** 删除按钮操作 */ diff --git a/src/views/data/channel/http/api/tag/TagForm.vue b/src/views/data/channel/http/api/tag/TagForm.vue index 210c944..5d61c53 100644 --- a/src/views/data/channel/http/api/tag/TagForm.vue +++ b/src/views/data/channel/http/api/tag/TagForm.vue @@ -31,7 +31,7 @@ <el-form-item label="是否启用" prop="enabled"> <el-select v-model="formData.enabled" placeholder="请选择"> <el-option - v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)" + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -56,8 +56,8 @@ </template> <script lang="ts" setup> import * as HttpTagApi from '@/api/data/channel/http/tag' -import { CommonEnabledBool } from '@/utils/constants' -import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict' +import {CommonEnabled} from '@/utils/constants' +import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict' defineOptions({name: 'HttpTagForm'}) @@ -73,7 +73,7 @@ tagName: undefined, dataType: undefined, tagDesc: '', - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, }) const formRules = reactive({ tagName: [{required: true, message: 'Tag名称不能为空', trigger: 'blur'}], @@ -136,7 +136,7 @@ tagName: undefined, dataType: undefined, tagDesc: '', - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, } formRef.value?.resetFields() } diff --git a/src/views/data/channel/http/api/tag/index.vue b/src/views/data/channel/http/api/tag/index.vue index 4fbca5b..82cf55a 100644 --- a/src/views/data/channel/http/api/tag/index.vue +++ b/src/views/data/channel/http/api/tag/index.vue @@ -2,7 +2,7 @@ <el-drawer v-model="drawer" size="50%" - title="Kio Tag" + title="Http Tag" :direction="direction" :before-close="handleClose" > @@ -26,22 +26,45 @@ </el-form-item> <el-form-item> <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> + <Icon icon="ep:search" class="mr-5px"/> 搜索 </el-button> <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> + <Icon icon="ep:refresh" class="mr-5px"/> 重置 </el-button> <el-button type="primary" plain @click="openForm('create')" - v-hasPermi="['data:channel-kio:create']" + v-hasPermi="['data:channel-http:create']" > - <Icon icon="ep:plus" class="mr-5px" /> + <Icon icon="ep:plus" class="mr-5px"/> 新增 </el-button> + <el-button + type="warning" + plain + @click="handleImport" + v-hasPermi="['data:channel-http-tag:import']"> + <Icon icon="ep:upload"/> + 导入 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['data:channel-http-tag:export']"> + <Icon icon="ep:download"/> + 导出 + </el-button> + </el-form-item> + <el-form-item label="更新当前值" label-width="100px"> + <el-switch + v-model="queryParams.currentValue" + active-color="#13ce66" + inactive-color="#ff4949"/> </el-form-item> </el-form> </ContentWrap> @@ -75,8 +98,27 @@ align="center" > <template #default="scope"> - <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag> + <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag> <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column + prop="dataValue" + label="数据值" + header-align="center" + align="center" + :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}" + /> + <el-table-column + prop="quality" + label="数据质量" + header-align="center" + align="center" + > + <template #default="scope"> + <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad + </el-tag> + <el-tag v-else size="small">good</el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" min-width="110" fixed="right"> @@ -85,7 +127,7 @@ link type="primary" @click="openForm('update', scope.row.id)" - v-hasPermi="['data:channel-kio:update']" + v-hasPermi="['data:channel-http:update']" > 编辑 </el-button> @@ -93,7 +135,7 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['data:channel-kio:delete']" + v-hasPermi="['data:channel-http:delete']" > 删除 </el-button> @@ -109,97 +151,128 @@ /> </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <TagForm ref="formRef" @success="getList" /> + <TagForm ref="formRef" @success="getList"/> + <TagImportForm ref="importFormRef" @success="getList"/> </el-drawer> </template> <script lang="ts" setup> -import type { DrawerProps } from 'element-plus' -import * as HttpTagApi from "@/api/data/channel/http/tag"; -import TagForm from './TagForm.vue' + import type {DrawerProps} from 'element-plus' + import * as HttpTagApi from "@/api/data/channel/http/tag"; + import TagForm from './TagForm.vue' + import download from "@/utils/download"; + import {ref} from "vue"; + import {onBeforeUnmount, onMounted} from "vue"; + import TagImportForm from '../../../common/tag/TagImportForm.vue' -defineOptions({name: 'HttpTag'}) + defineOptions({name: 'HttpTag'}) -const message = useMessage() // 消息弹窗 -const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 -const drawer = ref(false) -const direction = ref<DrawerProps['direction']>('rtl') -const loading = ref(true) // 列表的加载中 -const total = ref(0) // 列表的总页数 -const list = ref([]) // 列表的数据 -const queryParams = reactive({ - pageNo: 1, - pageSize: 10, - apiId: undefined, - tagName: undefined -}) -const queryFormRef = ref() // 搜索的表单 -const exportLoading = ref(false) // 导出的加载中 + const drawer = ref(false) + const direction = ref<DrawerProps['direction']>('rtl') + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 列表的数据 + const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + apiId: undefined, + tagName: undefined, + httpName: undefined, + currentValue:false, + }) + const queryFormRef = ref() // 搜索的表单 + const exportLoading = ref(false) // 导出的加载中 -/** 查询列表 */ -const getList = async () => { - loading.value = true - try { - const page = await HttpTagApi.getHttpTagPage(queryParams) - list.value = page.list - total.value = page.total - } finally { - loading.value = false + /** 查询列表 */ + const getList = async () => { + loading.value = true + try { + const page = await HttpTagApi.getHttpTagPage(queryParams) + list.value = page.list + total.value = page.total + } finally { + loading.value = false + } } -} -/** 搜索按钮操作 */ -const handleQuery = () => { - queryParams.pageNo = 1 - getList() -} - -/** 重置按钮操作 */ -const resetQuery = () => { - queryFormRef.value.resetFields() - handleQuery() -} - -/** 添加/修改操作 */ -const formRef = ref() -const openForm = (type: string, id?: number) => { - formRef.value.open(type, id, queryParams.apiId) -} - -/** 删除按钮操作 */ -const handleDelete = async (id: number) => { - try { - // 删除的二次确认 - await message.delConfirm() - // 发起删除 - await HttpTagApi.deleteHttpTag(id) - message.success(t('common.delSuccess')) - // 刷新列表 - await getList() - } catch { - } -} - -/** 打开弹窗 */ -const open = async (apiId?: string) => { - resetForm() - drawer.value = true - queryParams.apiId = apiId - if (apiId) { + /** 搜索按钮操作 */ + const handleQuery = () => { + queryParams.pageNo = 1 getList() } -} -defineExpose({open}) // 提供 open 方法,用于打开弹窗 -/** 重置表单 */ -const resetForm = () => { - queryParams.pageNo = 1 - queryParams.pageSize = 10 - queryParams.apiId = '' - queryParams.tagName = '' -} + /** 重置按钮操作 */ + const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() + } -const handleClose = (done: () => void) => { - drawer.value = false -} + /** 添加/修改操作 */ + const formRef = ref() + const openForm = (type: string, id?: number) => { + formRef.value.open(type, id, queryParams.apiId) + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await HttpTagApi.deleteHttpTag(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + /** 打开弹窗 */ + const open = async (apiId?: string, name?: string) => { + resetForm() + drawer.value = true + queryParams.apiId = apiId + queryParams.httpName = name + if (apiId) { + getList() + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + /** 重置表单 */ + const resetForm = () => { + queryParams.pageNo = 1 + queryParams.pageSize = 10 + queryParams.apiId = '' + queryParams.tagName = '' + } + + const handleClose = (done: () => void) => { + drawer.value = false + } + + /** tag导入 */ + const importFormRef = ref() + const handleImport = () => { + if (queryParams.apiId) { + importFormRef.value.open(queryParams.httpName, '/data/channel/http/tag/import', HttpTagApi.importHttpTagTemplate(), 'Http', queryParams.apiId) + } + } + + /** 导出按钮操作 */ + const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await HttpTagApi.exportHttpTag(queryParams) + download.excel(data, 'Http_' + queryParams.httpName + '_Tag列表.xlsx') + } catch { + } finally { + exportLoading.value = false + } + } </script> diff --git a/src/views/data/channel/kio/index.vue b/src/views/data/channel/kio/index.vue index dc9be25..ded1abb 100644 --- a/src/views/data/channel/kio/index.vue +++ b/src/views/data/channel/kio/index.vue @@ -145,8 +145,8 @@ /** TAG操作 */ const tagRef = ref() - const openTagList = (name?: string) => { - tagRef.value.open(name) + const openTagList = (id?: string,name?: string) => { + tagRef.value.open(id,name) } /** 删除按钮操作 */ diff --git a/src/views/data/channel/kio/tag/TagForm.vue b/src/views/data/channel/kio/tag/TagForm.vue index ff2fc72..ad6da51 100644 --- a/src/views/data/channel/kio/tag/TagForm.vue +++ b/src/views/data/channel/kio/tag/TagForm.vue @@ -36,7 +36,7 @@ <el-form-item label="是否启用" prop="enabled"> <el-select v-model="formData.enabled" placeholder="请选择"> <el-option - v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)" + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -61,9 +61,9 @@ </template> <script lang="ts" setup> import * as KioTagApi from '@/api/data/channel/kio/tag' - import { CommonEnabledBool } from '@/utils/constants' + import {CommonEnabled} from '@/utils/constants' import {isPositiveInteger} from '@/utils/validate' - import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict' + import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict' defineOptions({name: 'KioTagForm'}) @@ -79,7 +79,7 @@ dataType: undefined, tagId: undefined, tagDesc: '', - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, device: undefined, samplingRate: undefined @@ -152,7 +152,7 @@ dataType: undefined, tagId: undefined, tagDesc: '', - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, device: undefined, samplingRate: undefined } diff --git a/src/views/data/channel/kio/tag/index.vue b/src/views/data/channel/kio/tag/index.vue index 990c389..d334fb4 100644 --- a/src/views/data/channel/kio/tag/index.vue +++ b/src/views/data/channel/kio/tag/index.vue @@ -42,6 +42,27 @@ <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> + <el-button + type="warning" + plain + @click="handleImport" + v-hasPermi="['data:channel-kio-tag:import']"> + <Icon icon="ep:upload" /> 导入 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['data:channel-kio-tag:export']"> + <Icon icon="ep:download" />导出 + </el-button> + </el-form-item> + <el-form-item label="更新当前值" label-width="100px"> + <el-switch + v-model="queryParams.currentValue" + active-color="#13ce66" + inactive-color="#ff4949"/> </el-form-item> </el-form> </ContentWrap> @@ -75,8 +96,26 @@ align="center" > <template #default="scope"> - <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag> + <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag> <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column + prop="dataValue" + label="数据值" + header-align="center" + align="center" + :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}" + /> + <el-table-column + prop="quality" + label="数据质量" + header-align="center" + align="center" + > + <template #default="scope"> + <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag> + <el-tag v-else size="small">good</el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" min-width="110" fixed="right"> @@ -110,12 +149,17 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> <TagForm ref="formRef" @success="getList" /> + <TagImportForm ref="importFormRef" @success="getList" /> </el-drawer> </template> <script lang="ts" setup> import type { DrawerProps } from 'element-plus' import * as KioTagApi from "@/api/data/channel/kio/tag"; import TagForm from './TagForm.vue' + import download from "@/utils/download"; + import TagImportForm from '../../common/tag/TagImportForm.vue' + import {ref, onBeforeUnmount, onMounted} from "vue"; + import * as HttpTagApi from "@/api/data/channel/http/tag"; defineOptions({name: 'KioTag'}) @@ -131,6 +175,8 @@ pageNo: 1, pageSize: 10, device: undefined, + deviceId: undefined, + currentValue:false, tagName: undefined }) const queryFormRef = ref() // 搜索的表单 @@ -181,10 +227,11 @@ } /** 打开弹窗 */ - const open = async (device?: string) => { + const open = async (deviceId?: string,device?: string) => { resetForm() drawer.value = true queryParams.device = device + queryParams.deviceId = deviceId if (device) { getList() } @@ -202,4 +249,47 @@ const handleClose = (done: () => void) => { drawer.value = false } + + /** tag导入 */ + const importFormRef = ref() + const handleImport = () => { + if(queryParams.device){ + importFormRef.value.open(queryParams.device, '/data/channel/kio/tag/import',KioTagApi.importKioTagTemplate(), 'Kio', queryParams.device) + } + } + + /** 导出按钮操作 */ + const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await KioTagApi.exportKioTag(queryParams) + download.excel(data, 'Kio_' + queryParams.device + '_Tag列表.xlsx') + } catch { + } finally { + exportLoading.value = false + } + } + + let intervalId; + + onMounted(async () => { + // 创建定时器 + intervalId = setInterval(async () => { + if(queryParams.currentValue){ + const page = await KioTagApi.getKioTagPage(queryParams) + list.value = page.list + total.value = page.total + } + }, 10000); + }); + + // 在组件卸载时清除定时器 + onBeforeUnmount(() => { + if (intervalId) { + clearInterval(intervalId); + } + }); </script> diff --git a/src/views/data/channel/modbus/index.vue b/src/views/data/channel/modbus/index.vue index 58b0942..c97cada 100644 --- a/src/views/data/channel/modbus/index.vue +++ b/src/views/data/channel/modbus/index.vue @@ -75,7 +75,7 @@ <el-button link type="primary" - @click="openTagList(scope.row.name)" + @click="openTagList(scope.row.id,scope.row.name)" v-hasPermi="['data:channel-modbus:update']" > TAG @@ -161,8 +161,8 @@ /** TAG操作 */ const tagRef = ref() - const openTagList = (name?: string) => { - tagRef.value.open(name) + const openTagList = (id?: string,name?: string) => { + tagRef.value.open(id,name) } /** 删除按钮操作 */ diff --git a/src/views/data/channel/modbus/tag/TagForm.vue b/src/views/data/channel/modbus/tag/TagForm.vue index a8ec15d..d248a96 100644 --- a/src/views/data/channel/modbus/tag/TagForm.vue +++ b/src/views/data/channel/modbus/tag/TagForm.vue @@ -55,7 +55,7 @@ <el-form-item label="是否启用" prop="enabled"> <el-select v-model="formData.enabled" placeholder="请选择"> <el-option - v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)" + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -82,7 +82,7 @@ import * as ModBusTagApi from '@/api/data/channel/modbus/tag' import { CommonEnabled } from '@/utils/constants' import {isPositiveInteger} from '@/utils/validate' - import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict' + import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict' defineOptions({name: 'ModBusTagForm'}) @@ -98,7 +98,7 @@ dataType: undefined, enabled: CommonEnabled.ENABLE, format: undefined, - device: '1', + device: '', address: '', samplingRate: undefined, tagDesc: '', diff --git a/src/views/data/channel/modbus/tag/index.vue b/src/views/data/channel/modbus/tag/index.vue index 335b5e1..7739240 100644 --- a/src/views/data/channel/modbus/tag/index.vue +++ b/src/views/data/channel/modbus/tag/index.vue @@ -35,22 +35,44 @@ </el-form-item> <el-form-item> <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> + <Icon icon="ep:search" class="mr-5px"/> 搜索 </el-button> <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> + <Icon icon="ep:refresh" class="mr-5px"/> 重置 </el-button> <el-button type="primary" plain @click="openForm('create')" - v-hasPermi="['data:channel-modbus:create']" - > - <Icon icon="ep:plus" class="mr-5px" /> + v-hasPermi="['data:channel-modbus:create']"> + <Icon icon="ep:plus" class="mr-5px"/> 新增 </el-button> + <el-button + type="warning" + plain + @click="handleImport" + v-hasPermi="['data:channel-modbus-tag:import']"> + <Icon icon="ep:upload"/> + 导入 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['data:channel-modbus-tag:export']"> + <Icon icon="ep:download"/> + 导出 + </el-button> + </el-form-item> + <el-form-item label="更新当前值" label-width="100px"> + <el-switch + v-model="queryParams.currentValue" + active-color="#13ce66" + inactive-color="#ff4949"/> </el-form-item> </el-form> </ContentWrap> @@ -102,8 +124,26 @@ align="center" > <template #default="scope"> - <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag> + <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag> <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column + prop="dataValue" + label="数据值" + header-align="center" + align="center" + :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}" + /> + <el-table-column + prop="quality" + label="数据质量" + header-align="center" + align="center" + > + <template #default="scope"> + <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag> + <el-tag v-else size="small">good</el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" min-width="110" fixed="right"> @@ -136,13 +176,19 @@ /> </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <TagForm ref="formRef" @success="getList" /> + <TagForm ref="formRef" @success="getList"/> + <TagImportForm ref="importFormRef" @success="getList"/> </el-drawer> </template> <script lang="ts" setup> - import type { DrawerProps } from 'element-plus' + import type {DrawerProps} from 'element-plus' import * as ModBusTagApi from "@/api/data/channel/modbus/tag"; import TagForm from './TagForm.vue' + import download from "@/utils/download"; + import {ref} from "vue"; + import TagImportForm from '../../common/tag/TagImportForm.vue' + import {onBeforeUnmount, onMounted} from "vue"; + import * as HttpTagApi from "@/api/data/channel/http/tag"; defineOptions({name: 'ModBusTag'}) @@ -157,8 +203,10 @@ const queryParams = reactive({ pageNo: 1, pageSize: 10, + deviceId: undefined, device: undefined, tagName: undefined, + currentValue:false, address: undefined }) const queryFormRef = ref() // 搜索的表单 @@ -209,10 +257,11 @@ } /** 打开弹窗 */ - const open = async (device?: string) => { + const open = async (deviceId?: string,device?: string) => { resetForm() drawer.value = true queryParams.device = device + queryParams.deviceId = deviceId if (device) { getList() } @@ -231,4 +280,46 @@ const handleClose = (done: () => void) => { drawer.value = false } + + /** tag导入 */ + const importFormRef = ref() + const handleImport = () => { + if (queryParams.device) { + importFormRef.value.open(queryParams.device, '/data/channel/modbus/tag/import', ModBusTagApi.importModBusTagTemplate(), 'ModBus', queryParams.device) + } + } + + /** 导出按钮操作 */ + const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await ModBusTagApi.exportModBusTag(queryParams) + download.excel(data, 'ModBus_' + queryParams.device + '_Tag列表.xlsx') + } catch { + } finally { + exportLoading.value = false + } + } + let intervalId; + + onMounted(async () => { + // 创建定时器 + intervalId = setInterval(async () => { + if(queryParams.currentValue){ + const page = await ModBusTagApi.getModBusTagPage(queryParams) + list.value = page.list + total.value = page.total + } + }, 10000); + }); + + // 在组件卸载时清除定时器 + onBeforeUnmount(() => { + if (intervalId) { + clearInterval(intervalId); + } + }); </script> diff --git a/src/views/data/channel/opcda/index.vue b/src/views/data/channel/opcda/index.vue index b538779..6481531 100644 --- a/src/views/data/channel/opcda/index.vue +++ b/src/views/data/channel/opcda/index.vue @@ -60,7 +60,7 @@ <el-button link type="primary" - @click="openTagList(scope.row.id)" + @click="openTagList(scope.row.id,scope.row.serverName)" v-hasPermi="['data:channel-opcda:update']" > TAG @@ -145,8 +145,8 @@ /** TAG操作 */ const tagRef = ref() - const openTagList = (id?: string) => { - tagRef.value.open(id) + const openTagList = (id?: string,serverName?:string) => { + tagRef.value.open(id,serverName) } /** 删除按钮操作 */ diff --git a/src/views/data/channel/opcda/tag/TagForm.vue b/src/views/data/channel/opcda/tag/TagForm.vue index 9f4b6ab..813b9ca 100644 --- a/src/views/data/channel/opcda/tag/TagForm.vue +++ b/src/views/data/channel/opcda/tag/TagForm.vue @@ -36,7 +36,7 @@ <el-form-item label="是否启用" prop="enabled"> <el-select v-model="formData.enabled" placeholder="请选择"> <el-option - v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)" + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -54,8 +54,8 @@ </template> <script lang="ts" setup> import * as OpcdaTagApi from '@/api/data/channel/opcda/tag' - import { CommonEnabledBool } from '@/utils/constants' - import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict' + import {CommonEnabled} from '@/utils/constants' + import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict' defineOptions({name: 'OpcdaTagForm'}) @@ -70,7 +70,7 @@ serverId: undefined, tagName: undefined, dataType: undefined, - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, itemId: undefined }) const formRules = reactive({ @@ -133,7 +133,7 @@ serverId: undefined, tagName: undefined, dataType: undefined, - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, itemId: undefined } formRef.value?.resetFields() diff --git a/src/views/data/channel/opcda/tag/index.vue b/src/views/data/channel/opcda/tag/index.vue index 98f57da..17d4011 100644 --- a/src/views/data/channel/opcda/tag/index.vue +++ b/src/views/data/channel/opcda/tag/index.vue @@ -2,7 +2,7 @@ <el-drawer v-model="drawer" size="50%" - title="ModBus Tag" + title="OpcDA Tag" :direction="direction" :before-close="handleClose" > @@ -37,11 +37,32 @@ type="primary" plain @click="openForm('create')" - v-hasPermi="['data:channel-modbus:create']" + v-hasPermi="['data:channel-opcda:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> + <el-button + type="warning" + plain + @click="handleImport" + v-hasPermi="['data:channel-opcda-tag:import']"> + <Icon icon="ep:upload" /> 导入 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['data:channel-opcda-tag:export']"> + <Icon icon="ep:download" />导出 + </el-button> + </el-form-item> + <el-form-item label="更新当前值" label-width="100px"> + <el-switch + v-model="queryParams.currentValue" + active-color="#13ce66" + inactive-color="#ff4949"/> </el-form-item> </el-form> </ContentWrap> @@ -68,8 +89,26 @@ align="center" > <template #default="scope"> - <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag> + <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag> <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column + prop="dataValue" + label="数据值" + header-align="center" + align="center" + :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}" + /> + <el-table-column + prop="quality" + label="数据质量" + header-align="center" + align="center" + > + <template #default="scope"> + <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag> + <el-tag v-else size="small">good</el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" min-width="110" fixed="right"> @@ -103,12 +142,18 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> <TagForm ref="formRef" @success="getList" /> + <TagImportForm ref="importFormRef" @success="getList" /> </el-drawer> </template> <script lang="ts" setup> import type { DrawerProps } from 'element-plus' - import * as OpcdaTagApi from "@/api/data/channel/opcda/tag"; + import * as OpcDaTagApi from "@/api/data/channel/opcda/tag"; import TagForm from './TagForm.vue' + import download from "@/utils/download"; + import {ref,reactive} from "vue"; + import TagImportForm from '../../common/tag/TagImportForm.vue' + import {onBeforeUnmount, onMounted} from "vue"; + import * as OpcdaTagApi from "@/api/data/channel/opcda/tag"; defineOptions({name: 'ModBusTag'}) @@ -124,7 +169,9 @@ pageNo: 1, pageSize: 10, serverId: undefined, - tagName: undefined + tagName: undefined, + serverName: undefined + currentValue:false, }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 @@ -133,7 +180,7 @@ const getList = async () => { loading.value = true try { - const page = await OpcdaTagApi.getOpcdaTagPage(queryParams) + const page = await OpcDaTagApi.getOpcdaTagPage(queryParams) list.value = page.list total.value = page.total } finally { @@ -165,7 +212,7 @@ // 删除的二次确认 await message.delConfirm() // 发起删除 - await OpcdaTagApi.deleteOpcdaTag(id) + await OpcDaTagApi.deleteOpcdaTag(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -174,10 +221,11 @@ } /** 打开弹窗 */ - const open = async (serverId?: string) => { + const open = async (serverId?: string, serverName?: string) => { resetForm() drawer.value = true queryParams.serverId = serverId + queryParams.serverName = serverName if (serverId) { getList() } @@ -195,4 +243,46 @@ const handleClose = (done: () => void) => { drawer.value = false } + + /** tag导入 */ + const importFormRef = ref() + const handleImport = () => { + if(queryParams.serverId){ + importFormRef.value.open(queryParams.serverName, '/data/channel/opcda/tag/import',OpcDaTagApi.importOpcDaTagTemplate(), 'OpcDa', queryParams.serverId) + } + } + + /** 导出按钮操作 */ + const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await OpcDaTagApi.exportOpcDaTag(queryParams) + download.excel(data, 'OpcDa_' + queryParams.serverName + '_Tag列表.xlsx') + } catch { + } finally { + exportLoading.value = false + } + } + let intervalId; + + onMounted(async () => { + // 创建定时器 + intervalId = setInterval(async () => { + if(queryParams.currentValue){ + const page = await OpcdaTagApi.getOpcdaTagPage(queryParams) + list.value = page.list + total.value = page.total + } + }, 10000); + }); + + // 在组件卸载时清除定时器 + onBeforeUnmount(() => { + if (intervalId) { + clearInterval(intervalId); + } + }); </script> diff --git a/src/views/data/channel/opcua/tag/TagForm.vue b/src/views/data/channel/opcua/tag/TagForm.vue index 810fbff..adb01a8 100644 --- a/src/views/data/channel/opcua/tag/TagForm.vue +++ b/src/views/data/channel/opcua/tag/TagForm.vue @@ -36,7 +36,7 @@ <el-form-item label="是否启用" prop="enabled"> <el-select v-model="formData.enabled" placeholder="请选择"> <el-option - v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)" + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -54,8 +54,8 @@ </template> <script lang="ts" setup> import * as OpcuaTagApi from '@/api/data/channel/opcua/tag' - import { CommonEnabledBool } from '@/utils/constants' - import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict' + import {CommonEnabled} from '@/utils/constants' + import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict' defineOptions({name: 'OpcuaTagForm'}) @@ -70,7 +70,7 @@ device: undefined, tagName: undefined, dataType: undefined, - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, address: undefined, samplingRate: undefined }) @@ -134,7 +134,7 @@ device: undefined, tagName: undefined, dataType: undefined, - enabled: CommonEnabledBool.ENABLE, + enabled: CommonEnabled.ENABLE, address: undefined, samplingRate: undefined } diff --git a/src/views/data/channel/opcua/tag/index.vue b/src/views/data/channel/opcua/tag/index.vue index a535147..1ef3fc1 100644 --- a/src/views/data/channel/opcua/tag/index.vue +++ b/src/views/data/channel/opcua/tag/index.vue @@ -51,6 +51,21 @@ <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> + <el-button + type="warning" + plain + @click="handleImport" + v-hasPermi="['data:channel-opcua-tag:import']"> + <Icon icon="ep:upload" /> 导入 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['data:channel-opcua-tag:export']"> + <Icon icon="ep:download" />导出 + </el-button> </el-form-item> </el-form> </ContentWrap> @@ -89,8 +104,26 @@ align="center" > <template #default="scope"> - <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag> + <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag> <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column + prop="dataValue" + label="数据值" + header-align="center" + align="center" + :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}" + /> + <el-table-column + prop="quality" + label="数据质量" + header-align="center" + align="center" + > + <template #default="scope"> + <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag> + <el-tag v-else size="small">good</el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" min-width="110" fixed="right"> @@ -124,12 +157,16 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> <TagForm ref="formRef" @success="getList" /> + <TagImportForm ref="importFormRef" @success="getList" /> </el-drawer> </template> <script lang="ts" setup> import type { DrawerProps } from 'element-plus' - import * as OpcuaTagApi from "@/api/data/channel/opcua/tag"; + import * as OpcUaTagApi from "@/api/data/channel/opcua/tag"; import TagForm from './TagForm.vue' + import download from "@/utils/download"; + import {ref,reactive} from "vue"; + import TagImportForm from '../../common/tag/TagImportForm.vue' defineOptions({name: 'OpcuaTag'}) @@ -145,6 +182,7 @@ pageNo: 1, pageSize: 10, device: undefined, + deviceId: undefined, tagName: undefined, address: undefined }) @@ -155,7 +193,7 @@ const getList = async () => { loading.value = true try { - const page = await OpcuaTagApi.getOpcuaTagPage(queryParams) + const page = await OpcUaTagApi.getOpcuaTagPage(queryParams) list.value = page.list total.value = page.total } finally { @@ -187,7 +225,7 @@ // 删除的二次确认 await message.delConfirm() // 发起删除 - await OpcuaTagApi.deleteOpcuaTag(id) + await OpcUaTagApi.deleteOpcuaTag(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -196,10 +234,11 @@ } /** 打开弹窗 */ - const open = async (device?: string) => { + const open = async (deviceId?: string,device?: string) => { resetForm() drawer.value = true queryParams.device = device + queryParams.deviceId = deviceId if (device) { getList() } @@ -218,4 +257,27 @@ const handleClose = (done: () => void) => { drawer.value = false } + + /** tag导入 */ + const importFormRef = ref() + const handleImport = () => { + if(queryParams.device){ + importFormRef.value.open(queryParams.device, '/data/channel/opcua/tag/import',OpcUaTagApi.importOpcUaTagTemplate(), 'OpcUa', queryParams.device) + } + } + + /** 导出按钮操作 */ + const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await OpcUaTagApi.exportOpcUaTag(queryParams) + download.excel(data, 'OpcUa_' + queryParams.device + '_Tag列表.xlsx') + } catch { + } finally { + exportLoading.value = false + } + } </script> diff --git a/src/views/data/plan/category/CategoryForm.vue b/src/views/data/plan/category/CategoryForm.vue new file mode 100644 index 0000000..5bbbaad --- /dev/null +++ b/src/views/data/plan/category/CategoryForm.vue @@ -0,0 +1,141 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="100px" + > + <el-form-item label="上级菜单"> + <el-tree-select + v-model="formData.pid" + :data="categoryTree" + :default-expanded-keys="[0]" + :props="categoryTreeProps" + check-strictly + node-key="id" + /> + </el-form-item> + <el-form-item label="分类名称" prop="label"> + <el-input v-model="formData.label" clearable placeholder="请输入分类名称" /> + </el-form-item> + <el-form-item label="显示排序" prop="sort"> + <el-input-number v-model="formData.sort" :min="0" clearable controls-position="right" /> + </el-form-item> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as CategoryApi from '@/api/data/plan/category' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants' +import { defaultProps, handleTree } from '@/utils/tree' + +defineOptions({ name: 'PlanItemCategoryForm' }) + +const { wsCache } = useCache() +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const categoryTreeProps = ref({ + children: 'children', + label: 'label', + value: 'id', + isLeaf: 'leaf', + emitPath: false +}) + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + label: '', + pid: '0', + sort: Number(undefined) +}) +const formRules = reactive({ + label: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '分类顺序不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number, parentId?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + if (parentId) { + formData.value.parentId = parentId + } + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await CategoryApi.getCategory(id) + } finally { + formLoading.value = false + } + } + // 获得菜单列表 + await getTree() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as CategoryApi.PlanItemCategoryVO + if (formType.value === 'create') { + await CategoryApi.createCategory(data) + message.success(t('common.createSuccess')) + } else { + await CategoryApi.updateCategory(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + // 清空,从而触发刷新 + wsCache.delete(CACHE_KEY.ROLE_ROUTERS) + } +} + +/** 获取下拉框[上级菜单]的数据 */ +const categoryTree = ref<Tree[]>([]) // 树形结构 +const getTree = async () => { + categoryTree.value = [] + const res = await CategoryApi.getCategoryListAllSimple() + let menu: Tree = { id: '0', label: '主类目', children: [] } + menu.children = handleTree(res, 'id', 'pid') + categoryTree.value.push(menu) +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + label: '', + pid: '0', + sort: Number(undefined) + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/data/plan/category/index.vue b/src/views/data/plan/category/index.vue new file mode 100644 index 0000000..4dace49 --- /dev/null +++ b/src/views/data/plan/category/index.vue @@ -0,0 +1,159 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + ref="queryFormRef" + :inline="true" + :model="queryParams" + class="-mb-15px" + label-width="68px" + > + <el-form-item label="分类名称" prop="name"> + <el-input + v-model="queryParams.label" + class="!w-240px" + clearable + placeholder="请输入分类名称" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon class="mr-5px" icon="ep:refresh" /> + 重置 + </el-button> + <el-button + v-hasPermi="['data:plan-item-category:create']" + plain + type="primary" + @click="openForm('create')" + > + <Icon class="mr-5px" icon="ep:plus" /> + 新增 + </el-button> + <el-button plain type="danger" @click="toggleExpandAll"> + <Icon class="mr-5px" icon="ep:sort" /> + 展开/折叠 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-if="refreshTable" + v-loading="loading" + :data="list" + :default-expand-all="isExpandAll" + row-key="id" + > + <el-table-column :show-overflow-tooltip="true" label="分类名称" prop="label" width="300" /> + <el-table-column label="排序" prop="sort" width="60" /> + <el-table-column align="center" label="操作"> + <template #default="scope"> + <el-button + v-hasPermi="['data:plan-item-category:update']" + link + type="primary" + @click="openForm('update', scope.row.id)" + > + 修改 + </el-button> + <el-button + v-hasPermi="['data:plan-item-category:delete']" + link + type="danger" + @click="handleDelete(scope.row.id)" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <CategoryForm ref="formRef" @success="getList" /> +</template> +<script lang="ts" setup> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { handleTree } from '@/utils/tree' +import * as CategoryApi from '@/api/data/plan/category' +import CategoryForm from './CategoryForm.vue' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' + +defineOptions({ name: 'PlanItemCategory' }) + +const { wsCache } = useCache() +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const loading = ref(true) // 列表的加载中 +const list = ref<any>([]) // 列表的数据 +const queryParams = reactive({ + label: undefined +}) +const queryFormRef = ref() // 搜索的表单 +const isExpandAll = ref(false) // 是否展开,默认全部折叠 +const refreshTable = ref(true) // 重新渲染表格状态 + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await CategoryApi.getCategoryList(queryParams) + list.value = handleTree(data, 'id', 'pid') + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number, parentId?: number) => { + formRef.value.open(type, id, parentId) +} + +/** 展开/折叠操作 */ +const toggleExpandAll = () => { + refreshTable.value = false + isExpandAll.value = !isExpandAll.value + nextTick(() => { + refreshTable.value = true + }) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await CategoryApi.deleteCategory(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/data/plan/data/DataSetForm.vue b/src/views/data/plan/data/DataSetForm.vue new file mode 100644 index 0000000..5c02606 --- /dev/null +++ b/src/views/data/plan/data/DataSetForm.vue @@ -0,0 +1,129 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-form-item label="名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入名称" /> + </el-form-item> + <el-form-item label="数据源" prop="dataSource"> + <el-select v-model="formData.dataSource" clearable placeholder="请选择数据源"> + <el-option + v-for="item in dataSourceList" + :key="item.id" + :label="item.name" + :value="item.id + ''" + /> + </el-select> + </el-form-item> + <el-form-item label="查询语句" prop="querySql"> + <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="300" + show-word-limit :rows="6" spellcheck="false"/> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" maxlength="100" + show-word-limit/> + </el-form-item> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as DataSetApi from '@/api/data/plan/data' +import { CommonStatusEnum } from '@/utils/constants' +import * as DataSourceConfigApi from "@/api/infra/dataSourceConfig"; + +defineOptions({ name: 'PlanDataSetForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + name: '', + dataSource: '', + querySql: '', + remark: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], + dataSource: [{ required: true, message: '数据源不能为空', trigger: 'blur' }], + querySql: [{ required: true, message: '查询语句不能为空', trigger: 'change' }] +}) +const formRef = ref() // 表单 Ref +const dataSourceList = ref([] as DataSourceConfigApi.DataSourceConfigVO[]) + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + + // 加载数据源列表 + dataSourceList.value = await DataSourceConfigApi.getDataSourceConfigList() + + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await DataSetApi.getDataSet(id) + } finally { + formLoading.value = false + } + } + + +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as DataSetApi.DataSetVO + if (formType.value === 'create') { + await DataSetApi.createDataSet(data) + message.success(t('common.createSuccess')) + } else { + await DataSetApi.updateDataSet(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + dataSource: '', + querySql: '', + remark: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/data/plan/data/index.vue b/src/views/data/plan/data/index.vue new file mode 100644 index 0000000..94c58d5 --- /dev/null +++ b/src/views/data/plan/data/index.vue @@ -0,0 +1,158 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + ref="queryFormRef" + :inline="true" + :model="queryParams" + class="-mb-15px" + label-width="68px" + > + <el-form-item label="名称" prop="name"> + <el-input + v-model="queryParams.name" + class="!w-240px" + clearable + placeholder="请输入名称" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon class="mr-5px" icon="ep:refresh" /> + 重置 + </el-button> + <el-button + v-hasPermi="['data:plan-data-set:create']" + plain + type="primary" + @click="openForm('create')" + > + <Icon class="mr-5px" icon="ep:plus" /> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column align="center" label="名称" prop="name" show-overflow-tooltip /> + <el-table-column align="center" label="备注" prop="remark" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="180" + /> + <el-table-column align="center" label="操作"> + <template #default="scope"> + <el-button + v-hasPermi="['data:plan-data-set:update']" + link + type="primary" + @click="openForm('update', scope.row.id)" + > + 修改 + </el-button> + <el-button + v-hasPermi="['data:plan-data-set:delete']" + link + type="danger" + @click="handleDelete(scope.row.id)" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <DataSetForm ref="formRef" @success="getList" /> +</template> + +<script lang="ts" setup> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import * as DataSetApi from '@/api/data/plan/data' +import DataSetForm from './DataSetForm.vue' +import download from '@/utils/download' + +defineOptions({ name: 'PlanDataSet' }) + +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: '' +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +/** 查询字典类型列表 */ +const getList = async () => { + loading.value = true + try { + const data = await DataSetApi.getDataSetPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await DataSetApi.deleteDataSet(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/data/plan/item/ItemChart.vue b/src/views/data/plan/item/ItemChart.vue new file mode 100644 index 0000000..bb6d993 --- /dev/null +++ b/src/views/data/plan/item/ItemChart.vue @@ -0,0 +1,225 @@ +<template> + <el-dialog + title="采集值" + :close-on-click-modal="false" + width="50%" + v-model="visible" + > + <el-form + :inline="true" + :model="dataForm" + @keydown.enter="getDataList()" + > + <el-form-item> + <el-date-picker + v-model="dataForm.startTime" + type="datetime" + format="YYYY-MM-DD HH:mm:ss" + value-format="YYYY-MM-DD HH:mm:ss" + placeholder="选择日期时间" + /> + </el-form-item> + <el-form-item> + <el-date-picker + v-model="dataForm.endTime" + type="datetime" + format="YYYY-MM-DD HH:mm:ss" + value-format="YYYY-MM-DD HH:mm:ss" + placeholder="选择日期时间" + /> + </el-form-item> + <el-form-item> + <el-button @click="getDataList()">查询</el-button> + </el-form-item> + <el-form-item> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['data:point:export']" + > + <Icon icon="ep:download" />导出 + </el-button> + </el-form-item> + </el-form> + <div ref="chartDomPlan" class="result-chart"></div> + </el-dialog> +</template> + +<script lang="ts" setup> +import {ref} from 'vue'; +import * as echarts from 'echarts'; +import * as ItemApi from '@/api/data/plan/item' +import download from "@/utils/download"; +const message = useMessage() // 消息弹窗 +const visible = ref(false); +const chartDomPlan = ref(null); +let myChart = null; +const dataForm = ref({ + id: "", + itemNo: "", + itemName: "", + startTime: getYMDHMS(), + endTime: undefined, +}); +const queryParams = reactive({ + itemNos: [], + start: undefined, + end: undefined, +}) +function getYMDHMS() { + let timestamp = new Date().getTime(); + let time = new Date(timestamp - 1000 * 60 * 60 * 3); + let year = time.getFullYear(); + let month = (time.getMonth() + 1).toString(); + let date = time.getDate().toString(); + let hours = time.getHours().toString(); + let minute = time.getMinutes().toString(); + + if (month < 10) { + month = "0" + month; + } + if (date < 10) { + date = "0" + date; + } + if (hours < 10) { + hours = "0" + hours; + } + if (minute < 10) { + minute = "0" + minute; + } + + return ( + year + + "-" + + month + + "-" + + date + + " " + + hours + + ":" + + minute + + ":" + + "00" + ); +} +/** 打开弹窗 */ +const open = async (row: object) => { + visible.value = true + dataForm.value.id = row.id; + dataForm.value.itemNo = row.itemNo; + dataForm.value.itemName = row.itemName; + dataForm.value.startTime = getYMDHMS(); + dataForm.value.endTime = ""; + getDataList(); +} + +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +async function getDataList() { + visible.value = true; + if (dataForm.value.id) { + try { + queryParams.itemNos=[dataForm.value.itemNo]; + queryParams.start = dataForm.value.startTime; + queryParams.end = dataForm.value.endTime; + const data = await ItemApi.getPlanChart(queryParams) + let seriesData = [] + data.series.forEach(item => { + seriesData.push({ + name: item.name, + type: "line", + data: item.data, + showSymbol: true, + smooth: false, + lineStyle: { + normal: { + color: "#5B8FF9", + width: 1, + }, + }, + }); + }) + + myChart = echarts.init(chartDomPlan.value); + const option = { + title: { + text: dataForm.value.itemName, + top: 0, + left: "1%", + textStyle: { + fontSize: 14, + }, + }, + tooltip: { + trigger: "axis", + axisPointer: { + type: "line", + lineStyle: { + color: "#cccccc", + width: "1", + type: "dashed", + }, + }, + }, + legend: { + show: false, + top: 10, + }, + grid: { + top: 30, + left: "3%", + right: "5%", + bottom: 10, + containLabel: true, + }, + xAxis: { + type: "category", + boundaryGap: false, + data: data.categories, + }, + yAxis: { + type: "value", + }, + dataZoom: [ + { + type: "inside", + }, + ], + series: seriesData, + }; + myChart.setOption(option); + } catch (error) { + console.error(error) + } + } +} +/** 导出按钮操作 */ +const exportLoading = ref(false) +const handleExport = async () => { + queryParams.itemNos=[dataForm.value.itemNo]; + queryParams.start = dataForm.value.startTime; + queryParams.end = dataForm.value.endTime; + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await ItemApi.exportPlanValue(queryParams) + download.excel(data, dataForm.value.itemName +'.xls') + } catch { + } finally { + exportLoading.value = false + } +} +</script> +<style> +.el-select { + width: 100%; +} + +.result-chart { + height: 500px; +} +</style> diff --git a/src/views/data/plan/item/ItemForm.vue b/src/views/data/plan/item/ItemForm.vue new file mode 100644 index 0000000..97257c2 --- /dev/null +++ b/src/views/data/plan/item/ItemForm.vue @@ -0,0 +1,177 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row> + <el-col :span="12"> + <el-form-item label="指标编码" prop="itemNo"> + <el-input v-model="formData.itemNo" disabled/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="指标名称" prop="itemName"> + <el-input v-model="formData.itemName" placeholder="请输入指标名称"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="计划分类" prop="itemCategory"> + <el-tree-select + v-model="formData.itemCategory" + :data="categoryTree" + :default-expanded-keys="[0]" + :props="defaultProps" + check-strictly + node-key="id" + /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="时间粒度" prop="timeGranularity"> + <el-select v-model="formData.timeGranularity" placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.TIME_GRANULARITY)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="数据集" prop="dataSet"> + <el-select v-model="formData.dataSet" clearable placeholder="请选择数据集" @change="handleDataSetChange($event)"> + <el-option + v-for="item in dataSetList" + :key="item.id" + :label="item.name" + :value="item.id + ''" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" maxlength="100" + show-word-limit/> + </el-form-item> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict' +import * as ItemApi from '@/api/data/plan/item' +import { CommonStatusEnum } from '@/utils/constants' +import * as DataSetApi from "@/api/data/plan/data"; +import * as CategoryApi from "@/api/data/plan/category"; +import {defaultProps} from "@/api/data/plan/category"; +import {handleTree} from "@/utils/tree"; + +defineOptions({ name: 'PlanItemForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + itemNo: '', + itemName: '', + itemCategory: '', + timeGranularity: '', + dataSet: '', + remark: '', + status: undefined, +}) +const formRules = reactive({ + itemName: [{ required: true, message: '名称不能为空', trigger: 'blur' }], + itemCategory: [{ required: true, message: '分类不能为空', trigger: 'blur' }], + dataSet: [{ required: true, message: '数据集不能为空', trigger: 'change' }] +}) +const formRef = ref() // 表单 Ref +const dataSetList = ref([] as DataSetApi.DataSetVO[]) + +const categoryTree = ref<Tree[]>([]) +const getCategoryTree = async () => { + categoryTree.value = [] + const res = await CategoryApi.getCategoryListAllSimple() + let category: Tree = { id: 0, label: '主类目', children: [] } + category.children = handleTree(res, 'id', 'pid') + categoryTree.value.push(category) +} + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 加载数据源列表 + dataSetList.value = await DataSetApi.getDataSetList() + // 加载类别 + await getCategoryTree() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ItemApi.getItem(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as ItemApi.ItemVO + if (formType.value === 'create') { + await ItemApi.createItem(data) + message.success(t('common.createSuccess')) + } else { + await ItemApi.updateItem(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + dataSource: '', + querySql: '', + remark: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/data/plan/item/index.vue b/src/views/data/plan/item/index.vue new file mode 100644 index 0000000..cc85952 --- /dev/null +++ b/src/views/data/plan/item/index.vue @@ -0,0 +1,178 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" + label-width="68px"> + <el-form-item label="计划编码" prop="itemNo"> + <el-input v-model="queryParams.itemNo" class="!w-200px" clearable + placeholder="请输入计划编码" + @keyup.enter="handleQuery"/> + </el-form-item> + <el-form-item label="计划名称" prop="itemName"> + <el-input v-model="queryParams.itemName" class="!w-200px" clearable + placeholder="请输入计划名称" + @keyup.enter="handleQuery"/> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon class="mr-5px" icon="ep:refresh"/> + 重置 + </el-button> + <el-button + v-hasPermi="['data:ind-item:create']" + plain + type="primary" + @click="openForm('create')" + > + <Icon class="mr-5px" icon="ep:plus"/> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column prop="itemNo" label="计划编码" header-align="center" align="center" + min-width="80"/> + <el-table-column prop="itemName" label="计划名称" header-align="center" align="center" + min-width="120"/> + <el-table-column prop="itemCategoryName" label="计划分类" header-align="center" align="center" + min-width="100"/> + <el-table-column prop="timeGranularity" label="时间粒度" header-align="center" align="center" + min-width="40"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.TIME_GRANULARITY" :value="scope.row.timeGranularity"/> + </template> + </el-table-column> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="180"/> + <el-table-column align="center" label="操作"> + <template #default="scope"> + <el-button + v-hasPermi="['data:ind-item:update']" + link + type="primary" + @click="openForm('update', scope.row.id)"> + 修改 + </el-button> + <el-button link size="mini" type="primary" @click="chartHandle(scope.row)">数据 + </el-button> + <el-button + v-hasPermi="['data:ind-item:delete']" + link + type="danger" + @click="handleDelete(scope.row.id)"> + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <ItemForm ref="formRef" @success="getList"/> + + <!-- 表单弹窗:计划数据 --> + <ItemChart ref="chartView"/> +</template> + +<script lang="ts" setup> +import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict' +import {dateFormatter} from '@/utils/formatTime' +import ItemForm from './ItemForm.vue' +import download from '@/utils/download' +import * as ItemApi from '@/api/data/plan/item' +import * as CategoryApi from "@/api/data/plan/category"; +import ItemChart from "./ItemChart.vue"; + +defineOptions({name: 'PlanItem'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 +const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[]) +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + itemNo: '', + itemName: '', + itemCategory: '' +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +/** 查询字典类型列表 */ +const getList = async () => { + loading.value = true + try { + const data = await ItemApi.getItemPage(queryParams) + list.value = data.list + total.value = data.total + dataCategoryList.value = await CategoryApi.getCategoryListAllSimple() + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 查看数据操作 */ +const chartView = ref() +const chartHandle = (raw: object) => { + chartView.value.open(raw) +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ItemApi.deleteItem(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/data/point/DaPointChart.vue b/src/views/data/point/DaPointChart.vue index ddc9a2d..305e0e7 100644 --- a/src/views/data/point/DaPointChart.vue +++ b/src/views/data/point/DaPointChart.vue @@ -10,21 +10,21 @@ :model="dataForm" @keydown.enter="getDataList()" > - <el-form-item> + <el-form-item label="开始时间"> <el-date-picker + size="mini" v-model="dataForm.startTime" type="datetime" - value-format="yyyy-MM-dd HH:mm:ss" - placeholder="选择日期时间" - /> + :clearable="false" + placeholder="选择日期时间"/> </el-form-item> - <el-form-item> + <el-form-item label="结束时间"> <el-date-picker + size="mini" v-model="dataForm.endTime" type="datetime" - value-format="yyyy-MM-dd HH:mm:ss" - placeholder="选择日期时间" - /> + :clearable="false" + placeholder="选择日期时间"/> </el-form-item> <el-form-item> <el-button @click="getDataList()">查询</el-button> @@ -49,67 +49,39 @@ import {ref} from 'vue'; import * as echarts from 'echarts'; import * as DaPoint from '@/api/data/da/point/daPointChart' + import {getYMDHMS} from "@/utils/dateUtil" import download from "@/utils/download"; const message = useMessage() // 消息弹窗 const visible = ref(false); const chartDom = ref(null); let myChart = null; - const dataForm = ref({ - id: "", - pointNo: "", - pointName: "", - pointTypeName: "", - startTime: getYMDHMS(), - endTime: undefined, - }); const queryParams = reactive({ codes: [], startDate: undefined, endDate: undefined, }) - function getYMDHMS() { - let timestamp = new Date().getTime(); - let time = new Date(timestamp - 1000 * 60 * 30); - let year = time.getFullYear(); - let month = (time.getMonth() + 1).toString(); - let date = time.getDate().toString(); - let hours = time.getHours().toString(); - let minute = time.getMinutes().toString(); + const chartParams = reactive({ + pointNos:[], + start : undefined, + end : undefined, + }) + const dataForm = ref({ + id: "", + pointNo: "", + pointName: "", + pointTypeName: "", + startTime: getYMDHMS(new Date() - 1000 * 60 * 60), + endTime: getYMDHMS(new Date()), + }); - if (month < 10) { - month = "0" + month; - } - if (date < 10) { - date = "0" + date; - } - if (hours < 10) { - hours = "0" + hours; - } - if (minute < 10) { - minute = "0" + minute; - } - return ( - year + - "-" + - month + - "-" + - date + - " " + - hours + - ":" + - minute + - ":" + - "00" - ); - } /** 打开弹窗 */ const open = async (row: object) => { visible.value = true dataForm.value.id = row.id; dataForm.value.pointNo = row.pointNo; dataForm.value.pointName = row.pointName; - dataForm.value.startTime = getYMDHMS(); - dataForm.value.endTime = ""; + dataForm.value.startTime = getYMDHMS(new Date(this.dataForm.startTime) - 1000 * 60 * 60); + dataForm.value.endTime = getYMDHMS(new Date(this.dataForm.endTime) - 1000 * 60 * 60), getDataList(); } @@ -120,8 +92,8 @@ if (dataForm.value.id) { try { queryParams.codes=[dataForm.value.pointNo]; - queryParams.startDate = dataForm.value.startTime; - queryParams.endDate = dataForm.value.endTime; + queryParams.startDate = getYMDHMS(new Date(this.dataForm.startTime) - 1000 * 60 * 60); + queryParams.endDate = getYMDHMS(new Date(this.dataForm.endTime) - 1000 * 60 * 60); const data = await DaPoint.getPointDaChart(queryParams) let seriesData = [] data.series.forEach(item => { @@ -196,9 +168,9 @@ /** 导出按钮操作 */ const exportLoading = ref(false) const handleExport = async () => { - queryParams.pointNos=[dataForm.value.pointNo]; - queryParams.start = dataForm.value.startTime; - queryParams.end = dataForm.value.endTime; + chartParams.pointNos=[dataForm.value.pointNo]; + chartParams.start = dataForm.value.startTime; + chartParams.end = dataForm.value.endTime; try { // 导出的二次确认 await message.exportConfirm() diff --git a/src/views/data/point/DaPointForm.vue b/src/views/data/point/DaPointForm.vue index ac36580..0279054 100644 --- a/src/views/data/point/DaPointForm.vue +++ b/src/views/data/point/DaPointForm.vue @@ -310,7 +310,7 @@ pointNo: '' }]) const queryParams = reactive({ - pointType: "MEASURE" + pointTypes: "MEASURE,CONSTANT", }) const operatorList = ref(['+', '-', '*', '/', '&', '|', '!', '>', '<']) const formData = ref({ diff --git a/src/views/data/point/index.vue b/src/views/data/point/index.vue index af108ba..1b635f8 100644 --- a/src/views/data/point/index.vue +++ b/src/views/data/point/index.vue @@ -157,6 +157,7 @@ import DaPointForm from './DaPointForm.vue' import DaPointChart from './DaPointChart.vue' import * as UserApi from "@/api/system/user"; +import PointImportForm from './PointImportForm.vue' defineOptions({name: 'DataPoint'}) diff --git a/src/views/model/chart/ChartForm.vue b/src/views/model/chart/ChartForm.vue new file mode 100644 index 0000000..3a80398 --- /dev/null +++ b/src/views/model/chart/ChartForm.vue @@ -0,0 +1,111 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row :gutter="20"> + <el-col :span="20"> + <el-form-item label="图表名称" prop="chartName"> + <el-input v-model="formData.chartName" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="20"> + <el-form-item label="图表编码" prop="chartCode"> + <el-input v-model="formData.chartCode" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import * as ChartApi from '@/api/model/mpk/chart' + +defineOptions({ name: 'ChartForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + chartName: undefined, + chartCode: undefined, +}) +const formRules = reactive({ + chartName: [{ required: true, message: '不能为空', trigger: 'blur' }], + chartCode: [{ required: true, message: '不能为空', trigger: 'blur' }], +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: string) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ChartApi.get(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value + if (formType.value === 'create') { + await ChartApi.create(data) + message.success(t('common.createSuccess')) + } else { + await ChartApi.update(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + chartName: undefined, + chartCode: undefined, + paramName: undefined, + paramCode: undefined, + paramValue: undefined, + remark: undefined, + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/model/chart/index.vue b/src/views/model/chart/index.vue new file mode 100644 index 0000000..6fdb37f --- /dev/null +++ b/src/views/model/chart/index.vue @@ -0,0 +1,177 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="图表名称" prop="chartName"> + <el-input + v-model="queryParams.chartName" + placeholder="请输入图表名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item label="图表编码" prop="chartCode"> + <el-input + v-model="queryParams.chartCode" + placeholder="请输入图表编码" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['model:chart:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="chartName" label="图表名称"/> + <el-table-column prop="chartCode" label="图表编码"/> + <el-table-column prop="createTime" :formatter="dateFormatter" label="创建时间"/> + <el-table-column label="操作" align="center" width="200px"> + <template #default="scope"> + <div class="flex items-center justify-center"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['model:chart:update']" + > + 编辑 + </el-button> + <el-button + link + type="primary" + @click="openChartParam(scope.row.id)" + v-hasPermi="['model:chart:update']" + > + 参数 + </el-button> + <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['model:chart:delete']"> + 删除 + </el-button> + </div> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <ChartForm ref="formRef" @success="getList" /> + + <!-- 分组列表 --> + <ChartParam ref="ChartParamRef" /> + +</template> +<script lang="ts" setup> +import {dateFormatter} from '@/utils/formatTime' +import * as ChartApi from '@/api/model/mpk/chart' +import ChartForm from './ChartForm.vue' +import ChartParam from './param/index.vue' + + +defineOptions({name: 'Chart'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +const queryParams = reactive({ + page: 1, + limit: 10, + chartName: '', + chartCode: '' +}) +const queryFormRef = ref() // 搜索的表单 + +const getList = async () => { + loading.value = true + try { + const data = await ChartApi.getPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: string) => { + formRef.value.open(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: string) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ChartApi.del(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} + +/** List操作 */ +const ChartParamRef = ref() +const openChartParam = (id?: string) => { + ChartParamRef.value.open(id) +} + +/** 初始化 **/ +onMounted(async () => { + await getList() +}) +</script> diff --git a/src/views/model/chart/param/ChartParamForm.vue b/src/views/model/chart/param/ChartParamForm.vue new file mode 100644 index 0000000..7d27d88 --- /dev/null +++ b/src/views/model/chart/param/ChartParamForm.vue @@ -0,0 +1,130 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="参数名称" prop="paramName"> + <el-input v-model="formData.paramName" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="参数编码" prop="paramCode"> + <el-input v-model="formData.paramCode" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="24"> + <el-form-item label="参数值" prop="paramValue"> + <el-input v-model="formData.paramValue" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="24"> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import * as ChartParamApi from '@/api/model/mpk/chartParam' +import {deleteIcon} from "@/api/model/mpk/icon"; + +defineOptions({ name: 'ChartParamForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + chartId: undefined, + paramName: undefined, + paramCode: undefined, + paramValue: undefined, + remark: undefined, +}) +const formRules = reactive({ + paramName: [{ required: true, message: '不能为空', trigger: 'blur' }], + paramCode: [{ required: true, message: '不能为空', trigger: 'blur' }], + paramValue: [{ required: true, message: '不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: string, chartId?: string) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + if (chartId) { + formData.value.chartId = chartId + } + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ChartParamApi.get(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value + if (formType.value === 'create') { + await ChartParamApi.create(data) + message.success(t('common.createSuccess')) + } else { + await ChartParamApi.update(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + chartCode: undefined, + paramName: undefined, + paramCode: undefined, + paramValue: undefined, + remark: undefined, + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/model/chart/param/index.vue b/src/views/model/chart/param/index.vue new file mode 100644 index 0000000..8714213 --- /dev/null +++ b/src/views/model/chart/param/index.vue @@ -0,0 +1,183 @@ +<template> + <el-drawer + v-model="drawer" + size="40%" + title="参数列表" + direction="rtl" + :before-close="handleClose" + > + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="参数名称" prop="paramName"> + <el-input + v-model="queryParams.paramName" + placeholder="请输入参数名称" + clearable + class="!w-240px" + /> + </el-form-item> +<!-- <el-form-item label="参数编码" prop="paramCode">--> +<!-- <el-input--> +<!-- v-model="queryParams.paramCode"--> +<!-- placeholder="请输入"--> +<!-- clearable--> +<!-- class="!w-240px"--> +<!-- />--> +<!-- </el-form-item>--> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="paramName" label="参数名称"/> + <el-table-column prop="paramCode" label="参数编码"/> + <el-table-column prop="paramValue" label="参数值"/> + <el-table-column label="操作" align="center" width="150px"> + <template #default="scope"> + <div class="flex items-center justify-center"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + > + 编辑 + </el-button> + <el-button link type="danger" @click="handleDelete(scope.row.id)"> + 删除 + </el-button> + </div> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <ChartParamForm ref="formRef" @success="getList" /> + </el-drawer> +</template> +<script lang="ts" setup> +import {dateFormatter} from '@/utils/formatTime' +import * as ChartParamApi from '@/api/model/mpk/chartParam' +import ChartParamForm from './ChartParamForm.vue' + +import type {DrawerProps} from "element-plus"; + +defineOptions({name: 'ChartParam'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 + +const drawer = ref(false) +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +const queryParams = reactive({ + page: 1, + limit: 10, + chartId: undefined, + paramName: undefined, + paramCode: undefined, +}) +const queryFormRef = ref() // 搜索的表单 + +const getList = async () => { + loading.value = true + try { + const data = await ChartParamApi.getPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: string) => { + formRef.value.open(type, id, queryParams.chartId) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: string) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ChartParamApi.del(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} + +/** 打开弹窗 */ +const open = async (chartId?: string) => { + resetForm() + drawer.value = true + queryParams.chartId = chartId + if (chartId) { + getList() + } +} +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +/** 重置表单 */ +const resetForm = () => { + queryParams.chartId = '' + queryParams.name = '' +} + +const handleClose = (done: () => void) => { + drawer.value = false +} +</script> diff --git a/src/views/model/mpk/file/MpkGenerator.vue b/src/views/model/mpk/file/MpkGenerator.vue index 2756e05..dbd3687 100644 --- a/src/views/model/mpk/file/MpkGenerator.vue +++ b/src/views/model/mpk/file/MpkGenerator.vue @@ -18,7 +18,7 @@ </el-form-item> </el-form> <div style="width: 100%;display: flex;flex-direction: row;justify-content: end;margin-top: 16px"> - <el-button @click="generatorCode()" type="primary">生成</el-button> + <el-button :loading="loading" @click="generatorCode()" type="primary">生成</el-button> </div> </Dialog> </template> @@ -54,11 +54,19 @@ } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + // 代码生成loading + const loading = ref(false) /** 提交表单 */ const generatorCode = async () => { - const data = await MpkApi.generatorCode(formData.value) - download.zip(data, formData.value.zipFileName) - dialogVisible.value = false + try { + loading.value = true + const data = await MpkApi.generatorCode(formData.value) + download.zip(data, formData.value.zipFileName) + dialogVisible.value = true + }finally { + dialogVisible.value = false + } + } /** 重置表单 */ const resetForm = () => { diff --git a/src/views/model/mpk/file/MpkRun.vue b/src/views/model/mpk/file/MpkRun.vue index 6b0111b..d677062 100644 --- a/src/views/model/mpk/file/MpkRun.vue +++ b/src/views/model/mpk/file/MpkRun.vue @@ -233,6 +233,7 @@ const formRef = ref() // 运行 const modelRun = async () => { + modelRunResult.value = '' // 校验表单 if (!formRef) return const valid = await formRef.value.validate() @@ -258,7 +259,6 @@ } modelRunResult.value = await MpkApi.modelRun(data) - modelRunloading.value = false message.success('运行成功') } finally { modelRunloading.value = false diff --git a/src/views/model/pre/item/MmPredictItemForm.vue b/src/views/model/pre/item/MmPredictItemForm.vue index 4990942..9e5fb9f 100644 --- a/src/views/model/pre/item/MmPredictItemForm.vue +++ b/src/views/model/pre/item/MmPredictItemForm.vue @@ -170,7 +170,7 @@ </el-form-item> </el-col> <el-col :span="8"> - <el-form-item label="关联模型"> + <el-form-item label="关联项目"> <el-select v-model="dataForm.mmPredictModel.mpkprojectid" placeholder="请选择"> <el-option v-for="item in mpkProjectList" -- Gitblit v1.9.3