Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into ohos

# Conflicts:
#	src/views/main/components/header.vue
jxq 2 месяцев назад
Родитель
Сommit
3341d64470

+ 3 - 1
src/App.vue

@@ -9,7 +9,9 @@
     >
       <router-view v-slot="{ Component }">
         <transition name="fade" mode="out-in" appear>
-          <component :is="Component" />
+          <keep-alive :include="['MainPage']">
+            <component :is="Component" />
+          </keep-alive>
         </transition>
       </router-view>
     </el-watermark>

+ 13 - 2
src/api/project/index.ts

@@ -102,7 +102,7 @@ export function getProjectGlobalConfig(engineeringId: string): AxiosPromise {
   });
 }
 
-// 新增 测试模块 
+// 新增 测试模块
 export function addTestModule(data: object): AxiosPromise {
   return request({
     url: "/api/v1/test/engrProject/add",
@@ -118,4 +118,15 @@ export function delTestModule(data: object): AxiosPromise {
     method: "post",
     data: data,
   });
-}
+}
+
+//复制项目
+export function copyProject(id: string): AxiosPromise {
+  return request({
+    url: "/api/v1/test/engrProject/copyProject",
+    method: "post",
+    data: {
+      id: id,
+    },
+  });
+}

+ 156 - 0
src/api/system/dict/index.ts

@@ -0,0 +1,156 @@
+import request from "@/utils/request";
+import { AxiosPromise } from "axios";
+import {
+  DictTypeQuery,
+  DictTypePageResult,
+  DictTypeForm,
+  DictQuery,
+  DictForm,
+  DictPageResult,
+} from "./types";
+
+/**
+ * 字典类型分页列表
+ *
+ * @param queryParams
+ */
+export function getDictTypePage(queryParams: object): AxiosPromise<any> {
+  return request({
+    url: "/api/v1/sys/dictType/page",
+    method: "post",
+    data: queryParams,
+  });
+}
+
+/**
+ * 字典类型表单数据
+ *
+ * @param id
+ */
+export function getDictTypeForm(id: number): AxiosPromise<DictTypeForm> {
+  return request({
+    url: "/api/v1/dict/types/" + id + "/form",
+    method: "get",
+  });
+}
+
+/**
+ * 新增字典类型
+ *
+ * @param data
+ */
+export function addDictType(data: DictTypeForm) {
+  return request({
+    url: "/api/v1/sys/dictType/add",
+    method: "post",
+    data: data,
+  });
+}
+
+/**
+ * 修改字典类型
+ *
+ * @param id
+ * @param data
+ */
+export function updateDictType(data: DictTypeForm) {
+  return request({
+    url: "/api/v1/sys/dictType/update",
+    method: "post",
+    data: data,
+  });
+}
+
+/**
+ * 删除字典类型
+ */
+export function deleteDictTypes(id: string) {
+  return request({
+    url: "/api/v1/sys/dictType/del",
+    method: "post",
+    data: { id: id },
+  });
+}
+
+/**
+ * 获取字典类型的数据项
+ *
+ * @param typeCode 字典类型编码
+ */
+export function getDictOptions(typeCode: string): AxiosPromise<OptionType[]> {
+  return request({
+    url: "/api/v1/dict/" + typeCode + "/options",
+    method: "get",
+  });
+}
+
+/**
+ * 字典分页列表
+ */
+export function getDictPage(queryParams: object): AxiosPromise<any> {
+  return request({
+    url: "/api/v1/sys/dictData/page",
+    method: "post",
+    data: queryParams,
+  });
+}
+
+/**
+ * 获取字典表单数据
+ *
+ * @param id
+ */
+export function getDictFormData(id: number): AxiosPromise<DictForm> {
+  return request({
+    url: "/api/v1/dict/" + id + "/form",
+    method: "get",
+  });
+}
+
+/**
+ * 新增字典
+ *
+ * @param data
+ */
+export function addDict(data: DictForm) {
+  return request({
+    url: "/api/v1/sys/dictData/add",
+    method: "post",
+    data: data,
+  });
+}
+
+/**
+ * 修改字典项
+ *
+ * @param id
+ * @param data
+ */
+export function updateDict(data: DictForm) {
+  return request({
+    url: "/api/v1/sys/dictData/update",
+    method: "post",
+    data: data,
+  });
+}
+
+/**
+ * 删除字典
+ *
+ * @param ids 字典项ID,多个以英文逗号(,)分割
+ */
+export function deleteDict(ids: Array<string>) {
+  return request({
+    url: "/api/v1/sys/dictData/batch-del",
+    method: "post",
+    data: { ids: ids },
+  });
+}
+
+
+export function queryDictDataByType(str: string) {
+  return request({
+    url: "/api/v1/sys/dictData/queryByType/" + str,
+    method: "get",
+  });
+}

+ 144 - 0
src/api/system/dict/types.ts

@@ -0,0 +1,144 @@
+/**
+ * 字典类型查询参数
+ */
+export interface DictTypeQuery extends PageQuery {
+  /**
+   * 关键字(字典类型名称/编码)
+   */
+  keywords?: string;
+}
+
+/**
+ * 字典类型分页对象
+ */
+export interface DictTypePageVO {
+  /**
+   * 字典类型ID
+   */
+  id: number;
+  /**
+   * 类型编码
+   */
+  code: string;
+  /**
+   * 类型名称
+   */
+  name: string;
+  /**
+   * 状态(1:启用;0:禁用)
+   */
+  status?: number;
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+/**
+ * 字典分页项类型声明
+ */
+export type DictTypePageResult = PageResult<DictTypePageVO[]>;
+
+/**
+ * 字典表单类型声明
+ */
+export interface DictTypeForm {
+  /**
+   * 字典类型ID
+   */
+  id?: number;
+  /**
+   * 类型名称
+   */
+  dictName?: string;
+  /**
+   * 类型编码
+   */
+  dictType?: string;
+  /**
+   * 类型状态:0-禁用;1-启用
+   */
+  state: number;
+  /**
+   * 备注
+   */
+  remark?: string;
+  // 字典类别 0 用户字典 1 系统字典
+  type?: string;
+}
+
+/**
+ * 字典查询参数
+ */
+export interface DictQuery extends PageQuery {
+  /**
+   * 字典项名称
+   */
+  name?: string;
+  /**
+   * 字典类型编码
+   */
+  typeCode?: string;
+}
+
+/**
+ * 字典分页对象
+ */
+export interface DictPageVO {
+  /**
+   * 字典ID
+   */
+  id?: number;
+  /**
+   * 字典名称
+   */
+  name?: string;
+  /**
+   * 状态(1:启用;0:禁用)
+   */
+  status?: number;
+  /**
+   * 字典值
+   */
+  value?: string;
+}
+
+/**
+ * 字典分页
+ */
+export type DictPageResult = PageResult<DictPageVO[]>;
+
+/**
+ * 字典表单
+ */
+export interface DictForm {
+  /**
+   * 字典ID
+   */
+  id?: number;
+  /**
+   * 字典名称
+   */
+  dictLabel?: string;
+  /**
+   * 排序
+   */
+  dictSort?: number;
+  /**
+   * 状态(1:启用;0:禁用)
+   */
+  state?: number;
+  /**
+   * 类型编码
+   */
+  dictCode?: string;
+  /**
+   * 值
+   */
+  dictValue?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}

+ 37 - 24
src/components/hjflow/src/nodes/com/basic.vue

@@ -3,55 +3,69 @@ import {
   CurrentHeaderOperationProvideName,
   GrandparentMethod,
   HJInterNodeData,
-  HJMethodName, HJNodeData,
-  HJPosition
-} from '../../types/comTypes';
-import {Handle} from '@vue-flow/core'
-import {Position} from '@vue-flow/core';
-import {inject, watch} from "vue";
+  HJMethodName,
+  HJNodeData,
+  HJPosition,
+} from "../../types/comTypes";
+import { Handle } from "@vue-flow/core";
+import { Position } from "@vue-flow/core";
+import { inject, watch } from "vue";
 const props = defineProps<HJInterNodeData>();
 
 //  'basic-box--selected': props.data.isSelected || (currentHeaderOperationNode && currentHeaderOperationNode.id === props.id) 如果是选中或者正在通过header编辑节点信息的样式
