songshankun
2023-10-30 9bde1998a8a0bc6c1ab314f8cf27c10aef016689
feat: 通道展示组件添加查看更多逻辑;任务选中展示;添加加工信息组件
2个文件已添加
1 文件已重命名
9个文件已修改
610 ■■■■ 已修改文件
src/api/index.ts 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/plc.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/task.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DashboardLayout.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/tasks.ts 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/ChannelCollapse.vue 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/CraftInfo.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/ProcessingInfo.vue 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/TaskTabs.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/index.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.ts
@@ -1,5 +1,6 @@
import { request } from '@/common/utils'
import type { TasksResponse } from './task'
import type { TasksGroupByChannel } from './task'
import type { PLCResponse } from './plc'
export interface BaseResponse<T = any> {
  code: number
@@ -7,16 +8,38 @@
  msg: string
}
export interface TaskListParams {
  /** 1未完成2今天未完成3已完成 */
  type: 1 | 2 | 3
  /** 通道号 不传查所有通道的 */
  channel?: number
  /** 从第几个开始查,从0开始 */
  offset: number
  /** 查多少条 */
  limit: number
}
/**
 * 获取任务列表
 * @param taskMode 1: 待生产的任务 2: 看板各通道当前展示的任务
 * @param params
 */
export function getTaskList(taskMode: 1 | 2) {
  return request<BaseResponse<TasksResponse>>({
    url: '/v1/task/get',
export function getTaskList(params: TaskListParams) {
  return request<BaseResponse<TasksGroupByChannel>>({
    url: '/v1/task/list',
    method: 'get',
    params: {
      taskMode
    }
    params
  })
}
export interface ProductProgressParams {
  channel: number
  procedureId: number
}
export function getProductProgress(params: ProductProgressParams) {
  return request<BaseResponse<PLCResponse>>({
    url: '/v1/plc/productProgress',
    method: 'post',
    data: params
  })
}
src/api/plc.ts
New file
@@ -0,0 +1,6 @@
export interface PLCResponse {
  finishNumber: number
  totalNumber: number
  /** 1断开 2生产中 3待机*/
  plcStatus: 1 | 2 | 3
}
src/api/task.ts
@@ -82,3 +82,7 @@
  Prompt: Prompt
  ChannelAmount: number
}
export interface TasksGroupByChannel {
  [channel: number]: TasksResponse
}
src/components.d.ts
@@ -7,13 +7,17 @@
declare module 'vue' {
  export interface GlobalComponents {
    DashboardLayout: typeof import('./components/DashboardLayout.vue')['default']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCollapse: typeof import('element-plus/es')['ElCollapse']
    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
    DashboardLayout: (typeof import('./components/DashboardLayout.vue'))['default']
    ElCollapse: (typeof import('element-plus/es'))['ElCollapse']
    ElCollapseItem: (typeof import('element-plus/es'))['ElCollapseItem']
    ElIcon: (typeof import('element-plus/es'))['ElIcon']
    ElProgress: (typeof import('element-plus/es'))['ElProgress']
    ElScrollbar: (typeof import('element-plus/es'))['ElScrollbar']
    ElStep: (typeof import('element-plus/es'))['ElStep']
    ElSteps: (typeof import('element-plus/es'))['ElSteps']
    ElTabPane: (typeof import('element-plus/es'))['ElTabPane']
    ElTabs: (typeof import('element-plus/es'))['ElTabs']
    RouterLink: (typeof import('vue-router'))['RouterLink']
    RouterView: (typeof import('vue-router'))['RouterView']
  }
}
src/components/DashboardLayout.vue
@@ -16,18 +16,18 @@
      <div class="header-block padding-4">
        <slot name="middleBlock1"></slot>
      </div>
      <div class="base-block padding-4">
      <div class="top-block padding-4">
        <div class="card">
          <slot name="middleBlock2"></slot>
        </div>
      </div>
      <div class="block-container">
        <div class="base-block padding-4">
      <div class="bottom-block">
        <div class="bottom-block-item padding-4">
          <div class="card">
            <slot name="middleBlock3"></slot>
          </div>
        </div>
        <div class="base-block padding-4">
        <div class="bottom-block-item padding-4">
          <div class="card">
            <slot name="middleBlock4"></slot>
          </div>
@@ -67,6 +67,10 @@
$baseBlockHeight: calc((100vh - 2 * $layoutPadding - $headerBlockHeight) / 2);
// 双倍高布局块高度
$doubleBlockHeight: calc($baseBlockHeight * 2);
// 上边固定高
$topBlocHeight: 400px;
// 下边高度
$bottomBlockHeight: calc(100vh - 2 * $layoutPadding - $headerBlockHeight - $topBlocHeight);
.dashboard-layout {
  display: flex;
  align-items: center;
@@ -100,14 +104,24 @@
.padding-4 {
  padding: 6px;
}
.block-container {
.base-block {
  height: $baseBlockHeight;
  flex: 1;
}
.top-block {
  height: $topBlocHeight;
  flex: 0;
}
.bottom-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: $bottomBlockHeight;
}
.base-block {
  height: $baseBlockHeight;
  flex-grow: 1;
.bottom-block-item {
  height: $bottomBlockHeight;
  flex: 1;
}
.header-block {
  height: $headerBlockHeight;
src/stores/tasks.ts
@@ -1,12 +1,106 @@
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import type { Task } from '@/api/task'
import type { Task, TasksGroupByChannel, TasksResponse } from '@/api/task'
import type { TaskListParams } from '@/api'
import { getTaskList } from '@/api'
export interface ChannelMoreBtnStatus {
  /** true 任务未加载完  false 所有任务已经加载完成*/
  [channel: number]: boolean
}
export const useTasksStore = defineStore('tasks', () => {
  const channels = ref<TasksGroupByChannel>({})
  const currentType = ref<1 | 2 | 3>(1)
  /**
   * 获取任务数据
   * @param type 1未完成2今天未完成3已完成
   */
  function getChannels(type: 1 | 2 | 3) {
    currentType.value = type
    const params: TaskListParams = {
      type,
      offset: 0,
      limit: 3
    }
    getTaskList(params)
      .then((res) => {
        channels.value = res.data
      })
      .catch((err) => {
        console.error(err)
        channels.value = []
      })
  }
  function moreChannelTasksBtn(channelNumber: number) {
    const taskLength = channels.value[channelNumber].Tasks?.length ?? 0
    const params: TaskListParams = {
      type: currentType.value,
      channel: channelNumber,
      offset: taskLength,
      limit: 10
    }
    getTaskList(params)
      .then((res) => {
        const existTasks = channels.value![channelNumber].Tasks ?? []
        channels.value[channelNumber] = res.data[channelNumber] ?? {}
        channels.value[channelNumber].Tasks = channels.value[channelNumber].Tasks ?? []
        channels.value[channelNumber].Tasks = [...existTasks, ...channels.value[channelNumber].Tasks]
      })
      .catch((err) => {
        console.error(err)
      })
  }
  function foldChannelTasksBtn(channelNumber: number) {
    const tasks = channels.value[channelNumber].Tasks ?? []
    channels.value[channelNumber].Tasks = tasks.slice(0, 3)
  }
  const moreBtnStatus = computed(() => {
    return Object.entries(channels.value).reduce((pre, currentValue) => {
      const channelNumber = +currentValue[0]
      const channelData = currentValue[1] as TasksResponse
      pre[channelNumber] = channelData.TaskCount > (channelData.Tasks?.length ?? 0)
      return pre
    }, {} as ChannelMoreBtnStatus)
  })
  /** 当前高亮的任务 */
  const activeTask = ref<Task>()
  function setActiveTask(task: Task) {
    activeTask.value = task
  }
  return { activeTask, setActiveTask }
  const requestParamsMap = ref<{
    [channel: number]: TaskListParams
  }>({})
  function getParamsByChannel(channel: number) {
    return (
      requestParamsMap.value[channel] ?? {
        type: 1,
        offset: 0,
        limit: 3
      }
    )
  }
  function setParamsByChannel(channel: number, params: TaskListParams) {
    requestParamsMap.value[channel] = params
  }
  return {
    channels,
    getChannels,
    moreBtnStatus,
    activeTask,
    setActiveTask,
    requestParamsMap,
    getParamsByChannel,
    setParamsByChannel,
    moreChannelTasksBtn,
    foldChannelTasksBtn
  }
})
src/views/dashboard/components/ChannelCollapse.vue
@@ -2,35 +2,51 @@
  <div class="channel-collapse">
    <el-collapse v-model="activeChannel">
      <el-collapse-item
        v-for="(tasks, channelNumber) in channels"
        v-for="(channel, channelNumber) in channels"
        :key="channelNumber"
        :title="CHANNEL_NAME_MAP[channelNumber] + ' 通道'"
        :name="String(channelNumber)"
      >
        <TaskInfo
          v-for="task in tasks"
          v-for="task in channel.Tasks"
          :key="task.Procedure.ID"
          :active="task.Procedure.ID === tasksStore.activeTask?.Procedure.ID"
          :task="task"
          style="margin-bottom: 16px"
          @click="tasksStore.setActiveTask(task)"
        ></TaskInfo>
        <div
          v-show="channel.Tasks?.length && tasksStore.moreBtnStatus?.[channelNumber]"
          class="btn more"
          @click="tasksStore.moreChannelTasksBtn(channelNumber)"
        >
          查看更多
          <el-icon style="margin-left: 6px"><ArrowDownBold /></el-icon>
        </div>
        <div
          v-show="channel.Tasks?.length && !tasksStore.moreBtnStatus?.[channelNumber]"
          class="btn fold"
          @click="tasksStore.foldChannelTasksBtn(channelNumber)"
        >
          收起
          <el-icon style="margin-left: 6px"><ArrowUpBold /></el-icon>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
import type { Task } from '@/api/task'
import type { TasksGroupByChannel } from '@/api/task'
import TaskInfo from './TaskInfo.vue'
import { CHANNEL_NAME_MAP } from '@/common/constants'
import { useTasksStore } from '@/stores/tasks'
export interface Channel {
  [channelNumber: number]: Task[]
}
import { ArrowDownBold, ArrowUpBold } from '@element-plus/icons-vue'
export interface ChannelCollapseProps {
  channels: Channel
  channels: TasksGroupByChannel
}
const props = defineProps<ChannelCollapseProps>()
@@ -39,7 +55,8 @@
const tasksStore = useTasksStore()
watchEffect(() => {
  const channelNumbers = Object.keys(props.channels).sort((a, b) => +a - +b)
  // 通道数据变化后
  const channelNumbers = Object.keys(props?.channels ?? {}).sort((a, b) => +a - +b)
  activeChannel.value = [...channelNumbers]
})
</script>
@@ -75,4 +92,18 @@
  font-size: 16px;
  font-weight: 600;
}
.btn {
  width: 70%;
  height: 50px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;
  background: linear-gradient(to right, rgb(29, 96, 212) 0%, rgb(47, 122, 251), rgb(29, 96, 212) 100%);
}
</style>
src/views/dashboard/components/CraftInfo.vue
File was renamed from src/views/dashboard/components/ProcessInfo.vue
@@ -1,5 +1,5 @@
<template>
  <div class="process-info">
  <div class="craft-info">
    <div class="item-l-bng">
      <img src="~@/assets/images/process-model.png" />
    </div>
@@ -9,7 +9,7 @@
      <div class="item-r-b">{{ process.name }}</div>
    </div>
    <div class="tip-r">
      <img src="~@/assets/images/process-tip.png" />
      <img src="~@/assets/images/process-tip.png" alt="" />
    </div>
    <div class="tip-current">当前使用</div>
    <div class="btn">
@@ -18,7 +18,9 @@
  </div>
</template>
<script setup lang="ts">
import { computed, toRefs } from 'vue'
// 工艺信息
import { toRefs } from 'vue'
export interface ProcessInfoProps {
  process: process
}
@@ -33,7 +35,7 @@
.font_weight {
  font-weight: 600;
}
.process-info {
.craft-info {
  width: calc(50% - 35px);
  height: 110px;
  padding: 23px 10px 10px;
src/views/dashboard/components/ProcessingInfo.vue
New file
@@ -0,0 +1,220 @@
<template>
  <div class="processing-info">
    <div class="step">
      <el-steps
        v-if="task?.AllProcedures"
        :active="task.CurrentProcedureIndex ?? 0"
        finish-status="success"
        class="steps"
      >
        <el-step v-for="(item, index) in task.AllProcedures" :key="index" icon="" :title="item"></el-step>
      </el-steps>
    </div>
    <div class="details">
      <div class="row">
        <div class="col">工单编号: {{ task?.Order?.workOrderId || '' }}</div>
        <div class="col">订单编号: {{ task?.Order?.orderId || '' }}</div>
      </div>
      <div class="row">
        <div class="col">产品名称: {{ task?.Order?.productName || '--' }}</div>
        <div class="col">数量: {{ task?.Order?.amount || 0 }}{{ task?.Order?.unit }}</div>
      </div>
      <div class="row">
        <div class="col">交货日期: {{ task?.Order?.deliverDate || '--' }}</div>
        <div class="col">工时: {{ task?.Procedure?.procedure?.workHours || '--' }}</div>
      </div>
      <div class="row">
        <div class="col">
          起止时间: {{ formatDate(task?.Procedure?.startTime) }}
          ~
          {{ formatDate(task?.Procedure?.endTime) }}
        </div>
        <div class="col">通道: {{ isNumber(task?.Channel) ? CHANNEL_NAME_MAP[task?.Channel] : '--' }}</div>
      </div>
      <div class="row">
        <div class="col">客户名称: {{ task?.Order?.customer || '' }}</div>
        <div class="col">参数要求: {{ task?.Order?.parameter || '' }}</div>
      </div>
    </div>
    <div class="process">
      <div>完成进度:</div>
      <div class="process-bar">
        <el-progress
          define-back-color="#132f6e"
          color="#00cc66"
          text-color="#fff"
          :text-inside="true"
          :stroke-width="30"
          :percentage="processingPercent"
        ></el-progress>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
// 加工信息组件
import type { Task } from '@/api/task'
import { computed, onUnmounted, toRefs, watch } from 'vue'
import { useDateFormat } from '@vueuse/core'
import { useRequest } from 'vue-hooks-plus'
import { getProductProgress } from '@/api'
import type { ProductProgressParams } from '@/api'
import { isNumber } from 'lodash-es'
import { CHANNEL_NAME_MAP } from '@/common/constants'
const props = defineProps<{
  task?: Task
}>()
const { task } = toRefs(props)
export interface Statistics {
  totalNumber: number
  finishNumber: number
}
/**
 * 计算生产进度
 * @param statistics
 * @return 进度,0~100
 */
function calculateProgress(statistics: Statistics): number {
  if (!statistics) {
    return 0
  }
  if (statistics.finishNumber === 0) {
    return 0
  }
  if (statistics.finishNumber === statistics.totalNumber) {
    return 100
  }
  const result = Math.floor((statistics.finishNumber / statistics.totalNumber) * 100)
  return result > 100 ? 100 : result
}
/**
 * 计算完成进度, 任务状态未生产固定为 0% 已完成固定为 100% 生产中则从plc获取
 */
const processingPercent = computed(() => {
  if (task?.value?.Procedure?.Status === 1) {
    return 0
  }
  if (task?.value?.Procedure?.Status === 3) {
    return 100
  }
  if (task?.value?.Procedure?.Status === 2) {
    return calculateProgress(plcResponse?.value?.data as Statistics)
  }
  return 0
})
/**
 * 如果任务状态是进行中, 则轮询 plc 取进度
 */
const {
  data: plcResponse,
  run: startPLCPolling,
  cancel: cancelPLCPolling
} = useRequest(
  () =>
    getProductProgress({
      channel: task?.value?.Channel,
      procedureId: task?.value?.Procedure.ID
    } as ProductProgressParams),
  {
    manual: true,
    pollingInterval: 6000,
    pollingWhenHidden: false
  }
)
/**
 * 任务状态是生产中则轮询plc取目标数和完成数计算完成进度
 */
watch(
  () => task?.value,
  () => {
    cancelPLCPolling()
    if (task?.value?.Procedure?.Status === 2) {
      startPLCPolling()
    }
  }
)
onUnmounted(() => {
  cancelPLCPolling()
})
/**
 * 格式化时间戳
 * @param timestamp 后端返的10位时间戳
 */
function formatDate(timestamp?: number) {
  if (!timestamp) {
    return '--'
  }
  const time = useDateFormat(timestamp * 1000, 'YYYY-MM-DD', { locales: 'zh-cn' })
  return time.value
}
</script>
<style scoped lang="scss">
$text-color: #d7d7ca;
.step {
  width: 100%;
  height: 66px;
  overflow-x: auto;
  margin-top: -5px;
  padding: 0 20px;
  .steps {
    height: 100%;
    .el-step__icon {
      width: 16px;
      height: 16px;
    }
    .el-step__title {
      line-height: 25px;
      font-size: 14px;
    }
    .el-step__title.is-process {
      color: #a8abb2;
    }
  }
}
.details {
  font-size: 18px;
  padding: 10px 20px;
  color: $text-color;
  .row {
    width: 100%;
    padding: 2px 0;
    display: flex;
    align-items: center;
  }
  .col {
    width: 50%;
    flex: 1;
  }
}
.process {
  font-size: 18px;
  padding: 10px 20px;
  color: $text-color;
  display: flex;
}
.process-bar {
  flex: 1;
  margin-left: 20px;
}
:deep(.el-progress-bar__outer) {
  border-radius: 8px;
}
:deep(.el-progress-bar__inner) {
  border-radius: 8px;
}
</style>
src/views/dashboard/components/TaskTabs.vue
@@ -1,32 +1,39 @@
<template>
  <div class="task-tabs">
    <div
      v-for="tabName in list"
      :key="tabName"
      v-for="tab in list"
      :key="tab.value"
      class="task-tab-item triangle-tip"
      :class="{ active: props.modelValue === tabName }"
      @click="selectTab(tabName)"
      :class="{ active: props.modelValue === tab.value }"
      @click="selectTab(tab)"
    >
      {{ tabName }}
      {{ tab.label }}
    </div>
  </div>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
export interface LabelValue {
  label: string
  value: any
}
const props = defineProps<{
  /** tab 列表*/
  list: string[]
  list: LabelValue[]
  /** 当前选中的 tab*/
  modelValue?: string
  modelValue?: any
}>()
const emit = defineEmits<{
  'update:modelValue': [tabName: string]
  change: [tab: LabelValue]
}>()
const data = useVModel(props, 'modelValue', emit)
function selectTab(tabName: string) {
  data.value = tabName
function selectTab(tab: LabelValue) {
  data.value = tab.value
  emit('change', tab)
}
</script>
<style scoped lang="scss">
src/views/dashboard/index.vue
@@ -1,19 +1,20 @@
<template>
  <DashboardLayout>
    <template #leftBlock1>
      <TaskTabs v-model="activeTaskTab" style="margin-top: 20px" :list="taskTabsTitle"></TaskTabs>
      <TaskTabs v-model="activeTaskTab" style="margin-top: 20px" :list="taskTabsList" @change="changeTab"></TaskTabs>
    </template>
    <template #leftBlock2>
      <ChannelCollapse :channels="channels"></ChannelCollapse>
    </template>
    <template #middleBlock1>标题</template>
    <template #middleBlock2
      >主看板
      <ProcessInfo :process="process"></ProcessInfo>
      <div v-if="activeTask">
        任务详情
        {{ activeTask.Order.workOrderId }}
      </div>
    <template #middleBlock2>
      <el-tabs v-model="activeMainTabName" class="main-info-tabs">
        <el-tab-pane label="加工信息" name="加工信息">
          <ProcessingInfo style="margin-top: 6px" :task="activeTask"></ProcessingInfo>
        </el-tab-pane>
        <el-tab-pane label="工艺信息" name="工艺信息">Config</el-tab-pane>
        <el-tab-pane label="物料清单" name="物料清单">Role</el-tab-pane>
      </el-tabs>
    </template>
    <template #middleBlock3> 任务详情 </template>
    <template #middleBlock4
@@ -30,63 +31,55 @@
  </DashboardLayout>
</template>
<script setup lang="ts">
import { getTaskList } from '@/api'
import { computed, ref, watchEffect } from 'vue'
import { computed, ref } from 'vue'
import ChannelCollapse from '@/views/dashboard/components/ChannelCollapse.vue'
import type { Task, Worker } from '@/api/task'
import { chain } from 'lodash-es'
import ProcessInfo from '@/views/dashboard/components/ProcessInfo.vue'
import type { Worker } from '@/api/task'
import PersonInfo from '@/views/dashboard/components/PersonInfo.vue'
import type { LabelValue } from '@/views/dashboard/components/TaskTabs.vue'
import TaskTabs from '@/views/dashboard/components/TaskTabs.vue'
import CurrentDateTime from '@/views/dashboard/components/CurrentDateTime.vue'
import { useTasksStore } from '@/stores/tasks'
import { storeToRefs } from 'pinia'
import ProcessingInfo from '@/views/dashboard/components/ProcessingInfo.vue'
defineOptions({
  name: 'DashboardView'
})
const taskList = ref<Task[]>()
function getChannels() {
  getTaskList(2)
    .then((res) => {
      taskList.value = res.data.Tasks
    })
    .catch((err) => {
      console.error(err)
      taskList.value = []
    })
}
const channels = computed(() => {
  return chain<Task>(taskList.value)
    .groupBy((ele) => ele.Channel)
    .value()
})
const process = computed(() => {
  return { name: '工艺名称', number: '111' }
})
const person = computed(() => {
  return {
    workerName: '姓名',
    phone: '111'
  } as unknown as Worker
})
getChannels()
const taskTabsTitle = ['未完成', '今日任务', '已完成']
const activeTaskTab = ref('未完成')
const taskTabsList = [
  {
    label: '未完成',
    value: 1
  },
  {
    label: '今日任务',
    value: 2
  },
  {
    label: '已完成',
    value: 3
  }
]
const activeTaskTab = ref(1)
const activeMainTabName = ref<string>('加工信息')
const tasksStore = useTasksStore()
const { activeTask, channels } = storeToRefs(tasksStore)
tasksStore.getChannels(1)
const { activeTask } = storeToRefs(tasksStore)
watchEffect(() => {
  // console.log(activeTaskTab?.value, 111111)
})
function changeTab(tab: LabelValue) {
  tasksStore.getChannels(tab.value)
}
</script>
<style scoped>
<style scoped lang="scss">
$active-tab-color: #00dfdf;
.date-time {
  width: 100%;
  display: flex;
@@ -94,4 +87,30 @@
  justify-content: center;
  padding-top: 12px;
}
:deep(.el-tabs__item) {
  color: #fff;
  font-size: 20px;
}
:deep(.el-tabs__nav-scroll) {
  height: 46px;
}
:deep(.el-tabs__nav) {
  height: 46px;
}
:deep(.el-step__title.is-process) {
  color: #fff;
}
:deep(.el-tabs__item.is-active) {
  color: $active-tab-color;
  font-weight: 600;
}
:deep(.el-tabs__active-bar) {
  background-color: $active-tab-color;
  height: 4px;
}
:deep(.el-tabs__nav-wrap::after) {
  height: 1px;
}
</style>
vite.config.ts
@@ -14,7 +14,7 @@
  server: {
    proxy: {
      '/v1/': {
        target: 'http://192.168.20.4:8003',
        target: 'http://192.168.20.119:8003',
        ws: true,
        changeOrigin: true
      }