Эх сурвалжийг харах

feat(views): 初步完成“配置协议”页面各步骤基本功能

wangcong 3 сар өмнө
parent
commit
c8d94f73c6

+ 217 - 0
src/views/setup-protocol/CustomParams.vue

@@ -0,0 +1,217 @@
+<script setup lang="ts">
+import { computed, reactive } from 'vue';
+
+import { useViewVisible } from '@/hooks/view-visible';
+
+import type { CustomParamsForm, FormRules } from '@/types';
+
+const { visible, showView, hideView } = useViewVisible();
+
+const customParamsForm = reactive<CustomParamsForm>({
+  gatewayParamName: '',
+  gatewayParamCode: '',
+  unit: '',
+  module: '',
+  readWriteType: '',
+  parsingType: '',
+  writeFunctionCode: '',
+  readFunctionCode: '',
+  addressLength: 0,
+  registerAddress: 0,
+  coefficient: 0,
+  isHighFrequencyParameter: '',
+  readCalculationFormula: '',
+  writeCalculationFormula: '',
+  decimalPlaces: 0,
+  contiguousAddressingRange: 0,
+});
+
+const rules = computed<FormRules<CustomParamsForm>>(() => {
+  return {
+    gatewayParamName: [
+      {
+        required: true,
+        message: 'plz enter',
+        trigger: 'change',
+      },
+    ],
+    gatewayParamCode: [
+      {
+        required: true,
+        message: 'plz enter',
+        trigger: 'change',
+      },
+    ],
+    readWriteType: [
+      {
+        required: true,
+        message: 'plz enter',
+        trigger: 'change',
+      },
+    ],
+    parsingType: [
+      {
+        required: true,
+        message: 'plz enter',
+        trigger: 'change',
+      },
+    ],
+    addressLength: [
+      {
+        required: true,
+        message: 'plz enter',
+        trigger: 'change',
+      },
+    ],
+    registerAddress: [
+      {
+        required: true,
+        message: 'plz enter',
+        trigger: 'change',
+      },
+    ],
+  };
+});
+
+const handleOk = () => {
+  hideView();
+};
+
+defineExpose({
+  showView,
+  hideView,
+});
+</script>
+
+<template>
+  <AModal v-model:open="visible" :title="$t('setupProtocol.addCustomParams')" :width="720" centered @ok="handleOk">
+    <AForm :model="customParamsForm" :rules="rules" layout="vertical">
+      <ARow :gutter="[60, 32]">
+        <ACol :span="12">
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.gatewayParamName')" name="gatewayParamName">
+            <AInput
+              v-model:value="customParamsForm.gatewayParamName"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.unit')" name="unit">
+            <AInput v-model:value="customParamsForm.unit" class="protocol-input" :placeholder="$t('common.plzEnter')" />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.readWriteType')" name="readWriteType">
+            <AInput
+              v-model:value="customParamsForm.readWriteType"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.writeFunctionCode')" name="writeFunctionCode">
+            <AInput
+              v-model:value="customParamsForm.writeFunctionCode"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.addressLength')" name="addressLength">
+            <AInput
+              v-model:value="customParamsForm.addressLength"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            /> </AFormItem
+          ><AFormItem :label="$t('setupProtocol.protocolParamFields.coefficient')" name="coefficient">
+            <AInput
+              v-model:value="customParamsForm.coefficient"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem
+            :label="$t('setupProtocol.protocolParamFields.readCalculationFormula')"
+            name="readCalculationFormula"
+          >
+            <AInput
+              v-model:value="customParamsForm.readCalculationFormula"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            /> </AFormItem
+          ><AFormItem :label="$t('setupProtocol.protocolParamFields.decimalPlaces')" name="decimalPlaces">
+            <AInput
+              v-model:value="customParamsForm.decimalPlaces"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="12">
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.gatewayParamCode')" name="gatewayParamCode">
+            <AInput
+              v-model:value="customParamsForm.gatewayParamCode"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.module')" name="module">
+            <AInput
+              v-model:value="customParamsForm.module"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.parsingType')" name="parsingType">
+            <AInput
+              v-model:value="customParamsForm.parsingType"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.readFunctionCode')" name="readFunctionCode">
+            <AInput
+              v-model:value="customParamsForm.readFunctionCode"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem :label="$t('setupProtocol.protocolParamFields.registerAddress')" name="registerAddress">
+            <AInput
+              v-model:value="customParamsForm.registerAddress"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem
+            :label="$t('setupProtocol.protocolParamFields.isHighFrequencyParameter')"
+            name="isHighFrequencyParameter"
+          >
+            <AInput
+              v-model:value="customParamsForm.isHighFrequencyParameter"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem
+            :label="$t('setupProtocol.protocolParamFields.writeCalculationFormula')"
+            name="writeCalculationFormula"
+          >
+            <AInput
+              v-model:value="customParamsForm.writeCalculationFormula"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+          <AFormItem
+            :label="$t('setupProtocol.protocolParamFields.contiguousAddressingRange')"
+            name="contiguousAddressingRange"
+          >
+            <AInput
+              v-model:value="customParamsForm.contiguousAddressingRange"
+              class="protocol-input"
+              :placeholder="$t('common.plzEnter')"
+            />
+          </AFormItem>
+        </ACol>
+      </ARow>
+    </AForm>
+  </AModal>
+</template>
+
+<!-- <style lang="scss" scoped></style> -->