-const currentHeaderOperationNode = inject<HJNodeData | null>(CurrentHeaderOperationProvideName)
-
-
+const currentHeaderOperationNode = inject<HJNodeData | null>(
+  CurrentHeaderOperationProvideName
+);
 </script>
 
 <template>
   <div
-      class="basic-box"
-      :class="{
+    class="basic-box"
+    :class="{
       [`basic-box--${props.type}`]: props.type,
-      'basic-box--selected': props.data.isSelected || (currentHeaderOperationNode && currentHeaderOperationNode.id === props.id),
+      'basic-box--selected':
+        props.data.isSelected ||
+        (currentHeaderOperationNode &&
+          currentHeaderOperationNode.id === props.id),
       'basic-box--dragging': props.data.isDragging,
     }"
   >
-
     <slot name="header"></slot>
 
-    <slot name="default">
-    </slot>
+    <slot name="default"> </slot>
 
     <!--    这里是handle的操作-->
     <div v-if="props.sourcePosition && props.targetPosition">
-      <Handle class="handle-style"  type="source" :position="props.sourcePosition"/>
-      <Handle class="handle-style"  type="target" :position="props.targetPosition"/>
+      <Handle
+        class="handle-style"
+        type="source"
+        :position="props.sourcePosition"
+      />
+      <Handle
+        class="handle-style"
+        type="target"
+        :position="props.targetPosition"
+      />
     </div>
     <div v-else-if="props.data.handles && props.data.handles.length">
-      <Handle class="handle-style" v-for="(handle, index) in props.data.handles"  :type="handle.type"
-              :position="handle.position" :style="handle.style"/>
+      <Handle
+        class="handle-style"
+        v-for="(handle, index) in props.data.handles"
+        :type="handle.type"
+        :position="handle.position"
+        :style="handle.style"
+      />
     </div>
     <div v-else>
-      <Handle class="handle-style"  type="target" :position="Position.Top"/>
-      <Handle class="handle-style"  type="source" :position="Position.Bottom"/>
+      <Handle class="handle-style" type="target" :position="Position.Top" />
+      <Handle class="handle-style" type="source" :position="Position.Bottom" />
     </div>
