Explorar o código

feat: 重构流程设计器节点配置及样式

refactor(api): 修改字典接口字段名code为value,name为label
feat(process): 新增流程节点图标及样式
feat(process): 实现节点配置抽屉表单
refactor(process): 重构流程设计器数据加载逻辑
fix(upload): 修复上传组件空文件判断
docs: 删除无用JSON示例文件
style: 优化节点展示样式
zhanghaijun hai 11 meses
pai
achega
610ccd6da1

+ 4 - 0
src/App.vue

@@ -13,6 +13,7 @@
   import { setThemeTransitionClass } from './utils/theme/animation'
   import { checkStorageCompatibility } from './utils/storage'
   import { useDictStore } from './store/modules/dict'
+  import { useEventListener } from '@vueuse/core'
 
   const userStore = useUserStore()
   const dictStore = useDictStore()
@@ -23,6 +24,9 @@
     en: en
   }
 
+  // 阻止浏览器默认的右键菜单弹出(不会影响自定义右键事件)
+  useEventListener(window.document, 'contextmenu', (ev) => ev.preventDefault())
+
   onBeforeMount(() => {
     setThemeTransitionClass(true)
     // 这里可以一次性初始化所有字典

+ 12 - 12
src/api/course.api.ts

@@ -24,8 +24,8 @@ export interface Course {
 export class CourseServer {
   public static list = (params: Record<string, unknown>) => request.post<Course[]>('/course/list', params)
   public static detail = (courseId: string) => request.post<Course>('/course/detail/get', { courseId })
-  public static add = (data: Omit<Course, 'courseID' | 'courseGUID' | 'creator' | 'createTime' | 'modifyTime'>) => request.post('/course/add', data)
-  public static update = (data: Omit<Course, 'creator' | 'createTime' | 'modifyTime'>) => request.post('/course/update', data)
+  public static add = (data: Record<string, unknown>) => request.post('/course/add', data)
+  public static update = (data: Record<string, unknown>) => request.post('/course/update', data)
   public static delete = (courseId: string) => request.post('/course/delete', { courseId })
   public static deleteBatch = (courseIds: string[]) => request.post('/course/delete/batch', { courseIds })
   // 课程复制
@@ -51,15 +51,15 @@ export interface ProcessOperate {
   category: string /** 分类 */
   categoryName: string /** 分类名称 */
   param1Desc: string /** 参数1说明 */
-  param1Type: string /** 参数1类型 */
+  param1Type: number /** 参数1类型 */
   param2Desc: string /** 参数2说明 */
-  param2Type: string /** 参数2类型 */
+  param2Type: number /** 参数2类型 */
   param3Desc: string /** 参数3说明 */
-  param3Type: string /** 参数3类型 */
+  param3Type: number /** 参数3类型 */
   param4Desc: string /** 参数4说明 */
-  param4Type: string /** 参数4类型 */
+  param4Type: number /** 参数4类型 */
   param5Desc: string /** 参数5说明 */
-  param5Type: string /** 参数5类型 */
+  param5Type: number /** 参数5类型 */
   subCategory: string /** 子分类 */
   subCategoryName: string /** 子分类名称 */
 }
@@ -71,15 +71,15 @@ export interface ProcessFault {
   category: string /** 分类 */
   categoryName: string /** 分类名称 */
   param1Desc: string /** 参数1说明 */
-  param1Type: string /** 参数1类型 */
+  param1Type: number /** 参数1类型 */
   param2Desc: string /** 参数2说明 */
-  param2Type: string /** 参数2类型 */
+  param2Type: number /** 参数2类型 */
   param3Desc: string /** 参数3说明 */
-  param3Type: string /** 参数3类型 */
+  param3Type: number /** 参数3类型 */
   param4Desc: string /** 参数4说明 */
-  param4Type: string /** 参数4类型 */
+  param4Type: number /** 参数4类型 */
   param5Desc: string /** 参数5说明 */
-  param5Type: string /** 参数5类型 */
+  param5Type: number /** 参数5类型 */
   subCategory: string /** 子分类 */
   subCategoryName: string /** 子分类名称 */
 }

+ 2 - 2
src/api/dict.api.ts

@@ -8,8 +8,8 @@ export interface Dict {
 }
 export interface DictItem {
   dictCode: string /** 字典编码 */
-  code: string /** 字典项编码 */
-  name: string /** 字典项名称 */
+  value: string /** 字典项编码 */
+  label: string /** 字典项名称 */
   remark: string /** 备注 */
   sort: number /** 排序 */
 }

+ 54 - 10
src/components/business/EasyProcess/core/drawer_wrap.vue

@@ -1,7 +1,11 @@
 <template>
   <Dialog>
     <div class="drawer_wrap">
-      <component :is="node.nodeType in DrawerMap ? DrawerMap[node.nodeType as keyof typeof DrawerMap] : DrawerDefault" :node="node" />
+      <DrawerCondition v-if="node.nodeType === 'condition'" ref="conditionDrawerRef" :node="node as EasyProcess.ConditionNode" />
+      <DrawerOperation v-else-if="node.nodeType === 'operation'" ref="operationDrawerRef" :node="node as EasyProcess.OperationNode" />
+      <DrawerFault v-else-if="node.nodeType === 'fault'" ref="faultDrawerRef" :node="node as EasyProcess.FaultNode" />
+      <DrawerVoice v-else-if="node.nodeType === 'voice'" ref="voiceDrawerRef" :node="node as EasyProcess.VoiceNode" />
+      <DrawerDefault v-else :node="node" />
     </div>
   </Dialog>
 </template>
@@ -15,19 +19,59 @@
   import DrawerVoice from '../node/voice_drawer.vue'
   import DrawerCondition from '../node/condition_drawer.vue'
   import DrawerDefault from './drawer_default.vue'
-
-  const DrawerMap = shallowRef({
-    Operation: DrawerOperation,
-    Fault: DrawerFault,
-    Voice: DrawerVoice,
-    Condition: DrawerCondition
-  })
+  import { ProcessHelper } from '../common/process.helper'
+  import { ProcessConstant } from '../common/process.data'
 
   const props = withDefaults(defineProps<{ node: EasyProcess.NodeConfig }>(), {})
+  const processHelper = inject<ProcessHelper>(ProcessConstant.KEY_PROCESS_HELPER)
+  const processNode = ref<EasyProcess.NodeConfig>()
+
+  const conditionDrawerRef = ref()
+  const operationDrawerRef = ref()
+  const faultDrawerRef = ref()
+  const voiceDrawerRef = ref()
 
-  const [Dialog, dialogApi] = useDialog({ title: props.node.nodeName || '节点配置', width: '30%', dialogType: 'drawer', modal: true })
+  const [Dialog, dialogApi] = useDialog({
+    width: '40%',
+    dialogType: 'drawer',
+    modal: true,
+    destroyOnClose: true,
+    onConfirm() {
+      switch (processNode.value?.nodeType) {
+        case 'operation':
+          operationDrawerRef.value.getNodeData((nodeData: Record<string, unknown>) => {
+            processHelper?.updateNode(props.node.nodeId, nodeData)
+            dialogApi.close()
+          })
+          break
+        case 'fault':
+          faultDrawerRef.value.getNodeData((nodeData: Record<string, unknown>) => {
+            processHelper?.updateNode(props.node.nodeId, nodeData)
+            dialogApi.close()
+          })
+          break
+        case 'voice':
+          voiceDrawerRef.value.getNodeData((nodeData: Record<string, unknown>) => {
+            processHelper?.updateNode(props.node.nodeId, nodeData)
+            dialogApi.close()
+          })
+          break
+        case 'condition':
+          conditionDrawerRef.value.getNodeData((nodeData: Record<string, unknown>) => {
+            processHelper?.updateNode(props.node.nodeId, nodeData)
+            dialogApi.close()
+          })
+          break
+        default:
+          ElMessage.error('错误节点类型')
+          break
+      }
+    }
+  })
   defineExpose({
-    show() {
+    show(node: EasyProcess.NodeConfig) {
+      processNode.value = node
+      dialogApi.setState({ title: props.node.nodeName || '节点配置' })
       dialogApi.open()
     }
   })

+ 17 - 1
src/components/business/EasyProcess/index.vue

@@ -26,12 +26,28 @@
   import { ref, provide } from 'vue'
   import { blankProcessData, ProcessConstant } from './common/process.data'
   import { ProcessHelper, ProcessValidator } from './common/process.helper'
+  import { EasyProcess } from './common/process.type'
 
   // 流程数据
-  const processHelper = new ProcessHelper(blankProcessData())
+  const props = withDefaults(defineProps<{ data?: EasyProcess.NodeConfig }>(), {})
+
+  const getProcessData = (data: EasyProcess.NodeConfig = props.data || blankProcessData()) => {
+    if (data?.nodeType === 'start') return data
+    return blankProcessData()
+  }
+
+  const processHelper = new ProcessHelper(getProcessData(props.data))
   provide<ProcessHelper>(ProcessConstant.KEY_PROCESS_HELPER, processHelper)
   const processData = computed(() => processHelper.getProcessData())
 
+  watch(
+    () => props.data,
+    (newVal) => {
+      if (newVal) {
+        processHelper.setProcessData(getProcessData(newVal))
+      }
+    }
+  )
   /* ====== 节点数据验证 ====== start */
   // 注册节点验证器
   const validator = new ProcessValidator()

+ 35 - 58
src/components/business/EasyProcess/node/condition_drawer.vue

@@ -1,66 +1,43 @@
 <template>
-  <!-- 条件配置 -->
-  <div class="condition-config">
-    <el-form :model="formModel" label-width="80px">
-      <!-- 条件预览 -->
-      <el-form-item label="当前选择">
-        <el-tag type="primary">{{ selectedOptionLabel }}</el-tag>
-      </el-form-item>
-      <!-- 条件选项 -->
-      <el-form-item label="条件选项">
-        <el-radio-group size="small">
-          <el-radio v-for="item in options" :key="item.value" :value="item.value" :label="item.label" border />
-        </el-radio-group>
-      </el-form-item>
-    </el-form>
+  <div>
+    <BasicForm :model="nodeData"> </BasicForm>
   </div>
 </template>
-
-<script setup lang="ts" name="ConditionDrawer">
-  import { computed } from 'vue'
+<script setup lang="ts" name="DrawerOperation">
+  import { useForm } from '@/components/form'
   import { EasyProcess } from '../common/process.type'
 
-  const props = withDefaults(defineProps<{ node: EasyProcess.NodeConfig }>(), {})
-
-  // 计算当前选中的选项标签
-  const selectedOptionLabel = computed(() => {
-    const option = options.find((item) => item.value === formModel.value.value)
-    return option ? option.label : '未知'
-  })
-
-  // 表单模型(用于el-form绑定)
-  const formModel = computed(() => props.node.nodeData || {})
-
-  const options = [
-    { label: '与 (AND)', value: '1' },
-    { label: '或 (OR)', value: '2' },
-    { label: '非 (NOT)', value: '3' }
-  ]
-</script>
-
-<style lang="scss" scoped>
-  .condition-config {
-    padding: 16px;
-
-    .el-form-item {
-      margin-bottom: 20px;
-
-      &:last-child {
-        margin-bottom: 0;
+  const props = withDefaults(defineProps<{ node: EasyProcess.ConditionNode }>(), {})
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+
+  const [BasicForm, formApi] = useForm({
+    labelWidth: '100px',
+    schema: [
+      {
+        prop: 'value',
+        label: '条件选项',
+        component: 'ApiDict',
+        componentProps: {
+          renderType: 'RadioGroup',
+          options: [
+            { label: '与 (AND)', value: '1' },
+            { label: '或 (OR)', value: '2' },
+            { label: '非 (NOT)', value: '3' }
+          ]
+        }
       }
-    }
-
-    .el-radio {
-      margin-right: 12px;
-
-      &:last-child {
-        margin-right: 0;
-      }
-    }
+    ]
+  })
 
-    .el-tag {
-      font-size: 14px;
-      padding: 8px 12px;
+  defineExpose({
+    getNodeData: (callback: (nodeData: Record<string, unknown>) => void) => {
+      formApi.validate().then((valid) => {
+        if (valid) {
+          callback(nodeData.value)
+        } else {
+          ElMessage.error('请检查节点配置是否完整')
+        }
+      })
     }
-  }
-</style>
+  })
+</script>

+ 122 - 306
src/components/business/EasyProcess/node/fault_drawer.vue

@@ -1,324 +1,140 @@
 <template>
-  <!-- 异常触发节点 -->
   <div>
-    <BasicForm>
-      <!-- 动态参数 -->
-      <template #dynamicParams>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item prop="param1Type">
-              <el-select placeholder="请选择类型">
-                <el-option label="文本" value="1" />
-                <el-option label="数字" value="2" />
-                <el-option label="日期" value="3" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item prop="param1Desc">
-              <el-input placeholder="请输入参数1说明" />
-            </el-form-item>
-          </el-col>
-        </el-row>
+    <BasicForm :model="nodeData">
+      <template #templateID="{ model }">
+        <el-select-v2
+          v-model="model.templateID"
+          :options="
+            templateList.map((item) => ({
+              label: item.templateName,
+              value: item.templateID
+            }))
+          "
+          placeholder="请选择操作模板"
+          @change="handleTemplateChange"
+        />
       </template>
     </BasicForm>
   </div>
 </template>
-
-<script setup lang="ts">
-  import { ref, reactive, computed, onMounted } from 'vue'
-  import type { FormInstance, FormRules } from 'element-plus'
-  import { FormItemSchema, useForm } from '@/components/form'
-
-  const schema: FormItemSchema[] = [
-    {
-      prop: 'roleId',
-      label: '节点角色',
-      component: 'ApiSelect',
-      colSpan: 24,
-      componentProps: {
-        api: '/role/list',
-        labelField: 'roleName',
-        valueField: 'roleID',
-        title: '角色'
-      }
-    },
-    {
-      prop: 'templateID',
-      label: '故障模板',
-      component: 'ApiSelect',
-      colSpan: 24,
-      componentProps: {
-        api: '/templatefault/list',
-        labelField: 'templateName',
-        valueField: 'templateID',
-        title: '故障模板'
-      }
-    },
-    {
-      prop: 'category',
-      label: '节点分类',
-      component: 'Input',
-      colSpan: 24,
-      componentProps: {
-        placeholder: '请输入分类'
-      }
-    },
-    {
-      prop: 'param1Type',
-      label: '节点参数',
-      component: 'Select',
-      colSpan: 10,
-      componentProps: {
-        placeholder: '请选择类型',
-        options: [
-          { label: '文本', value: 1 },
-          { label: '数字', value: 2 },
-          { label: '日期', value: 3 }
-        ]
-      }
-    },
-    {
-      prop: 'param2Desc',
-      label: '',
-      labelWidth: '0px',
-      component: 'Input',
-      colSpan: 14,
-      componentProps: {
-        placeholder: '请输入参数2说明'
-      }
-    },
-    {
-      prop: 'dynamicParams',
-      label: '',
-      labelWidth: '0px',
-      component: 'Input',
-      colSpan: 24,
-      componentProps: {
-        placeholder: '请输入动态参数'
+<script setup lang="ts" name="DrawerOperation">
+  import { useForm } from '@/components/form'
+  import { EasyProcess } from '../common/process.type'
+  import { ProcessFault, ProcessServer } from '@/api/course.api'
+
+  const props = withDefaults(defineProps<{ node: EasyProcess.FaultNode }>(), {})
+
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+  const templateList = ref<ProcessFault[]>([])
+  const templateSelected = ref<ProcessFault>()
+
+  const [BasicForm, formApi] = useForm({
+    labelWidth: '100px',
+    schema: [
+      { prop: 'role', label: '选择角色', component: 'ApiSelect', componentProps: { api: '/role/list', labelField: 'roleName', valueField: 'roleID' } },
+      { prop: 'templateID', label: '操作模', component: 'Input' },
+      { prop: 'templateType', label: '操作模板类型', component: 'Input' },
+      { prop: 'category', label: '节点分类', component: 'Input' },
+      { prop: 'categoryName', label: '分类名称', component: 'Input' },
+      { prop: 'subCategory', label: '子分类', component: 'Input' },
+      { prop: 'subCategoryName', label: '子分类名称', component: 'Input' },
+      {
+        prop: 'param1Type',
+        label: '参数1类型',
+        component: 'Select',
+        colSpan: 10,
+        componentProps: {
+          placeholder: '请选择类型',
+          options: [
+            { label: '文本', value: 1 },
+            { label: '数字', value: 2 },
+            { label: '日期', value: 3 }
+          ]
+        },
+        ifShow: () => templateSelected.value?.param1Type != -1
+      },
+      {
+        prop: 'param1Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param1Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param1Type != -1
+      },
+      {
+        prop: 'param2Type',
+        label: '参数2类型',
+        component: 'ApiDict',
+        componentProps: { code: 'gender' },
+        colSpan: 10,
+        ifShow: () => templateSelected.value?.param2Type != -1
+      },
+      {
+        prop: 'param2Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param2Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param2Type != -1
+      },
+      { prop: 'param3Type', label: '参数3类型', component: 'Select', colSpan: 10, ifShow: () => templateSelected.value?.param3Type != -1 },
+      {
+        prop: 'param3Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param3Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param3Type != -1
+      },
+      { prop: 'param4Type', label: '参数4类型', component: 'Select', colSpan: 10, ifShow: () => templateSelected.value?.param4Type != -1 },
+      {
+        prop: 'param4Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param4Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param4Type != -1
+      },
+      { prop: 'param5Type', label: '参数5类型', component: 'Select', colSpan: 10, ifShow: () => templateSelected.value?.param5Type != -1 },
+      {
+        prop: 'param5Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param5Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param5Type != -1
       }
-    }
-  ]
-  // 表单验证规则
-  const rules: FormRules = {
-    roleId: [{ required: true, message: '请选择节点角色', trigger: 'change' }],
-    templateID: [{ required: true, message: '请选择故障模板', trigger: 'change' }],
-    category: [{ required: true, message: '请选择分类', trigger: 'change' }]
-  }
-  const [BasicForm, formApi] = useForm({ labelWidth: '100px', schema: schema, rules: rules })
-
-  /**
-   * 故障类模板
-   */
-  export interface Fault {
-    templateID: number /** 模板ID */
-    templateName: string /** 模板名称 */
-    category: number /** 分类 */
-    categoryName: string /** 分类名称 */
-    faultLogicMap: number /** 故障逻辑映射 */
-    param1Desc: string /** 参数1说明 */
-    param1Type: number /** 参数1类型 */
-    param2Desc: string /** 参数2说明 */
-    param2Type: number /** 参数2类型 */
-    param3Desc: string /** 参数3说明 */
-    param3Type: number /** 参数3类型 */
-    param4Desc: string /** 参数4说明 */
-    param4Type: number /** 参数4类型 */
-    param5Desc: string /** 参数5说明 */
-    param5Type: number /** 参数5类型 */
-    subCategory: number /** 子分类 */
-    subCategoryName: string /** 子分类名称 */
-  }
-
-  // Props
-  interface Props {
-    config?: Partial<Fault>
-  }
-
-  const props = withDefaults(defineProps<Props>(), {
-    config: () => ({})
+    ]
   })
 
-  // Emits
-  const emit = defineEmits<{
-    'update:config': [value: Partial<Fault>]
-    change: [value: Partial<Fault>]
-  }>()
-
-  // 表单引用
-  const formRef = ref<FormInstance>()
-
-  // 故障数据
-  const fault = reactive<Partial<Fault>>({
-    templateID: 0,
-    templateName: '',
-    category: undefined,
-    categoryName: '',
-    faultLogicMap: undefined,
-    param1Desc: '',
-    param1Type: undefined,
-    param2Desc: '',
-    param2Type: undefined,
-    param3Desc: '',
-    param3Type: undefined,
-    param4Desc: '',
-    param4Type: undefined,
-    param5Desc: '',
-    param5Type: undefined,
-    subCategory: undefined,
-    subCategoryName: '',
-    ...props.config
-  })
-
-  // 选项数据
-  const categoryOptions = ref([
-    { label: '硬件故障', value: 1 },
-    { label: '软件故障', value: 2 },
-    { label: '网络故障', value: 3 },
-    { label: '系统故障', value: 4 }
-  ])
-
-  const subCategoryOptions = ref([
-    { label: 'CPU故障', value: 1, category: 1 },
-    { label: '内存故障', value: 2, category: 1 },
-    { label: '硬盘故障', value: 3, category: 1 },
-    { label: '应用崩溃', value: 1, category: 2 },
-    { label: '服务异常', value: 2, category: 2 },
-    { label: '网络中断', value: 1, category: 3 },
-    { label: '网络延迟', value: 2, category: 3 },
-    { label: '系统资源不足', value: 1, category: 4 },
-    { label: '系统崩溃', value: 2, category: 4 }
-  ])
-
-  const paramTypeOptions = ref([
-    { label: '字符串', value: 1 },
-    { label: '数字', value: 2 },
-    { label: '布尔值', value: 3 },
-    { label: '枚举', value: 4 },
-    { label: '时间', value: 5 }
-  ])
-
-  const faultLogicOptions = ref([
-    { label: '立即触发', value: 1 },
-    { label: '延迟触发', value: 2 },
-    { label: '条件触发', value: 3 },
-    { label: '累计触发', value: 4 }
-  ])
-
-  // 计算属性:过滤子分类选项
-  const filteredSubCategoryOptions = computed(() => {
-    if (!fault.category) return []
-    return subCategoryOptions.value.filter((item) => item.category === fault.category)
-  })
-
-  // 处理方法
-  const handleCategoryChange = (value: number) => {
-    // 清除已选择的子分类
-    fault.subCategory = undefined
-    fault.subCategoryName = ''
-
-    // 设置分类名称
-    const category = categoryOptions.value.find((item) => item.value === value)
-    if (category) {
-      fault.categoryName = category.label
-    }
-  }
-
-  // 表单验证
-  const validate = (): Promise<boolean> => {
-    return new Promise((resolve) => {
-      if (!formRef.value) {
-        resolve(false)
-        return
+  const handleTemplateChange = (val: string) => {
+    if (val) {
+      const template = templateList.value.find((item) => item.templateID == val)
+      if (template) {
+        templateSelected.value = template
       }
-
-      formRef.value.validate((valid) => {
-        resolve(valid)
-      })
-    })
-  }
-
-  // 重置表单
-  const resetForm = () => {
-    if (formRef.value) {
-      formRef.value.resetFields()
     }
   }
 
-  // 获取表单数据
-  const getFormData = (): Partial<Fault> => {
-    return { ...fault }
-  }
-
-  // 监听数据变化
-  watch(
-    () => fault,
-    (newValue) => {
-      emit('update:config', newValue)
-      emit('change', newValue)
-    },
-    { deep: true }
-  )
+  onMounted(async () => {
+    const { data } = await ProcessServer.faultList()
+    templateList.value = data || []
+  })
 
-  // 监听子分类变化
-  watch(
-    () => fault.subCategory,
-    (value) => {
-      if (value) {
-        const subCategory = subCategoryOptions.value.find((item) => item.value === value)
-        if (subCategory) {
-          fault.subCategoryName = subCategory.label
+  defineExpose({
+    getNodeData: (callback: (nodeData: Record<string, unknown>) => void) => {
+      formApi.validate().then((valid) => {
+        if (valid) {
+          callback(nodeData.value)
+        } else {
+          ElMessage.error('请检查节点配置是否完整')
         }
-      }
+      })
     }
-  )
-
-  // 暴露方法给父组件
-  defineExpose({
-    validate,
-    resetForm,
-    getFormData
-  })
-
-  onMounted(() => {
-    // 初始化逻辑
-    console.log('FaultTrigger component mounted')
   })
 </script>
-
-<style lang="scss" scoped>
-  .fault-trigger-config {
-    padding: 16px;
-
-    .header {
-      font-size: 16px;
-      font-weight: 600;
-      margin-bottom: 20px;
-      color: #303133;
-      padding-bottom: 8px;
-      border-bottom: 1px solid #e4e7ed;
-    }
-
-    .form-container {
-      max-width: 600px;
-
-      .param-row {
-        display: flex;
-        align-items: center;
-
-        .el-input,
-        .el-select {
-          flex-shrink: 0;
-        }
-      }
-
-      .el-form-item {
-        margin-bottom: 20px;
-
-        &:last-child {
-          margin-bottom: 0;
-        }
-      }
-    }
-  }
-</style>

+ 19 - 1
src/components/business/EasyProcess/node/fault_node.vue

@@ -1,3 +1,21 @@
 <template>
-  <div>触发故障</div>
+  <div class="flex flex-row items-center gap-2">
+    <Icon :icon="getIcon" :width="26" :height="26"></Icon>
+    <div>{{ nodeData?.templateName || '设备故障' }}</div>
+  </div>
 </template>
+<script setup lang="ts">
+  import { EasyProcess } from '../common/process.type'
+
+  const props = withDefaults(defineProps<{ node: EasyProcess.FaultNode }>(), {})
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+  const roleIcon: string[] = ['role_default', 'role_substation', 'role_catenary', 'role_dispatch', 'role_isolator', 'role_supply']
+
+  const getIcon = computed(() => {
+    const role = nodeData.value?.role || 'role_default'
+    if (roleIcon.some((item) => item === role)) {
+      return `svg:${role}`
+    }
+    return 'svg:role_default'
+  })
+</script>

+ 138 - 1
src/components/business/EasyProcess/node/operation_drawer.vue

@@ -1,3 +1,140 @@
 <template>
-  <div> 基本操作 </div>
+  <div>
+    <BasicForm :model="nodeData">
+      <template #templateID="{ model }">
+        <el-select-v2
+          v-model="model.templateID"
+          :options="
+            templateList.map((item) => ({
+              label: item.templateName,
+              value: item.templateID
+            }))
+          "
+          placeholder="请选择操作模板"
+          @change="handleTemplateChange"
+        />
+      </template>
+    </BasicForm>
+  </div>
 </template>
+<script setup lang="ts" name="DrawerOperation">
+  import { useForm } from '@/components/form'
+  import { EasyProcess } from '../common/process.type'
+  import { ProcessOperate, ProcessServer } from '@/api/course.api'
+
+  const props = withDefaults(defineProps<{ node: EasyProcess.OperationNode }>(), {})
+
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+  const templateList = ref<ProcessOperate[]>([])
+  const templateSelected = ref<ProcessOperate>()
+
+  const [BasicForm, formApi] = useForm({
+    labelWidth: '100px',
+    schema: [
+      { prop: 'role', label: '选择角色', component: 'ApiSelect', componentProps: { api: '/role/list', labelField: 'roleName', valueField: 'roleID' } },
+      { prop: 'templateID', label: '操作模', component: 'Input' },
+      { prop: 'templateType', label: '操作模板类型', component: 'Input' },
+      { prop: 'category', label: '节点分类', component: 'Input' },
+      { prop: 'categoryName', label: '分类名称', component: 'Input' },
+      { prop: 'subCategory', label: '子分类', component: 'Input' },
+      { prop: 'subCategoryName', label: '子分类名称', component: 'Input' },
+      {
+        prop: 'param1Type',
+        label: '参数1类型',
+        component: 'Select',
+        colSpan: 10,
+        componentProps: {
+          placeholder: '请选择类型',
+          options: [
+            { label: '文本', value: 1 },
+            { label: '数字', value: 2 },
+            { label: '日期', value: 3 }
+          ]
+        },
+        ifShow: () => templateSelected.value?.param1Type != -1
+      },
+      {
+        prop: 'param1Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param1Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param1Type != -1
+      },
+      {
+        prop: 'param2Type',
+        label: '参数2类型',
+        component: 'ApiDict',
+        componentProps: { code: 'gender' },
+        colSpan: 10,
+        ifShow: () => templateSelected.value?.param2Type != -1
+      },
+      {
+        prop: 'param2Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param2Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param2Type != -1
+      },
+      { prop: 'param3Type', label: '参数3类型', component: 'Select', colSpan: 10, ifShow: () => templateSelected.value?.param3Type != -1 },
+      {
+        prop: 'param3Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param3Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param3Type != -1
+      },
+      { prop: 'param4Type', label: '参数4类型', component: 'Select', colSpan: 10, ifShow: () => templateSelected.value?.param4Type != -1 },
+      {
+        prop: 'param4Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param4Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param4Type != -1
+      },
+      { prop: 'param5Type', label: '参数5类型', component: 'Select', colSpan: 10, ifShow: () => templateSelected.value?.param5Type != -1 },
+      {
+        prop: 'param5Desc',
+        label: '',
+        labelWidth: '0px',
+        component: 'Input',
+        componentProps: () => ({ placeholder: templateSelected.value?.param5Desc || '请输入参数' }),
+        colSpan: 14,
+        ifShow: () => templateSelected.value?.param5Type != -1
+      }
+    ]
+  })
+
+  const handleTemplateChange = (val: string) => {
+    if (val) {
+      const template = templateList.value.find((item) => item.templateID == val)
+      if (template) {
+        templateSelected.value = template
+      }
+    }
+  }
+
+  onMounted(async () => {
+    const { data } = await ProcessServer.operateList()
+    templateList.value = data || []
+  })
+
+  defineExpose({
+    getNodeData: (callback: (nodeData: Record<string, unknown>) => void) => {
+      formApi.validate().then((valid) => {
+        if (valid) {
+          callback(nodeData.value)
+        } else {
+          ElMessage.error('请检查节点配置是否完整')
+        }
+      })
+    }
+  })
+</script>

+ 16 - 23
src/components/business/EasyProcess/node/operation_node.vue

@@ -1,29 +1,22 @@
-<!--
-  @desc: 操作节点
-  @date: 2025-10-10
--->
-
 <template>
-  <div class="operationNode">{{ title }} </div>
+  <div class="flex flex-row items-center gap-2">
+    <Icon :icon="getIcon" :width="26" :height="26"></Icon>
+    <div>{{ nodeData?.templateName || '常规操作' }}</div>
+  </div>
 </template>
-
 <script setup lang="ts">
-  /** -------------------- 组件参数 -------------------- */
-  interface Props {
-    title?: string
-  }
-  const props = withDefaults(defineProps<Props>(), {
-    title: 'hello'
-  })
+  import { EasyProcess } from '../common/process.type'
 
-  /** -------------------- 生命周期 ----------------------- */
-  onMounted(() => {
-    console.log(`${props.title} mounted`)
+  const props = withDefaults(defineProps<{ node: EasyProcess.OperationNode }>(), {})
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+
+  const roleIcon: string[] = ['role_default', 'role_substation', 'role_catenary', 'role_dispatch', 'role_isolator', 'role_supply']
+
+  const getIcon = computed(() => {
+    const role = nodeData.value?.role || 'role_default'
+    if (roleIcon.some((item) => item === role)) {
+      return `svg:${role}`
+    }
+    return 'svg:role_default'
   })
 </script>
-
-<style scoped lang="scss">
-  .operationNode {
-    padding: 10px;
-  }
-</style>

+ 71 - 1
src/components/business/EasyProcess/node/voice_drawer.vue

@@ -1,3 +1,73 @@
 <template>
-  <div>语言广播配置</div>
+  <div>
+    <BasicForm :model="nodeData">
+      <template #templateID="{ model }">
+        <el-select-v2
+          v-model="model.templateID"
+          :options="
+            templateList.map((item) => ({
+              label: item.templateName,
+              value: item.templateID
+            }))
+          "
+          placeholder="请选择语言模板"
+          @change="handleTemplateChange"
+        />
+      </template>
+    </BasicForm>
+  </div>
 </template>
+<script setup lang="ts" name="DrawerOperation">
+  import { useForm } from '@/components/form'
+  import { EasyProcess } from '../common/process.type'
+  import { ProcessVoice, ProcessServer } from '@/api/course.api'
+
+  const props = withDefaults(defineProps<{ node: EasyProcess.VoiceNode }>(), {})
+
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+  const templateList = ref<ProcessVoice[]>([])
+  const templateSelected = ref<ProcessVoice>()
+
+  const [BasicForm, formApi] = useForm({
+    labelWidth: '100px',
+    schema: [
+      { prop: 'role', label: '选择角色', component: 'ApiSelect', componentProps: { api: '/role/list', labelField: 'roleName', valueField: 'roleID' } },
+      { prop: 'templateID', label: '语言模板', component: 'Input' },
+      { prop: 'templateType', label: '语音模板类型', component: 'Input' },
+      { prop: 'category', label: '节点分类', component: 'Input' },
+      { prop: 'categoryName', label: '分类名称', component: 'Input' },
+      { prop: 'subCategory', label: '子分类', component: 'Input' },
+      { prop: 'subCategoryName', label: '子分类名称', component: 'Input' },
+      { prop: 'voiceContent', label: '语音内容', component: 'Input' },
+      { prop: 'evalTemplate', label: '评估模板', component: 'Input' },
+      { prop: 'destRoleID', label: '目标角色ID', component: 'Input' }
+    ]
+  })
+
+  const handleTemplateChange = (val: string) => {
+    if (val) {
+      const template = templateList.value.find((item) => item.templateID == val)
+      if (template) {
+        templateSelected.value = template
+        nodeData.value.subCategoryName = template.subCategoryName
+      }
+    }
+  }
+
+  onMounted(async () => {
+    const { data } = await ProcessServer.voiceList()
+    templateList.value = data || []
+  })
+
+  defineExpose({
+    getNodeData: (callback: (nodeData: Record<string, unknown>) => void) => {
+      formApi.validate().then((valid) => {
+        if (valid) {
+          callback(nodeData.value)
+        } else {
+          ElMessage.error('请检查节点配置是否完整')
+        }
+      })
+    }
+  })
+</script>

+ 20 - 1
src/components/business/EasyProcess/node/voice_node.vue

@@ -1,3 +1,22 @@
 <template>
-  <div>语言广播</div>
+  <div class="flex flex-row items-center gap-2">
+    <Icon :icon="getIcon" :width="26" :height="26"></Icon>
+    <div>{{ nodeData?.templateName || '语言广播' }}</div>
+  </div>
 </template>
+<script setup lang="ts">
+  import { EasyProcess } from '../common/process.type'
+
+  const props = withDefaults(defineProps<{ node: EasyProcess.VoiceNode }>(), {})
+  const nodeData = ref<Record<string, unknown>>(props.node.nodeData || {})
+
+  const roleIcon: string[] = ['role_default', 'role_substation', 'role_catenary', 'role_dispatch', 'role_isolator', 'role_supply']
+
+  const getIcon = computed(() => {
+    const role = nodeData.value?.role || 'role_default'
+    if (roleIcon.some((item) => item === role)) {
+      return `svg:${role}`
+    }
+    return 'svg:role_default'
+  })
+</script>

+ 3 - 7
src/components/form/components/upload.vue

@@ -174,18 +174,13 @@
           await uploadChunk(chunk.chunk, file.name, uploadId.value, fileInfoId, chunk.index)
         } catch {
           // 取消上传
-          await abortMultipartUpload({
-            fileInfoId
-          })
+          await abortMultipartUpload({ fileInfoId })
           return {}
         }
       }
 
       // 3. 完成上传
-      const completeRes: any = await completeMultipartUpload({
-        uploadId: uploadId.value,
-        fileInfoId
-      })
+      const completeRes: any = await completeMultipartUpload({ uploadId: uploadId.value, fileInfoId })
 
       return {
         uid: completeRes.fileInfoId,
@@ -231,6 +226,7 @@
     ...props.headers
   }
   const handleChange = (info: any) => {
+    if (!info.file) return
     if (info.status === 'success') {
       // 更新file-list当前file的uid
       fileList.value = fileList.value.map((item: any) => {

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/components/icon/svg/role_catenary.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/components/icon/svg/role_default.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/components/icon/svg/role_dispatch.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/components/icon/svg/role_isolator.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/components/icon/svg/role_substation.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
src/components/icon/svg/role_supply.svg


+ 6 - 6
src/components/view/components/api-dict.vue

@@ -107,14 +107,14 @@
       // 处理数组值的情况
       return props.value
         .map((val) => {
-          const option = options.value.find((opt) => opt.name === val)
-          return option ? option.name : val
+          const option = options.value.find((opt) => opt.label === val)
+          return option ? option.label : val
         })
         .join(', ')
     } else {
       // 处理单个值的情况
-      const option = options.value.find((opt) => opt.name === props.value)
-      return option ? option.name : props.value
+      const option = options.value.find((opt) => opt.label === props.value)
+      return option ? option.label : props.value
     }
   })
   const createOption = (label: string | number, value: string | number): NewOptionType => ({
@@ -149,7 +149,7 @@
   const cNewOptions = computed<NewOptionType[]>(() => {
     if (Array.isArray(props.value)) {
       return props.value.map((val: any) => {
-        const option = options.value.find((opt) => opt.name === val) ?? null
+        const option = options.value.find((opt) => opt.label === val) ?? null
         // 修复:根据选项在 options 数组中的实际索引来获取 colorList 颜色
         const optionIndex = option ? options.value.indexOf(option) : 0
         return createTagOption(option, val, optionIndex)
@@ -158,7 +158,7 @@
       if (props.value === undefined || props.value === null) {
         return []
       }
-      const option = options.value.find((opt) => opt.name === props.value)
+      const option = options.value.find((opt) => opt.label === props.value)
       // 修复:根据选项在 options 数组中的实际索引来获取 colorList 颜色
       const optionIndex = option ? options.value.indexOf(option) : 0
       return option ? [createTagOption(option, props.value, optionIndex)] : props.value !== null && props.value !== undefined ? [createOption(props.value, props.value)] : []

+ 10 - 10
src/views/train/course/detail/course-flow.vue

@@ -1,26 +1,26 @@
 <template>
   <div class="art-full-height w-full">
-    <ProcessDesigner ref="processRef" :data="processData" />
+    <EasyProcess ref="processRef" :data="processData" />
   </div>
 </template>
 
 <script setup lang="ts">
-  import ProcessDesigner from '@/components/business/EasyProcess/index.vue'
+  import { EasyProcess as ProcessType } from '@/components/business/EasyProcess/common/process.type'
+  import EasyProcess from '@/components/business/EasyProcess/index.vue'
   import { onMounted, ref } from 'vue'
-  import mockData from './flow.json'
 
+  const props = withDefaults(defineProps<{ data?: string }>(), {})
   // 定义 ProcessDesigner 的实例类型
-  // type ProcessDesignerInstance = InstanceType<typeof ProcessDesigner>
+  type ProcessDesignerInstance = InstanceType<typeof EasyProcess>
   // 审批数据
-  // const processData = ref<Record<string, any>>({})
+  const processData = ref<ProcessType.NodeConfig>()
   // 组件实例 ref
-  // const processRef = ref<ProcessDesignerInstance | null>(null)
-
-  const processData = ref({})
-  const processRef = ref(null)
+  const processRef = ref<ProcessDesignerInstance>()
 
   onMounted(() => {
-    processData.value = mockData
+    if (props.data) {
+      processData.value = JSON.parse(props.data) as ProcessType.NodeConfig
+    }
   })
 </script>
 

+ 12 - 9
src/views/train/course/detail/course.schem.ts

@@ -20,19 +20,22 @@ export const schema: FormItemSchema[] = [
   { prop: '', label: '', labelWidth: '0px', component: 'Divider', colSpan: 24, componentProps: { title: '课程基本信息' } },
   { prop: 'courseName', label: '课程名称', component: 'Input', colSpan: 6 },
   { prop: 'totalDuration', label: '课程时长', component: 'InputNumber', colSpan: 6, componentProps: { style: { width: '100%' } }, defaultValue: 30 },
-  { prop: 'typeID', label: '分类ID', component: 'Select', colSpan: 6, defaultValue: '1', componentProps: { options: options.value, clearable: true } },
-  { prop: 'externalTypeID', label: '外部分类ID', component: 'Select', colSpan: 6, defaultValue: '1', componentProps: { options: options.value, clearable: true } },
+  { prop: 'typeID', label: '分类ID', component: 'ApiDict', componentProps: { code: 'category' }, colSpan: 6 },
+  { prop: 'externalTypeID', label: '外部分类ID', component: 'Input', colSpan: 6, defaultValue: '' },
   { prop: 'courseDesc', label: '课程描述', component: 'Input', colSpan: 12 },
-  { prop: 'lineNo', label: '线路号', component: 'Select', colSpan: 6, defaultValue: '1', componentProps: { options: options.value, clearable: true } },
-  { prop: 'runningMap', label: '运行图', component: 'Select', colSpan: 6, defaultValue: '1', componentProps: { options: options.value, clearable: true } },
+  { prop: 'lineNo', label: '线路号', component: 'ApiDict', componentProps: { code: 'line_type' }, colSpan: 6 },
+  { prop: 'runningMap', label: '运行图', component: 'Input', colSpan: 6 },
   { prop: 'guideDesc', label: '指导描述', component: 'Input', colSpan: 12 },
-  { prop: 'guideFilePath', label: '指导文件地址', component: 'Upload', colSpan: 12 },
+  {
+    prop: 'guideFilePath',
+    label: '指导文件地址',
+    component: 'Upload',
+    componentProps: { action: `${import.meta.env.VITE_API_URL}/static/upload`, maxSize: 1024 * 1024 * 5, accept: '.pdf,.mp4,.png', limit: 1 },
+    colSpan: 12
+  },
   // 课程运行流程
   { prop: '', label: '', labelWidth: '0px', component: 'Divider', colSpan: 24, componentProps: { title: '课程运行流程' } },
-  { prop: 'courseDataStream', labelWidth: '0px', label: '', component: 'Input', colSpan: 24 },
-  // 电子考评
-  { prop: '', label: '', labelWidth: '0px', component: 'Divider', colSpan: 24, componentProps: { title: '电子考评' } },
-  { prop: 'evaluate', label: '电子考评', component: 'Input', colSpan: 24 }
+  { prop: 'courseDataStream', labelWidth: '0px', label: '', component: 'Input', colSpan: 24 }
 ]
 export const courseSchema: FormItemSchema[] = [
   // 课程基本信息

+ 0 - 75
src/views/train/course/detail/flow.json

@@ -1,75 +0,0 @@
-{
-  "processId": "10001",
-  "processName": "请假审批11",
-  "nodeConfig": {
-    "nodeName": "开始",
-    "nodeType": "start",
-    "nodeData": {
-      "name": null
-    },
-    "childNode": {
-      "nodeName": "审批人",
-      "nodeType": "approver",
-      "nodeData": {
-        "name": null
-      },
-      "childNode": {
-        "nodeName": "路由",
-        "nodeType": "router",
-        "conditionNodes": [
-          {
-            "nodeName": "条件",
-            "nodeType": "condition",
-            "isLastCondition": false,
-            "nodeData": {
-              "days": 1
-            },
-            "childNode": {
-              "nodeName": "审批人",
-              "nodeType": "approver",
-              "nodeData": {
-                "name": "赵六"
-              }
-            }
-          },
-          {
-            "nodeName": "条件",
-            "nodeType": "condition",
-            "isLastCondition": false,
-            "nodeData": {
-              "days": 3
-            },
-            "childNode": {
-              "nodeName": "审批人",
-              "nodeType": "approver",
-              "nodeData": {
-                "name": "老王"
-              }
-            }
-          },
-          {
-            "nodeName": "条件",
-            "nodeType": "condition",
-            "isLastCondition": true,
-            "nodeData": {},
-            "childNode": {}
-          }
-        ],
-        "childNode": {
-          "nodeName": "审批人",
-          "nodeType": "approver",
-          "nodeData": {
-            "name": "李四"
-          },
-          "childNode": {
-            "nodeName": "抄送人",
-            "nodeType": "notify",
-            "nodeData": {
-              "name": "王五"
-            }
-          }
-        }
-      }
-    }
-  }
-}

+ 22 - 45
src/views/train/course/detail/index.vue

@@ -5,6 +5,7 @@
       <h2 class="title" v-else-if="courseEdit">编辑课程</h2>
       <h2 class="title" v-else>课程详情</h2>
       <el-space wrap>
+        <el-button v-if="!courseQuery" v-ripple type="primary" @click="handleCancel"> 取消 </el-button>
         <el-button v-if="!courseQuery" v-ripple type="primary" @click="addCourse"> 保存课程 </el-button>
       </el-space>
     </div>
@@ -30,10 +31,10 @@
 
   // 路由参数
   const route = useRoute()
-  const courseId = computed<number>(() => Number(route.params.id))
+  const courseId = computed<string>(() => String(route.params.id))
   const query = computed(() => route.query)
 
-  const courseNew = computed(() => courseId.value === 0)
+  const courseNew = computed(() => courseId.value === '0')
   const courseEdit = computed(() => !courseNew.value && query.value.edit === 'true')
   const courseQuery = computed(() => !courseNew.value && !courseEdit.value)
   const loading = ref(true)
@@ -46,57 +47,22 @@
   const addCourse = async () => {
     formApi
       .validate()
-      .then((data2) => {
-        console.log('validate success', data2)
+      .then(async () => {
         const data = formData.value
         if (courseNew.value) {
-          CourseServer.add({
-            courseName: data.courseName,
-            courseDesc: data.courseDesc,
-            totalDuration: Number(data.totalDuration || 10),
-            typeID: Number(data.typeID || 1),
-            courseDataStream: data.courseDataStream,
-            externalTypeID: Number(data.externalTypeID || 1),
-            guideDesc: data.guideDesc,
-            guideFilePath: data.guideFilePath,
-            lineNo: Number(data.lineNo || 1),
-            runningMap: Number(data.runningMap || 1),
-            virtualRole: data.virtualRole,
-            virtualStartTime: Number(data.virtualStartTime || 0)
-          })
-            .then(() => {
-              ElMessage.success('新增课程成功')
-              router.push({ path: '/course/list' })
-            })
-            .catch(() => ElMessage.error('新增课程失败'))
+          await CourseServer.add(data)
+          ElMessage.success('新增课程成功')
+          router.push({ path: '/course/list' })
         } else if (courseEdit.value) {
-          CourseServer.update({
-            courseID: data.courseID,
-            courseName: data.courseName,
-            courseDesc: data.courseDesc,
-            totalDuration: Number(data.totalDuration || 10),
-            typeID: Number(data.typeID || 1),
-            courseDataStream: data.courseDataStream,
-            externalTypeID: Number(data.externalTypeID || 1),
-            courseGUID: data.courseGUID,
-            guideDesc: data.guideDesc,
-            guideFilePath: data.guideFilePath,
-            lineNo: Number(data.lineNo || 1),
-            runningMap: Number(data.runningMap || 1),
-            virtualRole: data.virtualRole,
-            virtualStartTime: Number(data.virtualStartTime || 0)
-          })
-            .then(() => {
-              ElMessage.success('新增课程成功')
-              router.push({ path: '/course/list' })
-            })
-            .catch(() => ElMessage.error('新增课程失败'))
+          await CourseServer.update(data)
+          ElMessage.success('编辑课程成功')
+          router.push({ path: '/course/list' })
         } else {
           ElMessage.error('不能编辑课程详情')
         }
       })
       .catch(() => {
-        ElMessage.error('请检查课程信息')
+        ElMessage.error('请填写完整课程信息')
       })
   }
 
@@ -113,6 +79,17 @@
       loading.value = false
     }
   })
+
+  const handleCancel = () => {
+    // 二次确认
+    ElMessageBox.confirm('确定要取消吗?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }).then(() => {
+      router.push({ path: '/course/list' })
+    })
+  }
 </script>
 <style lang="scss" scoped>
   .course-form {