+ 5 - 0
src/views/setup-protocol/FinishProtocol.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>
+    <AButton type="primary">{{ $t('setupProtocol.downloadProtocolToLocal') }}</AButton>
+  </div>
+</template>

+ 426 - 0
src/views/setup-protocol/ProtocolContent.vue

@@ -0,0 +1,426 @@
+<script setup lang="ts">
+import { onMounted, ref, useTemplateRef } from 'vue';
+import { debounce } from 'lodash-es';
+
+import { useDictData } from '@/hooks/dict-data';
+import { useRequest } from '@/hooks/request';
+import { getProtocolParamList } from '@/api';
+import { DictCode } from '@/constants';
+
+import CustomParams from './CustomParams.vue';
+
+import type { DefaultOptionType, SelectValue } from 'ant-design-vue/es/select';
+import type { Key } from 'ant-design-vue/es/table/interface';
+import type { CheckedType, PageParams, ProtocolBaseInfo, ProtocolParamInfo } from '@/types';
+
+interface Props {
+  info: ProtocolBaseInfo;
+}
+
+const props = defineProps<Props>();
+
+const { handleRequest } = useRequest();
+const { dictData: protocolTypes, getDictData: getProtocolTypes } = useDictData(DictCode.AllProtocolType);
+const { dictData: baudRateList, getDictData: getBaudRateList } = useDictData(DictCode.BaudRate);
+const { dictData: readContinuousAddr, getDictData: getReadContinuousAddr } = useDictData(DictCode.ReadContinuousAddr);
+const { dictData: byteOrderList, getDictData: getByteOrderList } = useDictData(DictCode.ByteOrder);
+const { dictData: addrOrderList, getDictData: getAddrOrderList } = useDictData(DictCode.AddrOrder);
+
+onMounted(() => {
+  handleRequest(async () => {
+    await getProtocolTypes();
+    await getBaudRateList();
+    await getReadContinuousAddr();
+    await getByteOrderList();
+    await getAddrOrderList();
+    await getCurrentProtocolParams();
+  });
+});
+
+const handleReadContinuousAddrChange = (checked: CheckedType) => {
+  const [first, second] = readContinuousAddr.value;
+
+  if (checked === first?.dictValue) {
+    props.info.readContinuousAddrCode = Number(first?.dictEngValue);
+  } else {
+    props.info.readContinuousAddrCode = Number(second?.dictEngValue);
+  }
+};
+
+const handleByteOrderChange = (_value: SelectValue, option: DefaultOptionType) => {
+  props.info.byteOrderCode = option.key;
+};
+
+const handleAddrOrderChange = (_value: SelectValue, option: DefaultOptionType) => {
+  props.info.addrOrderCode = option.key;
+};
+
+const paramList = ref<ProtocolParamInfo[]>([]);
+const paramTotal = ref(0);
+const paramNameOrCode = ref('');
+const pageParams = ref<PageParams>({
+  pageIndex: 1,
+  pageSize: 10,
+  pageSorts: [
+    {
+      column: 'id',
+      asc: true,
+    },
+  ],
+});
+
+const getCurrentProtocolParams = async () => {
+  const { id } = props.info;
+
+  if (!id) {
+    return;
+  }
+
+  const { records, total } = await getProtocolParamList({
+    ...pageParams.value,
+    baseInfoId: id,
+    paramName: paramNameOrCode.value,
+    paramCode: paramNameOrCode.value,
+  });
+
+  paramList.value = records;
+  paramTotal.value = total;
+};
+
+const handleParamNameOrCodeChange = debounce(() => {
+  pageParams.value.pageIndex = 1;
+  getCurrentProtocolParams();
+}, 300);
+
+const handlePageChange = (page: number, pageSize: number) => {
+  pageParams.value.pageIndex = page;
+  pageParams.value.pageSize = pageSize;
+  getCurrentProtocolParams();
+};
+
+const selectedParamIds = ref<Key[]>([]);
+
+const handleParamSelectChange = (selectedRowKeys: Key[]) => {
+  selectedParamIds.value = selectedRowKeys;
+};
+
+const customParamsRef = useTemplateRef('customParams');
+</script>
+
+<template>
+  <div>
+    <AForm class="protocol-container" layout="vertical">
+      <div class="protocol-label">{{ $t('common.basicInfo') }}</div>
+      <ARow>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.protocolName')" name="protocolName">
+            <AInput v-model:value="info.protocolName" class="protocol-input" :placeholder="$t('common.plzEnter')" />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.protocolType')" name="protocolType">
+            <ASelect
+              v-model:value="info.protocolType"
+              class="protocol-input"
+              :placeholder="$t('common.plzSelect')"
+              disabled
+            >
+              <ASelectOption v-for="item in protocolTypes" :key="item.dictValueId" :value="item.dictValue">
+                {{ item.dictValue }}
+              </ASelectOption>
+            </ASelect>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.deviceType')" name="deviceType">
+            <ASelect
+              v-model:value="info.deviceType"
+              class="protocol-input"
+              :placeholder="$t('common.plzSelect')"
+              disabled
+            >
+              <!-- <ASelectOption :value=""></ASelectOption> -->
+            </ASelect>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.dataBit')" name="dataBit">
+            <ARadioGroup v-model:value="info.dataBit">
+              <ARadio :value="5">5</ARadio>
+              <ARadio :value="6">6</ARadio>
+              <ARadio :value="7">7</ARadio>
+              <ARadio :value="8">8</ARadio>
+            </ARadioGroup>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.parityBit')" name="parityBit">
+            <ARadioGroup v-model:value="info.parityBit">
+              <ARadio value="N">N</ARadio>
+              <ARadio value="O">O</ARadio>
+              <ARadio value="E">E</ARadio>
+            </ARadioGroup>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.stopBit')" name="stopBit">
+            <ARadioGroup v-model:value="info.stopBit">
+              <ARadio value="1">1</ARadio>
+              <ARadio value="1.5">1.5</ARadio>
+              <ARadio value="2">2</ARadio>
+            </ARadioGroup>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.baudRate')" name="baudRate">
+            <ASelect v-model:value="info.baudRate" class="protocol-input" :placeholder="$t('common.plzSelect')">
+              <ASelectOption v-for="item in baudRateList" :key="item.dictValueId" :value="Number(item.dictValue)">
+                {{ item.dictValue }}
+              </ASelectOption>
+            </ASelect>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.dataSendInterval')" name="dataSendInterval">
+            <AInputNumber
+              v-model:value="info.dataSendInterval"
+              class="protocol-input"
+              addon-after="s"
+              :min="0"
+              :precision="0"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.highFreqSendInterval')" name="highFreqSendInterval">
+            <AInputNumber
+              v-model:value="info.highFreqSendInterval"
+              class="protocol-input"
+              addon-after="s"
+              :min="0"
+              :precision="0"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.readTimeout')" name="readTimeout">
+            <AInputNumber
+              v-model:value="info.readTimeout"
+              class="protocol-input"
+              addon-after="ms"
+              :min="0"
+              :precision="0"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.nextDataReadDelay')" name="nextDataReadDelay">
+            <AInputNumber
+              v-model:value="info.nextDataReadDelay"
+              class="protocol-input"
+              addon-after="ms"
+              :min="0"
+              :precision="0"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.nextRoundDataReadDelay')" name="nextRoundDataReadDelay">
+            <AInputNumber
+              v-model:value="info.nextRoundDataReadDelay"
+              class="protocol-input"
+              addon-after="ms"
+              :min="0"
+              :precision="0"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.readContinuousAddr')" name="readContinuousAddr">
+            <ASwitch
+              v-model:checked="info.readContinuousAddr"
+              :checked-value="readContinuousAddr[1]?.dictValue"
+              :un-checked-value="readContinuousAddr[0]?.dictValue"
+              :checked-children="$t('common.on')"
+              :un-checked-children="$t('common.off')"
+              @change="handleReadContinuousAddrChange"
+            />
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.readContinuousAddrLength')" name="readContinuousAddrLength">
+            <AInputNumber
+              v-model:value="info.readContinuousAddrLength"
+              class="protocol-input"
+              :min="0"
+              :precision="0"
+            />
+          </AFormItem>
+        </ACol>
+      </ARow>
+      <div class="protocol-label advanced-label">{{ $t('common.advancedSettings') }}</div>
+      <ARow :gutter="[4, 30]">
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.byteOrder')" name="byteOrder">
+            <ASelect
+              v-model:value="info.byteOrder"
+              class="protocol-input"
+              :placeholder="$t('common.plzSelect')"
+              @change="handleByteOrderChange"
+            >
+              <ASelectOption v-for="item in byteOrderList" :key="item.dictEngValue" :value="item.dictValue">
+                {{ item.dictValue }}
+              </ASelectOption>
+            </ASelect>
+          </AFormItem>
+        </ACol>
+        <ACol :span="6">
+          <AFormItem :label="$t('setupProtocol.addrOrder')" name="addrOrder">
+            <ASelect
+              v-model:value="info.addrOrder"
+              class="protocol-input"
+              :placeholder="$t('common.plzSelect')"
+              @change="handleAddrOrderChange"
+            >
+              <ASelectOption v-for="item in addrOrderList" :key="item.dictEngValue" :value="item.dictValue">
+                {{ item.dictValue }}
+              </ASelectOption>
+            </ASelect>
+          </AFormItem>
+        </ACol>
+      </ARow>
+    </AForm>
+    <div class="protocol-label params-label">
+      {{ $t('setupProtocol.protocolParams') }}
+      <span class="protocol-params-tip">{{ $t('setupProtocol.protocolTypeTipForPlc') }}</span>
+    </div>
+    <div class="protocol-container protocol-params-query">
+      <AInput
+        v-model:value="paramNameOrCode"
+        class="protocol-input"
+        :placeholder="$t('setupProtocol.plzEnterParamOrCode')"
+        allow-clear
+        @change="handleParamNameOrCodeChange"
+      />
+      <div>
+        <AButton danger :disabled="selectedParamIds.length === 0">{{ $t('setupProtocol.deleteSelected') }}</AButton>
+        <AButton @click="customParamsRef?.showView">{{ $t('setupProtocol.addCustomParams') }}</AButton>
+        <AButton type="primary">{{ $t('setupProtocol.addStandardParams') }}</AButton>
+      </div>
+    </div>
+    <ATable
+      :data-source="paramList"
+      row-key="id"
+      :row-selection="{ selectedRowKeys: selectedParamIds, onChange: handleParamSelectChange }"
+      :scroll="{ x: 2800 }"
+      :pagination="{
+        current: pageParams.pageIndex,
+        pageSize: pageParams.pageSize,
+        total: paramTotal,
+        showSizeChanger: true,
+        hideOnSinglePage: false,
+        showTotal: (total) => $t('common.pageTotal', { total }),
+        onChange: handlePageChange,
+      }"
+    >
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.customParamName')"
+        data-index="paramName"
+        :width="180"
+        fixed="left"
+      />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.result')" data-index="result" :width="110" />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.platformParamName')"
+        data-index="platformParamName"
+        :width="170"
+      />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.platformParamCode')"
+        data-index="platformParamCode"
+        :width="280"
+      />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.gatewayParamName')"
+        data-index="gatewayParamName"
+        :width="200"
+      />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.gatewayParamCode')"
+        data-index="gatewayParamCode"
+        :width="200"
+      />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.unit')" data-index="unit" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.module')" data-index="module" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.readWriteType')" data-index="readWriteType" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.readFunctionCode')" data-index="readFuncCode" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.writeFunctionCode')" data-index="writeFuncCode" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.registerAddress')" data-index="registerAddr" />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.parsingType')"
+        data-index="parsingType"
+        :width="150"
+      />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.addressLength')" data-index="addrLength" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.dataType')" data-index="dataType" />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.coefficient')" data-index="coefficient" />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.isHighFrequencyParameter')"
+        data-index="isHighFreqParam"
+        :width="150"
+      />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.writeCalculationFormula')"
+        data-index="writeCalcFormula"
+      />
+      <ATableColumn
+        :title="$t('setupProtocol.protocolParamFields.readCalculationFormula')"
+        data-index="readCalcFormula"
+      />
+      <ATableColumn :title="$t('setupProtocol.protocolParamFields.decimalPlaces')" data-index="decimalPlaces" />
+    </ATable>
+    <CustomParams ref="customParams" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.protocol-container {
+  width: 62%;
+}
+
+.protocol-label {
+  margin-bottom: 24px;
+  font-size: 14px;
+  font-weight: 600;
+  line-height: 22px;
+  color: var(--antd-color-text);
+
+  &.advanced-label {
+    margin-top: 46px;
+  }
+
+  &.params-label {
+    margin-top: 56px;
+  }
+}
+
+.protocol-input {
+  width: 224px;
+}
+
+.protocol-params-tip {
+  font-size: 11px;
+  font-weight: normal;
+  color: var(--antd-color-text-tertiary);
+}
+
+.protocol-params-query {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 24px;
+
+  button + button {
+    margin-left: 23px;
+  }
+}
+</style>