-
   </div>
 </template>
 
 <style scoped lang="less">
 .basic-box {
-  width: 200px;
+  width: 220px;
   height: 60px;
   border: 2px solid #625454;
   border-radius: 5px;
@@ -64,7 +78,6 @@ const currentHeaderOperationNode = inject<HJNodeData | null>(CurrentHeaderOperat
   height: 10px;
 }
 
-
 //可以根据type类型来设置不同的Node的样式
 .basic-box--custom {
   border-style: dashed;

+ 18 - 12
src/components/hjflow/src/nodes/com/operationHeader.vue

@@ -1,25 +1,31 @@
 <script setup lang="ts">
-
-import {Edit} from "@element-plus/icons-vue";
-import {GrandparentMethod, HJInterNodeData, HJMethodName, HJMethodProvideName} from "../../types/comTypes";
-import {inject, markRaw} from "vue";
+import { Edit } from "@element-plus/icons-vue";
+import {
+  GrandparentMethod,
+  HJInterNodeData,
+  HJMethodName,
+  HJMethodProvideName,
+} from "../../types/comTypes";
+import { inject, markRaw } from "vue";
 const props = defineProps<HJInterNodeData>();
 
-
-const editMethod = inject<GrandparentMethod>(HJMethodProvideName)
+const editMethod = inject<GrandparentMethod>(HJMethodProvideName);
 
 const editClick = () => {
-  editMethod && editMethod( HJMethodName.EditNode,JSON.parse(JSON.stringify(markRaw(props))))
-}
-
+  editMethod &&
+    editMethod(
+      HJMethodName.EditNode,
+      JSON.parse(JSON.stringify(markRaw(props)))
+    );
+};
 </script>
 
 <template>
   <div class="box-header">
-    <div> {{ props?.data?.label ?? "" }}</div>
+    <div>{{ props?.data?.label ?? "" }}</div>
     <div class="right-btns">
-      <el-icon :size="18" class="right-icon">
-        <Edit  @click.prevent="editClick"/>
+      <el-icon :size="20" class="right-icon">
+        <Edit @click.prevent="editClick" />
       </el-icon>
     </div>
   </div>

+ 1 - 1
src/components/hjflow/src/panel/index.vue

@@ -34,7 +34,7 @@ const onAddNode = (type: string) => {
       style="width: 80px"
       @click="() => emits('saveTemplate')"
       v-if="props.funNames.includes('saveTemplate')"
-      >保存模版</el-button
+      >保存</el-button
     >
     <Back @click="() => emits('back')" v-if="props.funNames.includes('back')" />
     <add-node

+ 7 - 1
src/router/index.ts

@@ -26,6 +26,7 @@ export const constantRoutes: RouteRecordRaw[] = [
     component: () => import("@/views/main/main.vue"),
     meta: { hidden: true },
     redirect: "/main/home",
+    name: "MainPage",
     children: [
       {
         path: "home",
@@ -33,7 +34,7 @@ export const constantRoutes: RouteRecordRaw[] = [
       },
       {
         path: "run-test/:engineerId",
-        name: "RunTestPage",
+        name: "RunTesting",
         component: () => import("@/views/modules/runTest/run-test.vue"),
       },
       {
@@ -72,6 +73,11 @@ export const constantRoutes: RouteRecordRaw[] = [
     meta: { hidden: true },
   },
   {
+    path: "/dictionary",
+    component: () => import("@/views/modules/dictionary/index.vue"),
+    meta: { hidden: true },
+  },
+  {
     path: "/detp-management",
     component: () =>
       import("@/views/modules/person-manager/com/dept-manage.vue"),

+ 1 - 0
src/styles/reset.scss

@@ -19,6 +19,7 @@ html {
   line-height: 1.5;
   tab-size: 4;
   text-size-adjust: 100%;
+  min-width: 1250px;
 }
 
 body {

+ 5 - 0
src/views/main/components/header.vue

@@ -18,6 +18,7 @@
     >
       <div class="loginOut" @click="deviceResourceFun">仪器资源</div>
       <div class="loginOut" @click="goToManange">部门管理</div>
+      <div class="loginOut" @click="gotoDictPage">字典管理</div>
       <div class="loginOut" @click="userCenter">修改密码</div>
       <!--      <div class="loginOut" @click="loginOutFun">退出登录</div>-->
       <div class="loginOut" @click="toExitApp">退出系统</div>
@@ -60,6 +61,10 @@ const toExitApp = () => {
     window.openHarmonyBridge.exitApp("");
   }
 };
+
+const gotoDictPage = () => {
+  router.push("/dictionary");
+};
 </script>
 
 <style scoped lang="scss">

+ 7 - 2
src/views/main/components/layout.vue

@@ -2,9 +2,14 @@
 
 <template>
   <div class="layout">
-    <router-view v-slot="{ Component }">
+    <router-view v-slot="{ Component, route }">
       <transition name="fade" appear>
-        <component :is="Component" />
+        <keep-alive :include="['RunTesting']">
+          <component
+            :is="Component"
+            :key="route.path + (route?.query?._refresh || '')"
+          />
+        </keep-alive>
       </transition>
     </router-view>
   </div>

+ 1 - 1
src/views/main/components/menu.vue

@@ -69,7 +69,7 @@ const handleMenuClick = (menu: MenuItem) => {
         return;
       }
       router.push({
-        name: "RunTestPage",
+        name: "RunTesting",
         params: { engineerId: currentExecutionId.value },
       });
     } else {

+ 4 - 0
src/views/main/main.vue

@@ -2,6 +2,10 @@
 import Menu from "./components/menu.vue";
 import Layout from "./components/layout.vue";
 import Header from "./components/header.vue";
+
+defineOptions({
+  name: "MainPage",
+});
 </script>
 
 <template>

+ 337 - 0
src/views/modules/dictionary/components/dict-item.vue

@@ -0,0 +1,337 @@
+<!-- 字典数据 -->
+<script setup lang="ts">
+import {
+  getDictPage,
+  getDictFormData,
+  addDict,
+  updateDict,
+  deleteDict,
+} from "@/api/system/dict";
+import { DictPageVO, DictForm, DictQuery } from "@/api/system/dict/types";
+import ButtonPermKeys from "@/common/configs/buttonPermission";
+
+defineOptions({
+  name: "DictData",
+  inheritAttrs: false,
+});
+
+const props = defineProps({
+  typeCode: {
+    type: String,
+    default: () => {
+      return "";
+    },
+  },
+  typeName: {
+    type: String,
+    default: () => {
+      return "";
+    },
+  },
+});
+
+watch?.(
+  () => props.typeCode,
+  (newVal: string) => {
+    queryParams.dictCode = newVal;
+    formData.dictCode = newVal;
+    resetQuery();
+  }
+);
+
+const queryFormRef = ref(ElForm);
+const dataFormRef = ref(ElForm);
+
+const loading = ref(false);
+const ids = ref<string[]>([]);
+const total = ref(0);
+
+const queryParams = reactive<any>({
+  pageNo: 1,
+  pageSize: 10,
+  dictCode: props.typeCode,
+});
+
+const dictList = ref<DictPageVO[]>();
+
+const dialog = reactive({
+  title: "",
+  visible: false,
+});
+
+const formData = reactive<any>({
+  state: 0,
+  dictCode: props.typeCode,
+  dictSort: 1,
+});
+
+const rules = reactive({
+  name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
+  value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
+});
+
+/**
+ * 查询
+ */
+function handleQuery() {
+  if (queryParams.dictCode) {
+    loading.value = true;
+    getDictPage(queryParams)
+      .then(({ data }) => {
+        dictList.value = data.records;
+        total.value = data.totalCount;
+      })
+      .finally(() => (loading.value = false));
+  }
+}
+
+/**
+ * 重置查询
+ */
+function resetQuery() {
+  queryParams.pageNo = 1;
+  handleQuery();
+}
+
+/**
+ * 行checkbox change事件
+ *
+ * @param selection
+ */
+function handleSelectionChange(selection: any) {
+  ids.value = selection.map((item: any) => item.id);
+}
+
+/**
+ * 打开字典表单弹窗
+ *
+ * @param dictId 字典ID
+ */
+function openDialog(form?: any) {
+  dialog.visible = true;
+  if (form) {
+    dialog.title = "修改字典";
+    Object.assign(formData, form);
+  } else {
+    dialog.title = "新增字典";
+    dataFormRef.value.label = "";
+    dataFormRef.value = "";
+    formData.dictLabel = "";
+    formData.dictValue = "";
+  }
+}
+
+/**
+ * 字典表单提交
+ */
+function handleSubmit() {
+  dataFormRef.value.validate((isValid: boolean) => {
+    if (isValid) {
+      const dictId = formData.id;
+      if (dictId) {
+        updateDict(formData)
+          .then(() => {
+            ElMessage.success("修改成功");
+            closeDialog();
+            resetQuery();
+          })
+          .finally(() => (loading.value = false));
+      } else {
+        addDict(formData)
+          .then(() => {
+            ElMessage.success("新增成功");
+            closeDialog();
+            resetQuery();
+          })
+          .finally(() => (loading.value = false));
+      }
+    }
+  });
+}
+
+/**
+ * 关闭弹窗
+ */
+function closeDialog() {
+  dialog.visible = false;
+  resetForm();
+}
+
+/**
+ * 重置表单
+ */
+function resetForm() {
+  dataFormRef.value.resetFields();
+  dataFormRef.value.clearValidate();
+
+  formData.id = undefined;
+  formData.state = 0;
+  formData.dictSort = 1;
+  formData.dictCode = props.typeCode;
+}
+
+/**
+ * 删除字典
+ */
+function handleDelete(dictId?: string) {
+  if (dictId) {
+    ids.value.push(dictId);
+  }
+  if (!ids.value) {
+    ElMessage.warning("请勾选删除项");
+    return;
+  }
+
+  ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(() => {
+    deleteDict(ids.value).then(() => {
+      ElMessage.success("删除成功");
+      resetQuery();
+    });
+  });
+}
+
+onMounted?.(() => {
+  handleQuery();
+});
+</script>
+
+<template>
+  <div class="app-container">
+    <!--    <div class="search-container">
+      &lt;!&ndash; 搜索表单 &ndash;&gt;
+      <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+        <el-form-item label="关键字" prop="name">
+          <el-input
+            v-model="queryParams.name"
+            placeholder="字典名称"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery"
+            ><i-ep-search />搜索</el-button
+          >
+          <el-button @click="resetQuery"> <i-ep-refresh />重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>-->
+    <el-card shadow="never">
+      <template #header>
+        <el-button type="primary" @click="openDialog()"
+          ><i-ep-plus />新增</el-button
+        >
+        <!--        <el-button
+          v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.dict_del]"
+          type="danger"
+          :disabled="ids.length === 0"
+          @click="handleDelete()"
+          ><i-ep-delete />删除</el-button
+        >-->
+      </template>
+
+      <!-- 数据表格 -->
+      <el-table
+        v-loading="loading"
+        :data="dictList"
+        border
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="50" />
+        <el-table-column label="字典名称" prop="dictLabel" />
+        <el-table-column label="字典值" prop="dictValue" />
+        <el-table-column label="排序" prop="dictSort" />
+        <el-table-column label="状态" align="center">
+          <template #default="scope">
+            <el-tag v-if="scope.row.state === 0" type="success">启用</el-tag>
+            <el-tag v-else type="info">禁用</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" label="操作" align="center">
+          <template #default="scope">
+            <el-button
+              v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.dict_edit]"
+              type="primary"
+              link
+              @click="openDialog(scope.row)"
+              ><i-ep-edit />编辑</el-button
+            >
+            <!--            <el-button
+              v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.dict_del]"
+              type="primary"
+              link
+              @click.stop="handleDelete(scope.row.id)"
+              ><i-ep-delete />删除</el-button
+            >-->
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-if="total > 0"
+        v-model:total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="handleQuery"
+      />
+    </el-card>
+
+    <!-- 表单弹窗 -->
+    <el-dialog
+      v-model="dialog.visible"
+      :title="dialog.title"
+      width="500px"
+      @close="closeDialog"
+    >
+      <el-form
+        ref="dataFormRef"
+        :model="formData"
+        :rules="rules"
+        label-width="100px"
+      >
+        <el-form-item label="字典名称" prop="dictLabel">
+          <el-input v-model="formData.dictLabel" placeholder="请输入字典名称" />
+        </el-form-item>
+        <el-form-item label="字典值" prop="dictValue">
+          <el-input
+            v-if="formData.id"
+            disabled
+            v-model="formData.dictValue"
+            placeholder="字典值"
+          />
+          <el-input
+            v-if="!formData.id"
+            v-model="formData.dictValue"
+            placeholder="字典值"
+          />
+        </el-form-item>
+
+        <el-form-item label="排序" prop="dictSort">
+          <el-input-number
+            v-model="formData.dictSort"
+            controls-position="right"
+            :min="0"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="formData.state">
+            <el-radio :value="0">正常</el-radio>
+            <el-radio :value="1">停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="formData.remark" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">确 定</el-button>
+          <el-button @click="closeDialog">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>

+ 361 - 0
src/views/modules/dictionary/index.vue

@@ -0,0 +1,361 @@
+<!--字典类型-->
+<script setup lang="ts">
+import SecondHeader from "@/views/modules/conmon/SecondHeader.vue";
+import DictItem from "./components/dict-item.vue";
+import {
+  getDictTypePage,
+  getDictTypeForm,
+  addDictType,
+  updateDictType,
+  deleteDictTypes,
+} from "@/api/system/dict";
+
+import {
+  DictTypePageVO,
+  DictTypeQuery,
+  DictTypeForm,
+} from "@/api/system/dict/types";
+import ButtonPermKeys from "@/common/configs/buttonPermission";
+
+defineOptions({
+  name: "DictType",
+  inheritAttrs: false,
+});
+
+const queryFormRef = ref(ElForm);
+const dataFormRef = ref(ElForm);
+
+const loading = ref(false);
+const ids = ref<number[]>([]);
+const total = ref(0);
+
+const queryParams = reactive<DictTypeQuery>({
+  pageNo: 1,
+  pageSize: 10,
+});
+
+const dictTypeList = ref<DictTypePageVO[]>();
+
+const dialog = reactive({
+  title: "",
+  visible: false,
+});
+
+const formData = reactive<DictTypeForm>({
+  state: 1,
+  type: "0",
+});
+
+const rules = reactive({
+  dictName: [
+    { required: true, message: "请输入字典类型名称", trigger: "blur" },
+  ],
+  dictType: [
+    { required: true, message: "请输入字典类型编码", trigger: "blur" },
+  ],
+});
+
+/** 查询 */
+function handleQuery() {
+  loading.value = true;
+  getDictTypePage(queryParams)
+    .then(({ data }) => {
+      dictTypeList.value = data.records;
+      total.value = data.totalCount;
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+}
+
+/**
+ * 重置查询
+ */
+function resetQuery() {
+  queryFormRef.value.resetFields();
+  queryParams.pageNo = 1;
+  handleQuery();
+}
+
+/** 行复选框选中  */
+function handleSelectionChange(selection: any) {
+  ids.value = selection.map((item: any) => item.id);
+}
+
+/**
+ * 打开字典类型表单弹窗
+ *
+ * @param dicTypeId 字典类型ID
+ */
+function openDialog(row?: any) {
+  dialog.visible = true;
+  console.log(row);
+  if (row) {
+    dialog.title = "修改字典类型";
+    Object.assign(formData, row);
+  } else {
+    formData.id = null;
+    formData.dictName = "";
+    formData.dictType = "";
+    formData.remark = "";
+    dialog.title = "新增字典类型";
+  }
+}
+
+/** 字典类型表单提交 */
+function handleSubmit() {
+  dataFormRef.value.validate((isValid: boolean) => {
+    if (isValid) {
+      const dictTypeId = formData.id;
+      if (dictTypeId) {
+        updateDictType(formData)
+          .then(() => {
+            ElMessage.success("修改成功");
+            closeDialog();
+            handleQuery();
+          })
+          .finally(() => (loading.value = false));
+      } else {
+        addDictType(formData)
+          .then(() => {
+            ElMessage.success("新增成功");
+            closeDialog();
+            handleQuery();
+          })
+          .finally(() => (loading.value = false));
+      }
+    }
+  });
+}
+
+/** 关闭字典类型弹窗 */
+function closeDialog() {
+  dialog.visible = false;
+  resetForm();
+}
+
+/**  重置字典类型表单 */
+function resetForm() {
+  dataFormRef.value.resetFields();
+  dataFormRef.value.clearValidate();
+
+  formData.id = undefined;
+  formData.state = 1;
+}
+
+/** 删除字典类型 */
+function handleDelete(dictTypeId?: number) {
+  const dictTypeIds = [dictTypeId || ids.value].join(",");
+  if (!dictTypeIds) {
+    ElMessage.warning("请勾选删除项");
+    return;
+  }
+
+  ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(() => {
+    deleteDictTypes(dictTypeIds).then(() => {
+      ElMessage.success("删除成功");
+      resetQuery();
+    });
+  });
+}
+
+const dictDataDialog = reactive({
+  title: "",
+  visible: false,
+});
+
+const selectedDictType = reactive({ typeCode: "", typeName: "" }); // 当前选中的字典类型
+
+/** 打开字典数据弹窗 */
+function openDictDialog(row: DictTypeForm) {
+  dictDataDialog.visible = true;
+  dictDataDialog.title = "【" + row.dictName + "】字典数据";
+
+  selectedDictType.typeCode = row.dictType + "";
+  selectedDictType.typeName = row.dictName + "";
+}
+
+/**  关闭字典数据弹窗 */
+function closeDictDialog() {
+  dictDataDialog.visible = false;
+}
+
+onMounted?.(() => {
+  handleQuery();
+});
+</script>
+
+<template>
+  <div class="app-container">
+    <SecondHeader>仪器资源配置</SecondHeader>
+    <div class="search-container">
+      <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+        <el-form-item label="关键字" prop="name">
+          <el-input
+            v-model="queryParams.keywords"
+            placeholder="字典类型名称/编码"
+            clearable
+            style="width: 200px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery()"
+            ><i-ep-search />搜索</el-button
+          >
+          <el-button @click="resetQuery()"><i-ep-refresh />重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <el-card shadow="never" class="table-container">
+      <template #header>
+        <el-button
+          v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.dictType_add]"
+          type="primary"
+          @click="openDialog()"
+          ><i-ep-plus />新增</el-button
+        >
+        <!-- <el-button
+          type="danger"
+          :disabled="ids.length === 0"
+          @click="handleDelete()"
+          ><i-ep-delete />删除</el-button
+        > -->
+      </template>
+      <el-table
+        v-loading="loading"
+        highlight-current-row
+        :data="dictTypeList"
+        border
+        @selection-change="handleSelectionChange"
+      >
+        <!-- <el-table-column type="selection" width="55" align="center" /> -->
+        <el-table-column label="字典类型名称" prop="dictName" />
+        <el-table-column label="字典类型编码" prop="dictType" />
+        <el-table-column label="状态" align="center" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.state === 1" type="success">启用</el-tag>
+            <el-tag v-else type="info">禁用</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" prop="remark" align="center" />
+        <el-table-column fixed="right" label="操作" align="center" width="220">
+          <template #default="scope">
+            <el-button
+              type="primary"
+              link
+              size="small"
+              @click.stop="openDictDialog(scope.row)"
+              ><i-ep-Collection />字典数据</el-button
+            >
+            <el-button
+              v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.dictType_edit]"
+              type="primary"
+              link
+              size="small"
+              @click.stop="openDialog(scope.row)"
+              ><i-ep-edit />编辑</el-button
+            >
+            <!--            <el-button
+              v-hasPerm="[ButtonPermKeys.SYSTEM.BTNS.dictType_del]"
+              type="primary"
+              link
+              size="small"
+              @click.stop="handleDelete(scope.row.id)"
+              ><i-ep-delete />删除</el-button
+            >-->
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-if="total > 0"
+        v-model:total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="handleQuery"
+      />
+    </el-card>
+
+    <el-dialog
+      v-model="dialog.visible"
+      :title="dialog.title"
+      width="500px"
+      @close="closeDialog"
+    >
+      <el-form
+        ref="dataFormRef"
+        :model="formData"
+        :rules="rules"
+        label-width="80px"
+      >
+        <el-form-item label="字典名称" prop="dictName">
+          <el-input v-model="formData.dictName" placeholder="请输入字典名称" />
+        </el-form-item>
+        <el-form-item label="字典编码" prop="dictType">
+          <el-input
+            v-model="formData.dictType"
+            v-if="formData.id"
+            placeholder="请输入字典编码 sys_test user_test"
+          />
+          <el-input
+            v-model="formData.dictType"
+            v-if="!formData.id"
+            placeholder="请输入字典编码 sys_test user_test"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="state">
+          <el-radio-group v-model="formData.state">
+            <el-radio :value="1">正常</el-radio>
+            <el-radio :value="0">停用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <!--        <el-form-item label="字典类别" prop="type">
+          <el-radio-group v-model="formData.type">
+            <el-radio value="0">用户字典</el-radio>
+            <el-radio value="1">系统字典</el-radio>
+          </el-radio-group>
+        </el-form-item>-->
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="formData.remark"
+            type="textarea"
+            placeholder="字典类型备注"
+            :autosize="{ minRows: 2, maxRows: 4 }"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSubmit">确 定</el-button>
+          <el-button @click="closeDialog">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!--字典数据弹窗-->
+    <el-dialog
+      v-model="dictDataDialog.visible"
+      :title="dictDataDialog.title"
+      width="1000px"
+      @close="closeDictDialog"
+    >
+      <dict-item
+        v-model:typeCode="selectedDictType.typeCode"
+        v-model:typeName="selectedDictType.typeName"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.app-container {
+  width: 100%;
+  height: 100vh;
+}
+</style>

+ 136 - 0
src/views/modules/global-config/components/GlobalInstrument.vue

@@ -0,0 +1,136 @@
+<script setup lang="ts">
+import ResourceForm from "./resourceForm.vue";
+import ResourceDetail from "./resourceDetail.vue";
+import { delInstrumentResource, selectInstrumentResource } from "@/api/config";
+import { ref } from "vue";
+import type { TabsPaneContext } from "element-plus";
+
+const resourceList = ref<any[]>([]);
+
+const props = defineProps({
+  property: {
+    type: String,
+    default: "1",
+  },
+});
+
+onMounted?.(() => {
+  getList();
+});
+
+const getList = () => {
+  selectInstrumentResource({ instrumentProperty: props.property }).then(
+    (data) => {
+      resourceList.value = data.data;
+    }
+  );
+};
+
+const configHandleDelete = async (index: number, row: any) => {
+  await delInstrumentResource(row.id);
+  resourceList.value.splice(index, 1);
+};
+
+const configHandleEdit = async (index: number, row: any) => {
+  resourceRef.value && resourceRef.value.openDialog("2", row, props.property);
+};
+
+const addConfigFun = () => {
+  resourceRef.value && resourceRef.value.openDialog("1", {}, props.property);
+};
+
+const resourceRef = ref();
+const detailRef = ref();
+
+const showDetail = (row: any) => {
+  detailRef.value && detailRef.value.openDialog(row);
+};
+</script>
+
+<template>
+  <div>
+    <div class="btns">
+      <el-button type="primary" @click="addConfigFun">
+        <span class="add">+</span>
+        新增
+      </el-button>
+    </div>
+    <el-table :data="resourceList" show-overflow-tooltip>
+      <el-table-column label="序号" type="index" width="80" />
+
+      <el-table-column label="波特率" prop="baudRate"> </el-table-column>
+      <el-table-column label="通道号" prop="channel"> </el-table-column>
+      <el-table-column label="校验位" prop="checkBit"> </el-table-column>
+      <el-table-column label="通信配置" prop="commConfig" width="200">
+      </el-table-column>
+      <el-table-column label="连接url" prop="connUrl"> </el-table-column>
+      <el-table-column label="数据位" prop="dataBit"> </el-table-column>
+      <el-table-column label="配置型号" prop="instrumentModule">
+      </el-table-column>
+      <el-table-column label="仪器名称" prop="instrumentName">
+      </el-table-column>
+      <!--      <el-table-column label="仪器属性" prop="instrumentProperty">-->
+      <!--        <el-input v-model="formData.instrumentProperty" />-->
+      <!--      </el-table-column>-->
+      <el-table-column label="设备地址" prop="ip"> </el-table-column>
+      <el-table-column label="设备端口" prop="port"> </el-table-column>
+      <el-table-column label="读url" prop="queryUrl"> </el-table-column>
+      <el-table-column label="槽位号" prop="slot"> </el-table-column>
+      <el-table-column label="停止位" prop="stopBit"> </el-table-column>
+      <el-table-column label="写url" prop="writeUrl"> </el-table-column>
+
+      <el-table-column label="通信类型" prop="commType" />
+      <el-table-column label="仪器类型" prop="instrumentType">
+      </el-table-column>
+
+      <el-table-column label="操作" prop="操作" fixed="right" width="220">
+        <template #default="scope">
+          <el-button
+            text
+            size="small"
+            type="primary"
+            @click="configHandleDelete(scope.$index, scope.row)"
+          >
+            删除
+          </el-button>
+          <el-button
+            text
+            size="small"
+            type="primary"
+            @click="configHandleEdit(scope.$index, scope.row)"
+          >
+            修改
+          </el-button>
+          <el-button
+            text
+            size="small"
+            type="primary"
+            @click="showDetail(scope.row)"
+          >
+            查看
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <ResourceForm ref="resourceRef" @finished="getList"></ResourceForm>
+    <ResourceDetail ref="detailRef"></ResourceDetail>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.btns {
+  text-align: start;
+  margin: 12px;
+}
+.add {
+  width: 12px;
+  height: 12px;
+  line-height: 12px;
+  border-radius: 12px;
+  text-align: center;
+  background-color: #fff;
+  color: #3b7cff;
+  margin-right: 10px;
+  vertical-align: middle;
+}
+</style>

+ 5 - 3
src/views/modules/global-config/components/resourceForm.vue

@@ -21,7 +21,7 @@ const formData = ref({
   dataBit: "",
   instrumentModule: "",
   instrumentName: "",
-  instrumentProperty: "1",
+  instrumentProperty: "",
   instrumentType: "",
   ip: "",
   port: "",
@@ -42,7 +42,7 @@ const onCancel = () => {
 
 // 1 新增 2 修改
 
-const openDialog = (type: string, data: any) => {
+const openDialog = (type: string, data: any, instrumentType: string) => {
   formData.value.id = "";
   dialogType.value = type;
 
@@ -56,13 +56,15 @@ const openDialog = (type: string, data: any) => {
   } else {
     formData.value = data;
   }
+
+  console.log(instrumentType);
+  formData.value.instrumentProperty = instrumentType;
 };
 
 const saveFun = () => {
   formRef.value
     ?.validate()
     .then(async () => {
-      formData.value.instrumentProperty = "1";
       if (dialogType.value === "1") {
         await addInstrumentResource(formData.value);
       } else {

+ 25 - 112
src/views/modules/global-config/deviceResources.vue

@@ -1,111 +1,25 @@
 <script lang="ts" setup>
 import SecondHeader from "@/views/modules/conmon/SecondHeader.vue";
-import ResourceForm from "./components/resourceForm.vue";
-import ResourceDetail from "./components/resourceDetail.vue";
-import { delInstrumentResource, selectInstrumentResource } from "@/api/config";
+import GlobalInstrument from "./components/GlobalInstrument.vue";
 
-const resourceList = ref<any[]>([]);
-
-onMounted?.(() => {
-  getList();
-});
-
-const getList = () => {
-  selectInstrumentResource({ instrumentProperty: "1" }).then((data) => {
-    resourceList.value = data.data;
-  });
-};
-
-const configHandleDelete = async (index: number, row: any) => {
-  await delInstrumentResource(row.id);
-  resourceList.value.splice(index, 1);
-};
-
-const configHandleEdit = async (index: number, row: any) => {
-  resourceRef.value && resourceRef.value.openDialog("2", row);
-};
-
-const addConfigFun = () => {
-  resourceRef.value && resourceRef.value.openDialog("1");
-};
-
-const resourceRef = ref();
-const detailRef = ref();
-
-const showDetail = (row: any) => {
-  detailRef.value && detailRef.value.openDialog(row);
+const activeName = ref("测试仪器");
+const handleClick = (tab: any, event: Event) => {
+  // console.log(tab, event);
 };
 </script>
 
 <template>
   <div class="global-config">
-    <SecondHeader>仪器资源配置</SecondHeader>
-    <div class="btns">
-      <el-button type="primary" @click="addConfigFun">
-        <span class="add">+</span>
-        新增
-      </el-button>
-    </div>
-    <el-table :data="resourceList" show-overflow-tooltip>
-      <el-table-column label="序号" type="index" width="80" />
-
-      <el-table-column label="波特率" prop="baudRate"> </el-table-column>
-      <el-table-column label="通道号" prop="channel"> </el-table-column>
-      <el-table-column label="校验位" prop="checkBit"> </el-table-column>
-      <el-table-column label="通信配置" prop="commConfig" width="200">
-      </el-table-column>
-      <el-table-column label="连接url" prop="connUrl"> </el-table-column>
-      <el-table-column label="数据位" prop="dataBit"> </el-table-column>
-      <el-table-column label="配置型号" prop="instrumentModule">
-      </el-table-column>
-      <el-table-column label="仪器名称" prop="instrumentName">
-      </el-table-column>
-      <!--      <el-table-column label="仪器属性" prop="instrumentProperty">-->
-      <!--        <el-input v-model="formData.instrumentProperty" />-->
-      <!--      </el-table-column>-->
-      <el-table-column label="设备地址" prop="ip"> </el-table-column>
-      <el-table-column label="设备端口" prop="port"> </el-table-column>
-      <el-table-column label="读url" prop="queryUrl"> </el-table-column>
-      <el-table-column label="槽位号" prop="slot"> </el-table-column>
-      <el-table-column label="停止位" prop="stopBit"> </el-table-column>
-      <el-table-column label="写url" prop="writeUrl"> </el-table-column>
-
-      <el-table-column label="通信类型" prop="commType" />
-      <el-table-column label="仪器类型" prop="instrumentType">
-      </el-table-column>
-
-      <el-table-column label="操作" prop="操作" fixed="right" width="220">
-        <template #default="scope">
-          <el-button
-            text
-            size="small"
-            type="primary"
-            @click="configHandleDelete(scope.$index, scope.row)"
-          >
-            删除
-          </el-button>
-          <el-button
-            text
-            size="small"
-            type="primary"
-            @click="configHandleEdit(scope.$index, scope.row)"
-          >
-            修改
-          </el-button>
-          <el-button
-            text
-            size="small"
-            type="primary"
-            @click="showDetail(scope.row)"
-          >
-            查看
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <ResourceForm ref="resourceRef" @finished="getList"></ResourceForm>
-    <ResourceDetail ref="detailRef"></ResourceDetail>
+    <SecondHeader>仪器配置</SecondHeader>
+
+    <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+      <el-tab-pane label="测试仪器" name="测试仪器">
+        <GlobalInstrument></GlobalInstrument>
+      </el-tab-pane>
+      <el-tab-pane label="执行终端" name="执行终端">
+        <GlobalInstrument property="2"></GlobalInstrument
+      ></el-tab-pane>
+    </el-tabs>
   </div>
 </template>
 
@@ -119,19 +33,18 @@ const showDetail = (row: any) => {
       padding: 0 20px;
     }
   }
-  .add {
-    width: 12px;
-    height: 12px;
-    line-height: 12px;
-    border-radius: 12px;
-    text-align: center;
-    background-color: #fff;
-    color: #3b7cff;
-    margin-right: 10px;
-    vertical-align: middle;
-  }
 }
-
+.add {
+  width: 12px;
+  height: 12px;
+  line-height: 12px;
+  border-radius: 12px;
+  text-align: center;
+  background-color: #fff;
+  color: #3b7cff;
+  margin-right: 10px;
+  vertical-align: middle;
+}
 .btns {
   text-align: start;
   margin: 12px;

+ 1 - 1
src/views/modules/home/components/add.vue

@@ -97,7 +97,7 @@ const submitForm = async (formEl) => {
   await formEl.validate(async (valid, fields) => {
     if (valid) {
       if (isEdit1) {
-        updateProject(formLabelAlign.value);
+        await updateProject(formLabelAlign.value);
       } else {
         await addProject(formLabelAlign.value);
       }

+ 23 - 7
src/views/modules/home/home.vue

@@ -67,11 +67,16 @@
                   {{ item.engineeringProductName }}
                 </div>
                 <div class="list-item-switch">
-                  <span class="item-gray">发布</span
-                  ><el-switch
-                    v-model="item.publishStatus"
-                    @change="changeProjectStatus(item)"
-                  />
+                  <span class="item-gray">发布</span>
+                  <el-popconfirm
+                    width="220"
+                    @confirm="changeProjectStatus(item)"
+                    title="确定要修改发布状态吗?"
+                  >
+                    <template #reference>
+                      <el-switch :model-value="item.publishStatus" />
+                    </template>
+                  </el-popconfirm>
                 </div>
               </div>
               <div class="list-item-flex">
@@ -225,8 +230,13 @@ const toEdit = (row) => {
 };
 
 // 发布
+const isLoading = ref(false);
 const changeProjectStatus = async (item: VersionItem) => {
-  await updateProject(item);
+  let p = {
+    ...item,
+  };
+  p.publishStatus = !p.publishStatus;
+  await updateProject(p);
   getEngineeringList(false);
 };
 
@@ -307,7 +317,13 @@ const gotoExecuteTest = (project) => {
 
   currentMenuIndex.value = 1;
 
-  router.push({ name: "RunTestPage", params: { engineerId: project.id } });
+  router.push({
+    name: "RunTesting",
+    query: {
+      _refresh: Date.now(), // 每次跳转生成不同的时间戳
+    },
+    params: { engineerId: project.id },
+  });
 };
 </script>
 

+ 15 - 1
src/views/modules/project-config/com/function-col.vue

@@ -54,7 +54,15 @@ onMounted(() => {
                 @dragstart="onDragStart($event, dragData)"
                 @click="test(dragData)"
               >
-                {{ dragData?.data?.information?.functionName ?? "-" }}
+                <el-tooltip
+                  :content="dragData?.data?.information?.functionName ?? '-'"
+                  placement="right"
+                  effect="light"
+                >
+                  <div class="ellipsis">
+                    {{ dragData?.data?.information?.functionName ?? "-" }}
+                  </div>
+                </el-tooltip>
               </div>
             </div>
           </el-scrollbar>
@@ -112,4 +120,10 @@ onMounted(() => {
 :deep(.el-collapse, .el-collapse-item__content) {
   border: 0;
 }
+.ellipsis {
+  white-space: nowrap; /* 防止文本换行 */
+  overflow: hidden; /* 隐藏溢出的文本 */
+  text-overflow: ellipsis; /* 使用省略号显示被隐藏的文本 */
+  width: 100%; /* 必须设置宽度,以便溢出的文本生效 */
+}
 </style>

+ 17 - 2
src/views/modules/project-config/com/project-message.vue

@@ -1,7 +1,7 @@
 <script setup>
 import moduleDialog from "@/views//modules/project-config/components/test-module-dialog.vue";
 import TitleHeader from "@/views/modules/project-config/com/titleHeader.vue";
-import { delTestModule } from "@/api/project";
+import { copyProject, delTestModule } from "@/api/project";
 import { useCommonStoreHook } from "@/store";
 import { getProjectById, getTestProjectList } from "@/api/project/index";
 const { currentTestItemId } = toRefs(useCommonStoreHook());
@@ -72,6 +72,15 @@ const confirmEvent = () => {
     });
   }
 };
+
+// 复制项目
+const toCopyProject = async () => {
+  if (currentTestItemId.value) {
+    await copyProject(currentTestItemId.value);
+    ElMessage.success("复制成功");
+    toGetTestProjectList();
+  }
+};
 </script>
 
 <template>
@@ -106,6 +115,9 @@ const confirmEvent = () => {
     <div class="project-test-btn">
       <div class="name">测试项目</div>
       <div class="btn">
+        <span class="edit" @click="toCopyProject">
+          <svg-icon icon-class="download" />
+        </span>
         <el-popconfirm
           width="220"
           confirm-button-text="确认"
@@ -122,6 +134,7 @@ const confirmEvent = () => {
         <span class="edit" @click="addProject">
           <svg-icon icon-class="addHao" />
         </span>
+
         <span class="edit" @click="editProject">
           <svg-icon icon-class="homeIcon2" />
         </span>
@@ -234,7 +247,9 @@ const confirmEvent = () => {
       height: 36px;
       line-height: 36px;
       text-align: center;
-
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
       border-radius: 4px 4px 4px 4px;
       border: 1px solid #3b7cff;
     }

+ 59 - 47
src/views/modules/project-config/project-config.vue

@@ -45,17 +45,17 @@ const onNodeOperation = (name: HJMethodName, node: HJNodeData): void => {
       return;
     }
 
-    if (selectedNode.value) {
-      ElMessage.warning("请先保存节点信息");
-      return;
-    }
+    // if (selectedNode.value) {
+    //   ElMessage.warning("请先保存节点信息");
+    //   return;
+    // }
 
     //   保存模版数据
     let flowData = {
       nodes: nodes.value,
       edges: edges.value,
     };
-    console.log("dddddd", flowData);
+    console.log("dddddd", nodes.value.length, edges.value.length, flowData);
     let p = {
       id: currentTestItemId.value,
       routeData: JSON.stringify(flowData),
@@ -83,6 +83,16 @@ const onSaveConfigs = () => {
   infoVisible.value = false;
   selectedNode.value = null;
 };
+watch(
+  selectedNode,
+  (newVal) => {
+    flowRef.value &&
+      flowRef.value.updateNodeData(
+        JSON.parse(JSON.stringify(selectedNode.value))
+      );
+  },
+  { deep: true }
+);
 
 onMounted(async () => {
   //   如果选择的测试项目id有值,就获取数据
@@ -140,50 +150,52 @@ const handleSearch = (_: string, prefix: string) => {
     />
     <div class="right-message-box">
       <TitleHeader>属性配置</TitleHeader>
-      <div class="form-box" v-if="infoVisible">
-        <el-form rer="formRef" label-position="top">
-          <el-form-item label="节点标识名称">
-            <el-input
-              v-model="selectedNode!.data.information.nodeName"
-            ></el-input>
-          </el-form-item>
-          <el-form-item>
-            <el-text class="mx-1" type="warning">
-              输入@选择全局变量或#选择仪器
-            </el-text>
-          </el-form-item>
-          <el-form-item
-            v-for="property in selectedNode?.data?.information?.properties"
-            :label="property.proName"
-            :key="property.proName"
-          >
-            <el-mention
-              v-model="property.bindLabel"
-              :options="configOptions"
-              :prefix="['@', '#']"
-              whole
-              @search="handleSearch"
-              @select="
-                (opt) => {
-                  property.bingObjJson = JSON.stringify(opt);
-                }
-              "
-              @blur="
-                () => {
-                  if (property.bindLabel === '') {
-                    property.bingObjJson = '';
+      <el-scrollbar height="calc(100vh - 140px)">
+        <div class="form-box" v-if="infoVisible">
+          <el-form rer="formRef" label-position="top">
+            <el-form-item label="节点标识名称">
+              <el-input
+                v-model="selectedNode!.data.information.nodeName"
+              ></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-text class="mx-1" type="warning">
+                输入@选择全局变量或#选择仪器
+              </el-text>
+            </el-form-item>
+            <el-form-item
+              v-for="property in selectedNode?.data?.information?.properties"
+              :label="property.proName"
+              :key="property.proName"
+            >
+              <el-mention
+                v-model="property.bindLabel"
+                :options="configOptions"
+                :prefix="['@', '#']"
+                whole
+                @search="handleSearch"
+                @select="
+                  (opt) => {
+                    property.bingObjJson = JSON.stringify(opt);
                   }
-                }
-              "
-            />
-          </el-form-item>
-        </el-form>
-      </div>
-      <div class="bottom-box">
-        <div class="save-btn" @click="onSaveConfigs">
-          <svg-icon icon-class="save" />
-          保存节点信息
+                "
+                @blur="
+                  () => {
+                    if (property.bindLabel === '') {
+                      property.bingObjJson = '';
+                    }
+                  }
+                "
+              />
+            </el-form-item>
+          </el-form>
         </div>
+      </el-scrollbar>
+      <div class="bottom-box">
+        <!--        <div class="save-btn" @click="onSaveConfigs">-->
+        <!--          <svg-icon icon-class="save" />-->
+        <!--          保存节点信息-->
+        <!--        </div>-->
       </div>
     </div>
   </div>

+ 97 - 14
src/views/modules/runTest/run-test.vue

@@ -4,7 +4,7 @@ import titHeader from "./components/tit-header.vue";
 import ConfigDataAndDevice from "./components/configDataAndDevice.vue";
 import { useWebSocket } from "@vueuse/core";
 import { useCommonStoreHook } from "@/store";
-import { getTestProjectList } from "@/api/project";
+import { getProjectById, getTestProjectList } from "@/api/project";
 import { CheckboxValueType } from "element-plus";
 import {
   searchExcutingGlobalData,
@@ -17,6 +17,10 @@ import {
   stopExecuteProjects,
 } from "@/api/project/excute";
 
+defineOptions({
+  name: "RunTesting",
+});
+
 const route = useRoute();
 
 interface VersionItem {
@@ -35,7 +39,12 @@ const selectedExcutingMachine = ref<any>();
 
 const drawer = ref(false);
 
+// 测试工程信息
+const engineeringObj = ref<any>();
 onMounted(async () => {
+  getProjectById(route.params.engineerId as string).then((res) => {
+    engineeringObj.value = res.data;
+  });
   getAllTestTypes();
   getExcutingMachines();
   getTestProjectTopList();
@@ -155,12 +164,12 @@ const onClickGlobalConfig = () => {
 };
 // 开始测试相关
 const isTesting = ref(false);
+const usedTime = ref(0);
+let interval = 0;
 const currentTestingProject = ref<any>();
 const startToRunTest = async () => {
   if (!checkStartEnable()) return;
 
-  wsClient.open();
-
   let params = {
     configList: testingMachines.value,
     engineeringId: route.params.engineerId,
@@ -171,10 +180,22 @@ const startToRunTest = async () => {
   };
 
   let res = await startExecuteProjects(params);
+
+  wsClient.open();
+
   currentTestingProject.value = res.data;
   isTesting.value = true;
+
+  usedTime.value = 0;
+  interval = setInterval(() => {
+    usedTime.value += 1;
+  }, 1000);
 };
 
+onUnmounted(async () => {
+  clearInterval(interval);
+});
+
 const stopTesting = async () => {
   let res = await stopExecuteProjects(currentTestingProject.value?.id);
   isTesting.value = false;
@@ -203,6 +224,53 @@ const setTestingData = () => {
     data[project.projectName] = [];
   });
   testingWSData.value = data;
+
+  // testingWSData.value = {
+  //   电源测试: [
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //   ],
+  //   wanger: [
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //     {
+  //       dataItem: "测试值0嘟嘟嘟嘟嘟嘟",
+  //       dataContent: "dddddafvdafgvjf多发点顺丰水电费阿道夫爸的阿凡达深V啊",
+  //     },
+  //   ],
+  // };
 };
 watch(topProSelectedList, () => {
   setTestingData();
@@ -435,6 +503,16 @@ const cancelMsgType5 = () => {
           :model="formLabelAlign"
           ref="ruleFormRef"
         >
+          <el-form-item label="产品名称">
+            {{ engineeringObj?.engineeringProductName }}
+          </el-form-item>
+          <el-form-item label="工程类型">
+            {{ engineeringObj?.engineeringType }}
+          </el-form-item>
+          <el-form-item label="	工程版本" label-position="left">
+            {{ engineeringObj?.engineeringVersion }}
+          </el-form-item>
+
           <el-form-item label="产品编号" prop="productCode">
             <el-input v-model="formLabelAlign.productCode" />
           </el-form-item>
@@ -541,15 +619,15 @@ const cancelMsgType5 = () => {
         <el-scrollbar class="content-B-height-4" v-if="testingWSData">
           <div v-for="(proName, index) in Object.keys(testingWSData)">
             <div class="cssj-tit">项目名称: {{ proName }}</div>
-            <div
-              v-for="(data, index) in testingWSData[proName]"
-              :key="index"
-              class="cssj-row-flex"
+            <el-table
+              :data="testingWSData[proName]"
+              show-overflow-tooltip
+              :show-header="false"
             >
-              <span>{{ index }}</span>
-              <span>{{ data?.dataItem }}</span>
-              <span>{{ data?.dataContent }}</span>
-            </div>
+              <el-table-column type="index" width="55" />
+              <el-table-column property="dataItem" width="160" />
+              <el-table-column property="dataContent" />
+            </el-table>
           </div>
         </el-scrollbar>
       </div>
@@ -568,11 +646,12 @@ const cancelMsgType5 = () => {
       </div>
       <div class="center">
         <div class="center-item">
-          <div class="center-num">10</div>
+          <div class="center-num">{{ statisticsData?.todayTestNum }}</div>
           <div class="center-tit">今日检测产品数量</div>
         </div>
         <div class="center-item">
-          <div class="center-num">10</div>
+          <div class="center-num">{{ statisticsData?.hisTestNum }}</div>
+
           <div class="center-tit">历史检测产品数量</div>
         </div>
       </div>
@@ -580,7 +659,7 @@ const cancelMsgType5 = () => {
         <!--        <el-button type="primary" @click="handleWSMessage"-->
         <!--          >测试假数据</el-button-->
         <!--        >-->
-        <div>已用时:300s</div>
+        <div class="used-time" v-if="isTesting">已用时:{{ usedTime }}s</div>
         <!--        <div class="test-btn progress" @click="startToRunTest">-->
         <!--          <svg-icon icon-class="start-test" />-->
         <!--          开始测试-->
@@ -1014,6 +1093,10 @@ $color-progress: #3cbaff;
   }
 }
 
+.used-time {
+  margin-right: 20px;
+}
+
 .content-B-height-2 {
   height: calc(100vh - $main-header-height - 300px - 64px - 228px);
 }