+ 70 - 0
src/views/setup-protocol/RecognitionResult.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import { onMounted } from 'vue';
+import { message } from 'ant-design-vue';
+
+import { useRequest } from '@/hooks/request';
+import { t } from '@/i18n';
+import { downloadUserProtocol, getProtocolBaseInfo, updateProtocolBaseInfo } from '@/api';
+import { downloadBlob } from '@/utils';
+
+import ProtocolContent from './ProtocolContent.vue';
+
+import type { SetupProtocolForm, UseGuideStepItemExpose, UseGuideStepItemProps } from '@/types';
+
+const props = defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+
+const { handleRequest } = useRequest();
+let protocolName = '';
+let protocolType = '';
+let fileName = '';
+
+onMounted(() => {
+  handleRequest(async () => {
+    const data = await getProtocolBaseInfo(37);
+    Object.assign(props.form.protocolInfo, data);
+    protocolName = data.protocolName;
+    protocolType = data.protocolType;
+    fileName = `${protocolType} - ${protocolName}.xlsx`;
+  });
+});
+
+const exportData = () => {
+  handleRequest(async () => {
+    const file = await downloadUserProtocol(protocolType, protocolName);
+    downloadBlob(file, fileName);
+    message.success(t('setupProtocol.downloadProtocolSuccessful', { name: fileName }));
+  });
+};
+
+const finish = async () => {
+  await updateProtocolBaseInfo(props.form.protocolInfo);
+};
+
+defineExpose<UseGuideStepItemExpose>({
+  exportData,
+  finish,
+});
+</script>
+
+<template>
+  <div>
+    <div class="result-header">
+      <div class="use-guide-title">{{ $t('setupProtocol.recognitionResult') }}</div>
+      <AButton>{{ $t('setupProtocol.reRecognize') }}</AButton>
+    </div>
+    <ProtocolContent class="result-protocol" :info="form.protocolInfo" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.result-header {
+  display: flex;
+  justify-content: space-between;
+  width: calc(62% + 17px);
+  margin-bottom: 36px;
+}
+
+.result-protocol {
+  margin: 0 17px;
+}
+</style>

+ 69 - 2
src/views/setup-protocol/SelectConfigMethod.vue

@@ -1,9 +1,76 @@
 <script setup lang="ts">
 <script setup lang="ts">
+import { onMounted, shallowRef } from 'vue';
+
+import { t } from '@/i18n';
 import { ProtocolConfigMethod } from '@/constants';
 import { ProtocolConfigMethod } from '@/constants';
 
 
-import type { SetupProtocolForm, UseGuideStepItemProps } from '@/types';
+import FinishProtocol from './FinishProtocol.vue';
+import RecognitionResult from './RecognitionResult.vue';
+import SelectDeviceType from './SelectDeviceType.vue';
+import UploadProtocolFile from './UploadProtocolFile.vue';
+import WaitingRecognition from './WaitingRecognition.vue';
+
+import type { SetupProtocolForm, UseGuideStepItemExpose, UseGuideStepItemProps } from '@/types';
+
+const props = defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+
+const currentStep = props.steps[props.stepIndex];
+
+onMounted(() => {
+  currentStep.title = t('setupProtocol.selectConfigMethod');
+  props.steps.length = props.stepIndex + 1; // 回退到该步骤时,移除其后续步骤
+});
+
+const finish = () => {
+  const isImportFromTemplate = props.form.configMethod === ProtocolConfigMethod.ImportFromTemplate;
+  currentStep.title = isImportFromTemplate ? t('setupProtocol.importFromTemplate') : t('setupProtocol.manuallyCreate');
+
+  if (isImportFromTemplate) {
+    props.steps.push(
+      {
+        title: t('setupProtocol.uploadFile'),
+        component: shallowRef(UploadProtocolFile),
+      },
+      {
+        title: t('setupProtocol.waitingForRecognition'),
+        component: shallowRef(WaitingRecognition),
+      },
+      {
+        title: t('setupProtocol.confirmResult'),
+        component: shallowRef(RecognitionResult),
+        contentOffset: 36,
+        formLayout: 'vertical',
+        exportButtonShow: true,
+        nextStepButtonText: t('common.finishSetup'),
+      },
+      {
+        title: t('common.finishSetup'),
+        component: shallowRef(FinishProtocol),
+        isLastStep: true,
+      },
+    );
+  } else {
+    props.steps.push(
+      {
+        title: t('setupProtocol.selectDeviceType'),
+        component: shallowRef(SelectDeviceType),
+      },
+      {
+        title: t('setupProtocol.createProtocol'),
+        component: shallowRef(RecognitionResult),
+        contentOffset: 36,
+      },
+      {
+        title: t('common.finishSetup'),
+        component: shallowRef(FinishProtocol),
+      },
+    );
+  }
+};
 
 
-defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+defineExpose<UseGuideStepItemExpose>({
+  finish,
+});
 </script>
 </script>
 
 
 <template>
 <template>

+ 35 - 0
src/views/setup-protocol/SelectDeviceType.vue

@@ -0,0 +1,35 @@
+<script setup lang="ts">
+import { onMounted } from 'vue';
+
+import { t } from '@/i18n';
+
+import type { SetupProtocolForm, UseGuideStepItemProps } from '@/types';
+
+const props = defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+
+onMounted(() => {
+  const { steps, stepIndex } = props;
+  const currStep = steps[stepIndex];
+  currStep.title = t('setupProtocol.selectProtocolType');
+});
+</script>
+
+<template>
+  <div>
+    <div class="use-guide-title">{{ $t('setupProtocol.selectDeviceType') }}</div>
+    <div class="use-guide-description">{{ $t('setupProtocol.selectDeviceTypeTip') }}</div>
+    <AFormItem :label="$t('setupProtocol.deviceType')" name="deviceType">
+      <ASelect v-model:value="form.deviceType" class="device-select" :placeholder="$t('common.plzSelect')">
+        <!-- <ASelectOption v-for="item in protocolTypes" :key="item.value" :value="item.label">
+          {{ item.label }}
+        </ASelectOption> -->
+      </ASelect>
+    </AFormItem>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.device-select {
+  width: 224px;
+}
+</style>

+ 20 - 17
src/views/setup-protocol/SelectProtocolType.vue

@@ -1,31 +1,34 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { onMounted, ref } from 'vue';
+import { onMounted } from 'vue';
 
 
+import { useDictData } from '@/hooks/dict-data';
 import { useRequest } from '@/hooks/request';
 import { useRequest } from '@/hooks/request';
-import { getDictTypeData } from '@/api';
+import { t } from '@/i18n';
 import { DictCode } from '@/constants';
 import { DictCode } from '@/constants';
 
 
-import type { OptionItem, SetupProtocolForm, UseGuideStepItemProps } from '@/types';
+import type { SetupProtocolForm, UseGuideStepItemExpose, UseGuideStepItemProps } from '@/types';
 
 
-defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+const props = defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
 
 
 const { handleRequest } = useRequest();
 const { handleRequest } = useRequest();
-const protocolTypes = ref<OptionItem<number>[]>([]);
+const { dictData: protocolTypes, getDictData: getProtocolTypes } = useDictData(DictCode.AllProtocolType);
+const currentStep = props.steps[props.stepIndex];
 
 
 onMounted(() => {
 onMounted(() => {
-  handleRequest(async () => {
-    const data = await getDictTypeData({
-      dictCode: DictCode.AllProtocolType,
-    });
+  currentStep.title = t('setupProtocol.selectProtocolType');
 
 
-    if (data.length) {
-      protocolTypes.value = data[0].dictTypeDataList.map((item) => ({
-        value: item.dictValueId,
-        label: item.dictValue,
-      }));
-    }
+  handleRequest(async () => {
+    await getProtocolTypes();
   });
   });
 });
 });
+
+const finish = () => {
+  currentStep.title = t('setupProtocol.protocolDisplayName', { name: props.form.protocolType });
+};
+
+defineExpose<UseGuideStepItemExpose>({
+  finish,
+});
 </script>
 </script>
 
 
 <template>
 <template>
@@ -34,8 +37,8 @@ onMounted(() => {
     <div class="use-guide-description">{{ $t('setupProtocol.selectProtocolTypeTip') }}</div>
     <div class="use-guide-description">{{ $t('setupProtocol.selectProtocolTypeTip') }}</div>
     <AFormItem :label="$t('setupProtocol.protocolType')" name="protocolType">
     <AFormItem :label="$t('setupProtocol.protocolType')" name="protocolType">
       <ASelect v-model:value="form.protocolType" class="protocol-select" :placeholder="$t('common.plzSelect')">
       <ASelect v-model:value="form.protocolType" class="protocol-select" :placeholder="$t('common.plzSelect')">
-        <ASelectOption v-for="item in protocolTypes" :key="item.value" :value="item.value">
-          {{ item.label }}
+        <ASelectOption v-for="item in protocolTypes" :key="item.dictValueId" :value="item.dictValue">
+          {{ item.dictValue }}
         </ASelectOption>
         </ASelectOption>
       </ASelect>
       </ASelect>
     </AFormItem>
     </AFormItem>

+ 65 - 17
src/views/setup-protocol/SetupProtocol.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { computed, reactive } from 'vue';
+import { computed, reactive, ref, shallowRef } from 'vue';
 
 
 import UseGuidance from '@/layout/UseGuidance.vue';
 import UseGuidance from '@/layout/UseGuidance.vue';
 import { t } from '@/i18n';
 import { t } from '@/i18n';
@@ -7,33 +7,49 @@ import { ProtocolConfigMethod } from '@/constants';
 
 
 import SelectConfigMethod from './SelectConfigMethod.vue';
 import SelectConfigMethod from './SelectConfigMethod.vue';
 import SelectProtocolType from './SelectProtocolType.vue';
 import SelectProtocolType from './SelectProtocolType.vue';
-import UploadProtocolFile from './UploadProtocolFile.vue';
-import WaitingRecognition from './WaitingRecognition.vue';
 
 
-import type { FormRules, SetupProtocolForm, UseGuideStepItem } from '@/types';
+import type { UploadProps } from 'ant-design-vue';
+import type { FormRules, RuleValidator, SetupProtocolForm, UseGuideStepItem } from '@/types';
 
 
-const steps: UseGuideStepItem[] = [
+const steps = ref<UseGuideStepItem[]>([
   {
   {
     title: t('setupProtocol.selectProtocolType'),
     title: t('setupProtocol.selectProtocolType'),
-    component: SelectProtocolType,
+    component: shallowRef(SelectProtocolType),
   },
   },
   {
   {
     title: t('setupProtocol.selectConfigMethod'),
     title: t('setupProtocol.selectConfigMethod'),
-    component: SelectConfigMethod,
+    component: shallowRef(SelectConfigMethod),
   },
   },
-  {
-    title: t('setupProtocol.uploadFile'),
-    component: UploadProtocolFile,
-  },
-  {
-    title: t('setupProtocol.waitingForRecognition'),
-    component: WaitingRecognition,
-  },
-];
+]);
 
 
 const setupProtocolForm = reactive<SetupProtocolForm>({
 const setupProtocolForm = reactive<SetupProtocolForm>({
-  protocolType: '',
+  protocolType: 'ModbusRTU',
+  protocolInfo: {
+    id: 37,
+    protocolName: '',
+    protocolType: '',
+    deviceType: '',
+    deviceTypeId: null,
+    dataBit: 5,
+    parityBit: 'N',
+    stopBit: '1',
+    baudRate: 0,
+    dataSendInterval: 0,
+    highFreqSendInterval: 0,
+    readTimeout: 0,
+    nextDataReadDelay: 0,
+    nextRoundDataReadDelay: 0,
+    readContinuousAddr: '',
+    readContinuousAddrCode: 0,
+    readContinuousAddrLength: 0,
+    byteOrder: '',
+    byteOrderCode: '',
+    addrOrder: '',
+    addrOrderCode: '',
+  },
+  protocolFile: [],
   configMethod: ProtocolConfigMethod.ImportFromTemplate,
   configMethod: ProtocolConfigMethod.ImportFromTemplate,
+  deviceType: '',
 });
 });
 
 
 const rules = computed<FormRules<SetupProtocolForm>>(() => {
 const rules = computed<FormRules<SetupProtocolForm>>(() => {
@@ -45,8 +61,40 @@ const rules = computed<FormRules<SetupProtocolForm>>(() => {
         trigger: 'change',
         trigger: 'change',
       },
       },
     ],
     ],
+    protocolFile: [
+      {
+        validator: validateProtocolFile,
+        trigger: 'change',
+      },
+    ],
   };
   };
 });
 });
+
+/**
+ * 文件大小最大值(MB)
+ */
+const MAX_FILE_SIZE = 10;
+
+const validateProtocolFile: RuleValidator<UploadProps['fileList']> = (_rule, value) => {
+  if (!value?.length) {
+    return Promise.reject(t('setupProtocol.plzSelectProtocolFile'));
+  }
+
+  const file = value[0];
+  const excelFileRegExp = /\.(xls|xlsx)$/i;
+
+  if (!excelFileRegExp.test(file.name)) {
+    return Promise.reject(t('setupProtocol.wrongProtocolFileType'));
+  }
+
+  const fileSize = (file.size || 0) / 1024 / 1024;
+
+  if (fileSize > MAX_FILE_SIZE) {
+    return Promise.reject(t('setupProtocol.wrongProtocolFileFize', { size: MAX_FILE_SIZE }));
+  }
+
+  return Promise.resolve();
+};
 </script>
 </script>
 
 
 <template>
 <template>

+ 67 - 11
src/views/setup-protocol/UploadProtocolFile.vue

@@ -1,19 +1,77 @@
+<script setup lang="ts">
+import { onMounted } from 'vue';
+import { message } from 'ant-design-vue';
+
+import { useRequest } from '@/hooks/request';
+import { t } from '@/i18n';
+import { downloadProtocolTemplate, getDictTypeData } from '@/api';
+import { downloadBlob } from '@/utils';
+import { DictCode, ProtocolType } from '@/constants';
+
+import type { SetupProtocolForm, UseGuideStepItemProps } from '@/types';
+
+const props = defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+
+const { handleRequest } = useRequest();
+let fileName: string | undefined;
+
+onMounted(() => {
+  handleRequest(async () => {
+    const data = await getDictTypeData({
+      dictCode: DictCode.ProtocolTemplateFileName,
+    });
+
+    if (data.length) {
+      const { protocolType } = props.form;
+      const fileNameList = data[0].dictTypeDataList.map((item) => item.dictValue);
+
+      if (protocolType.includes(ProtocolType.ModbusRTU)) {
+        fileName = fileNameList.find((item) => item.includes(ProtocolType.ModbusRTU));
+      } else if (protocolType.includes(ProtocolType.ModbusTCP)) {
+        fileName = fileNameList.find((item) => item.includes(ProtocolType.ModbusTCP));
+      } else if (protocolType.includes(ProtocolType.S7)) {
+        fileName = fileNameList.find((item) => item.includes(ProtocolType.S7));
+      }
+    }
+  });
+});
+
+const downloadTemplate = () => {
+  handleRequest(async () => {
+    if (!fileName) {
+      throw new Error(t('setupProtocol.canNotDownloadByEmptyFileName'));
+    }
+
+    const file = await downloadProtocolTemplate(fileName);
+    downloadBlob(file, fileName);
+    message.success(t('setupProtocol.downloadTemplateSuccessful', { name: fileName }));
+  });
+};
+</script>
+
 <template>
 <template>
   <div>
   <div>
     <div class="use-guide-title upload-title">{{ $t('setupProtocol.uploadFile') }}</div>
     <div class="use-guide-title upload-title">{{ $t('setupProtocol.uploadFile') }}</div>
     <div class="upload-step-text">1. {{ $t('setupProtocol.clickToDownloadTemplate') }}</div>
     <div class="upload-step-text">1. {{ $t('setupProtocol.clickToDownloadTemplate') }}</div>
-    <AButton class="upload-template-button" type="primary">
+    <AButton class="upload-template-button" type="primary" @click="downloadTemplate">
       <img src="@/assets/img/file-white.png" />
       <img src="@/assets/img/file-white.png" />
       {{ $t('setupProtocol.downloadTemplate') }}
       {{ $t('setupProtocol.downloadTemplate') }}
     </AButton>
     </AButton>
     <div class="upload-step-text">2. {{ $t('setupProtocol.clickToUpload') }}</div>
     <div class="upload-step-text">2. {{ $t('setupProtocol.clickToUpload') }}</div>
-    <AUploadDragger class="upload-drag-area">
-      <p class="ant-upload-drag-icon">
-        <un-i-mdi-folder-open class="upload-folder-icon" />
-      </p>
-      <p class="ant-upload-text">{{ $t('setupProtocol.uploadFileTip') }}</p>
-      <p class="ant-upload-hint">{{ $t('setupProtocol.uploadFileFormat') }}</p>
-    </AUploadDragger>
+    <AFormItem class="upload-drag-area" name="protocolFile">
+      <AUploadDragger
+        v-model:file-list="form.protocolFile"
+        :max-count="1"
+        :multiple="false"
+        :before-upload="() => false"
+      >
+        <p class="ant-upload-drag-icon">
+          <un-i-mdi-folder-open class="upload-folder-icon" />
+        </p>
+        <p class="ant-upload-text">{{ $t('setupProtocol.uploadFileTip') }}</p>
+        <p class="ant-upload-hint">{{ $t('setupProtocol.uploadFileFormat') }}</p>
+      </AUploadDragger>
+    </AFormItem>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -40,9 +98,7 @@
 }
 }
 
 
 .upload-drag-area {
 .upload-drag-area {
-  :deep(.ant-upload-drag) {
-    width: 617px;
-  }
+  width: 617px;
 }
 }
 
 
 .upload-folder-icon {
 .upload-folder-icon {

+ 30 - 2
src/views/setup-protocol/WaitingRecognition.vue

@@ -1,8 +1,36 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+
+import { useRequest } from '@/hooks/request';
+import { uploadUserProtocol } from '@/api';
+import { waitTime } from '@/utils';
+
+import type { SetupProtocolForm, UseGuideStepItemProps } from '@/types';
+
+const props = defineProps<UseGuideStepItemProps<SetupProtocolForm>>();
+
+const { handleRequest } = useRequest();
+const remainingSeconds = ref(90);
+const currentPercent = ref(0);
+
+onMounted(() => {
+  handleRequest(async () => {
+    const { protocolType, protocolFile } = props.form;
+    const file = protocolFile?.[0].originFileObj;
+    const data = await uploadUserProtocol(protocolType, file as File);
+    Object.assign(props.form.protocolInfo, data);
+
+    await waitTime(1000);
+    currentPercent.value = 100;
+  });
+});
+</script>
+
 <template>
 <template>
   <div>
   <div>
     <div class="use-guide-title">{{ $t('setupProtocol.recognizing') }}</div>
     <div class="use-guide-title">{{ $t('setupProtocol.recognizing') }}</div>
-    <div class="recognize-tip">{{ $t('setupProtocol.recognizeTip', { time: 90 }) }}</div>
-    <AProgress class="recognize-progress" :percent="40" />
+    <div class="recognize-tip">{{ $t('setupProtocol.recognizeTip', { time: remainingSeconds }) }}</div>
+    <AProgress class="recognize-progress" :percent="currentPercent" />
   </div>
   </div>
 </template>
 </template>