From 33bdc00cdee41fa0e8d315f4bb2c3fc13ed0df10 Mon Sep 17 00:00:00 2001 From: haoxuan <haoxuan> Date: 星期四, 02 十一月 2023 19:30:50 +0800 Subject: [PATCH] Merge branch 'dev' of http://192.168.5.5:10010/r/web/bulletin-board-style1 into wn --- src/views/dashboard/index.vue | 209 ++++++++++++++++ src/views/dashboard/components/TaskControlModal.vue | 561 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 770 insertions(+), 0 deletions(-) diff --git a/src/views/dashboard/components/TaskControlModal.vue b/src/views/dashboard/components/TaskControlModal.vue new file mode 100644 index 0000000..c51b154 --- /dev/null +++ b/src/views/dashboard/components/TaskControlModal.vue @@ -0,0 +1,561 @@ +<template> + <div class="task-control-modal"> + <BaseModal v-model="modelData" :wider="false"> + <template #title> + {{ !['涓嬪彂鍙傛暟鎴愬姛', '涓嬪彂鍙傛暟澶辫触'].includes(state.value as string) ? '鏂颁换鍔�' : '鎻愮ず' }} + </template> + <div class="modal-content"> + <template v-if="['鍒濆鍖�', '璁℃椂涓�', '鍑嗗鐢熶骇', '涓嬪彂鍙傛暟涓�'].includes(state.value as string)"> + <div class="content-title"> + <div class="content-title-item">褰撳墠浠诲姟锛歿{ task?.Procedure.procedure.procedureName || '' }}</div> + <div class="content-title-item"> + 鐢熶骇鏁伴噺锛� + <div class="leaf-shape"> + {{ task?.Order?.amount || 0 }} + </div> + </div> + </div> + + <div class="content-scroll"> + <div class="scroll-container"> + <el-scrollbar always class="scroller"> + <template v-if="task"> + <div class="info"> + <div class="info-item">璁㈠崟缂栧彿锛歿{ task.Order.orderId || '' }}</div> + <div class="info-item">宸ュ崟缂栧彿锛歿{ task.Order.workOrderId || '' }}</div> + <div class="info-item">浜у搧鍚嶇О锛歿{ task.Order.productName || '' }}</div> + <div class="info-item">鏁伴噺锛歿{ task.Order.amount || 0 }}{{ task.Order.unit }}</div> + <div class="info-item">浜よ揣鏃ユ湡锛歿{ task.Order.deliverDate || '' }}</div> + <div class="info-item">宸ユ椂锛� {{ task.Procedure.procedure.workHours || '' }}</div> + <div class="info-item"> + 璁″垝鏃堕棿锛� {{ formatDate(task.Procedure.startTime) || '' }} + - + {{ formatDate(task.Procedure.endTime) }} + </div> + + <div class="info-item">瀹㈡埛鍚嶇О锛歿{ task.Order.customer || '' }}</div> + <div class="info-item info-item-two">閫氶亾锛� {{ CHANNEL_NAME_MAP[task.Channel] || '' }}</div> + + <div class="info-item info-item-two">鍙傛暟瑕佹眰锛歿{ task.Order.parameter || '' }}</div> + + <div class="info-item-two"> + <div style="color: #4efefa; font-size: 18px; margin-bottom: 10px; margin-top: 20px">宸ヨ壓鍙傛暟</div> + <div v-for="(item, index) in craftParams" :key="index" class="info-item info-item-two"> + {{ item.Key }}锛歿{ item.Value || '' }} + </div> + </div> + </div> + </template> + </el-scrollbar> + </div> + </div> + </template> + + <!-- 鍙湁鑾峰彇鍒板伐鑹哄弬鏁版墠鍙互杩涜鎿嶄綔--> + <template v-if="getCraftParamsTip"> + <div class="content-tips"> + <div class="craft-params-error"> + <div class="error-icon"> + <el-icon size="90" color="red"><CircleCloseFilled /></el-icon> + </div> + <div class="error-tip">{{ getCraftParamsTip }}</div> + </div> + </div> + </template> + + <template v-else> + <div v-if="['璁℃椂涓�', '鍑嗗鐢熶骇'].includes(state.value as string)" class="content-tips"> + <div class="prepare"> + <div class="countdown"> + <div class="alert-light"> + <div class="light" :class="{ blink: state.value === '璁℃椂涓�' }"></div> + </div> + <div class="time"> + <div class="time-label">----- 鍓╀綑鏃堕棿 -----</div> + <div class="time-text">00:{{ countdown30s.formattedTime.value }}</div> + </div> + </div> + <div class="safe-tip"> + {{ safeProduce }} + </div> + </div> + </div> + + <div v-if="['涓嬪彂鍙傛暟涓�'].includes(state.value as string)" class="content-tips"> + <div class="delivery"> + <div class="delivery-tip">宸ヨ壓鍙傛暟涓嬪彂涓�...</div> + <div class="delivery-progress"> + <el-progress :text-inside="true" :stroke-width="30" :percentage="50" status="success" /> + </div> + </div> + </div> + + <div v-if="['涓嬪彂鍙傛暟鎴愬姛'].includes(state.value as string)" class="delivery-success-tips"> + <div class="success-icon"> + <el-icon size="90" color="green"><SuccessFilled /></el-icon> + </div> + <div class="success-tip">{{ deliveryTip }}</div> + <div class="success-sub-tip">{{ countdown3s.remainingSeconds }}s</div> + </div> + + <div v-if="['涓嬪彂鍙傛暟澶辫触'].includes(state.value as string)" class="delivery-error-tips"> + <div class="error-icon"> + <el-icon size="90" color="red"><CircleCloseFilled /></el-icon> + </div> + <div class="error-tip">{{ deliveryTip }}</div> + <div class="error-sub-tip">璇烽噸璇�</div> + </div> + </template> + </div> + <template #footer> + <template v-if="getCraftParamsTip"> + <div class="btn"> + <BigButton bg-color="#4765c0" @click="closeModal"> 鍏抽棴 </BigButton> + </div> + </template> + + <template v-else> + <div class="btn"> + <BigButton + v-if="!['涓嬪彂鍙傛暟鎴愬姛', '涓嬪彂鍙傛暟澶辫触'].includes(state.value as string)" + bg-color="#4765c0" + @click="respiteProduce" + > + 鏆傜紦鐢熶骇 + </BigButton> + <BigButton + v-if="['鍒濆鍖�', '璁℃椂涓�'].includes(state.value as string)" + color="#0d0d0d" + :disabled="state.value === '璁℃椂涓�'" + @click="prepareProduce" + > + 鐢熶骇鍑嗗 + </BigButton> + <BigButton v-if="state.value === '鍑嗗鐢熶骇'" bg-color="#4efefa" @click="startProduce"> 寮�濮嬬敓浜� </BigButton> + <BigButton v-if="state.value === '涓嬪彂鍙傛暟涓�'" bg-color="#4efefa"> + <el-icon class="is-loading" color="#000"> + <Loading /> + </el-icon> + </BigButton> + <BigButton v-if="state.value === '涓嬪彂鍙傛暟澶辫触'" bg-color="#4765c0" @click="deliverParams"> + 鍐嶆涓嬪彂 + </BigButton> + <BigButton v-if="state.value === '涓嬪彂鍙傛暟鎴愬姛'" bg-color="#4765c0" @click="closeModal"> 鍏抽棴 </BigButton> + </div> + </template> + </template> + </BaseModal> + </div> +</template> +<script setup lang="ts"> +import type { CraftParam, Task } from '@/api/task' +import { useDateFormat, useVModel } from '@vueuse/core' +import { ref, toRefs, watch } from 'vue' +import BigButton from '@/views/dashboard/components/BigButton.vue' +import { CHANNEL_NAME_MAP } from '@/common/constants' +import { getCraftParams, sendProcessParams } from '@/api' +import { useCountDown } from '@/common/composable' +import { storeToRefs } from 'pinia' +import { useTasksStore } from '@/stores/tasks' +import { createMachine } from 'xstate' +import { useMachine } from '@xstate/vue' +import { CircleCloseFilled, Loading, SuccessFilled } from '@element-plus/icons-vue' + +export interface TaskControlModalProps { + task?: Task + /** 鏄惁灞曠ず妯℃�佹 */ + modelValue: boolean +} +const props = withDefaults(defineProps<TaskControlModalProps>(), { + task: undefined, + modelValue: false +}) +const emit = defineEmits<{ + 'update:modelValue': [show: boolean] + /** 涓嬪彂鎴愬姛鍚庤Е鍙�, 鐢ㄤ簬澶栭儴鑾峰緱鍒锋柊鏁版嵁鐨勬椂鏈� */ + produceStart: [] +}>() + +const modelData = useVModel(props, 'modelValue', emit) + +const { task } = toRefs(props) + +/** + * 鏍煎紡鍖栨椂闂存埑 + * @param timestamp 鍚庣杩旂殑10浣嶆椂闂存埑 + */ +function formatDate(timestamp?: number) { + if (!timestamp) { + return '--' + } + const time = useDateFormat(timestamp * 1000, 'YYYY-MM-DD', { locales: 'zh-cn' }) + return time.value +} + +// 宸ヨ壓鍙傛暟 +const craftParams = ref<CraftParam[]>() +// 鑾峰彇宸ヨ壓鍙傛暟缁撴灉淇℃伅 +const getCraftParamsTip = ref('') + +/** + * 鑾峰彇褰撳墠灞曠ず鐨勪换鍔$殑宸ヨ壓鍙傛暟 + */ +function getTaskProduceParams(taskId?: number) { + if (taskId) { + craftParams.value = [] + getCraftParamsTip.value = '' + getCraftParams({ id: taskId }).then( + (res) => { + craftParams.value = res.data.Params ?? [] + getCraftParamsTip.value = '' + }, + (err) => { + console.error(err) + craftParams.value = [] + getCraftParamsTip.value = '鑾峰彇宸ヨ壓鍙傛暟澶辫触锛�' + } + ) + } +} + +watch(modelData, () => { + // 寮圭獥鏄剧ず鏃惰幏鍙栧伐鑹哄弬鏁� + if (modelData.value) { + getTaskProduceParams(task?.value?.Procedure?.ID) + } else { + reset() + } +}) + +const countdown30s = useCountDown(30, { + onEnd: () => { + send('缁撴潫璁℃椂') + } +}) + +// 寮圭獥鏃惰幏鍙栧畨鍏ㄧ敓浜ф彁绀烘枃鏈� +const { channels } = storeToRefs(useTasksStore()) +const safeProduce = ref('') +watch(modelData, () => { + if (modelData.value) { + safeProduce.value = channels?.value?.[task?.value?.Channel ?? 0]?.Prompt?.safeProduce ?? '' + } +}) + +/** + * 閲嶇疆寮圭獥缂撳瓨鐘舵�� + */ +function reset() { + countdown30s.reset() + countdown3s.reset() + getCraftParamsTip.value = '' + deliveryTip.value = '' +} + +/** + * 鎸夐挳鐘舵�佹満 + * 鍙互鍘� https://stately.ai/registry/new?mode=Design 鏌ョ湅鐘舵�佽浆鎹㈠浘 + */ +const toggleMachine = createMachine({ + id: 'produce', + initial: '鍒濆鍖�', + predictableActionArguments: true, + states: { + 鍒濆鍖�: { + on: { + 寮�濮嬭鏃�: { target: '璁℃椂涓�' }, + 缁撴潫: { target: '鍒濆鍖�' } + } + }, + 璁℃椂涓�: { + on: { + 缁撴潫璁℃椂: { target: '鍑嗗鐢熶骇' }, + 鏆傜紦鐢熶骇: { target: '鍒濆鍖�' } + } + }, + 鍑嗗鐢熶骇: { + on: { + 寮�濮嬬敓浜�: { target: '涓嬪彂鍙傛暟涓�' }, + 鏆傜紦鐢熶骇: { target: '鍒濆鍖�' } + } + }, + 涓嬪彂鍙傛暟涓�: { + on: { + 鎴愬姛: { target: '涓嬪彂鍙傛暟鎴愬姛' }, + 澶辫触: { target: '涓嬪彂鍙傛暟澶辫触' }, + 鏆傜紦鐢熶骇: { target: '鍒濆鍖�' } + } + }, + 涓嬪彂鍙傛暟鎴愬姛: { + on: { + 缁撴潫: { + target: '鍒濆鍖�' + } + } + }, + 涓嬪彂鍙傛暟澶辫触: { + on: { + 鍐嶆涓嬪彂: { target: '鍑嗗鐢熶骇' } + } + } + } +}) +const { state, send } = useMachine(toggleMachine) + +/** + * 鏆傜紦鐢熶骇, 鐩存帴鍏抽棴寮圭獥 + */ +function respiteProduce() { + modelData.value = false + send('鏆傜紦鐢熶骇') + reset() +} +/** + * 鐢熶骇鍑嗗 + */ +function prepareProduce() { + send('寮�濮嬭鏃�') + countdown30s.startCountdown() +} + +// 鍙傛暟涓嬪彂鎴愬姛鎴栧け璐ョ粨鏋� +const deliveryTip = ref('') + +// 鍙傛暟涓嬪彂鎴愬姛鍚庡欢鏃�3绉掑悗鍏抽棴寮圭獥 +const countdown3s = useCountDown(3, { + onEnd: () => { + closeModal() + } +}) + +/** + * 寮�濮嬬敓浜� , 涓嬪彂宸ヨ壓鍙傛暟 + */ +function startProduce() { + send('寮�濮嬬敓浜�') + + sendProcessParams({ + procedureId: task!.value!.Procedure.ID + }) + .then( + (res) => { + deliveryTip.value = '涓嬪彂鎴愬姛' + send('鎴愬姛') + countdown3s.startCountdown() + }, + (err) => { + console.error(err) + deliveryTip.value = err.msg ? err.msg : '鎶辨瓑锛屽伐搴忎笅鍙戝け璐ワ紒' + send('澶辫触') + } + ) + .finally(() => {}) +} + +/** + * 鍐嶆涓嬪彂 + */ +function deliverParams() { + send('鍐嶆涓嬪彂') +} + +/** + * 鍏抽棴寮圭獥 + */ +function closeModal() { + modelData.value = false + send('缁撴潫') + reset() + emit('produceStart') +} +</script> +<style scoped lang="scss"> +.modal-content { + height: 550px; +} +.content-scroll { + height: 350px; + overflow: hidden; +} +.content-tips { + height: 120px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 40px; +} + +@keyframes blink { + from { + opacity: 1; + } + 50% { + opacity: 1; + } + 51% { + opacity: 0; + } + to { + opacity: 0; + } +} +.prepare { + width: 100%; + .safe-tip { + width: 100%; + text-align: center; + color: red; + font-size: 30px; + margin-top: 10px; + background-color: #142974; + } +} +.countdown { + color: #fff; + display: flex; + align-items: center; + justify-content: center; + .alert-light { + margin-right: 20px; + .light { + height: 56px; + width: 56px; + background-color: red; + border-radius: 50%; + } + .light.blink { + animation: blink 800ms infinite; + } + } + .time { + .time-text { + text-align: center; + font-size: 20px; + font-weight: 600; + } + } +} +:deep(.el-dialog__body) { + padding: 0 20px; +} +.btn { + display: flex; + align-items: center; + justify-content: space-around; +} +.content-title { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 40px; +} +.content-title-item { + width: 50%; + font-size: 20px; + color: #4efefa; +} + +.leaf-shape { + position: relative; + display: inline-block; + width: 140px; + height: 46px; + border-radius: 23px 0 23px 0; + text-align: center; + line-height: 46px; + color: #fff; + background: url('/leaf-shape.png') no-repeat center center / cover; +} +.scroll-container { + margin: 0 auto; + padding: 10px 20px; + width: calc(100% - 40px); + height: 340px; +} +.info { + display: flex; + align-items: center; + flex-wrap: wrap; + background-color: #0e246a; + padding: 10px 20px; +} +.info-item { + width: 50%; + height: 35px; + line-height: 35px; + font-size: 16px; + color: #fff; +} +.info-item-two { + width: 100%; +} + +.delivery-success-tips { + padding-top: 140px; + color: #fff; + + height: 100%; + width: 100%; + .success-icon { + display: flex; + align-items: center; + justify-content: center; + } + .success-tip { + margin-top: 50px; + text-align: center; + font-size: 30px; + } + .success-sub-tip { + margin-top: 10px; + font-size: 30px; + text-align: center; + } +} +.delivery-error-tips { + padding-top: 140px; + color: #fff; + + height: 100%; + width: 100%; + .error-icon { + display: flex; + align-items: center; + justify-content: center; + } + .error-tip { + margin-top: 50px; + text-align: center; + font-size: 30px; + } + .error-sub-tip { + margin-top: 10px; + font-size: 20px; + text-align: center; + } +} + +.delivery { + height: 100%; + width: 100%; + padding: 0 90px; + .delivery-tip { + text-align: center; + font-size: 30px; + color: red; + } + .delivery-progress { + margin-top: 8px; + } +} +.craft-params-error { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + .error-tip { + font-size: 18px; + color: #fff; + } +} +</style> diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..815948f --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,209 @@ +<template> + <DashboardLayout> + <template #leftBlock1> + <TaskTabs v-model="activeTaskTab" style="margin-top: 20px" :list="taskTabsList" @change="changeTab"></TaskTabs> + </template> + <template #leftBlock2> + <ChannelCollapse :channels="channels"></ChannelCollapse> + </template> + <template #middleBlock1> + <DashboardTitle></DashboardTitle> + </template> + <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="宸ヨ壓淇℃伅"> + <ProcessInfo + v-for="item in craftModelStore.craftModelList" + :key="item.ID" + :craft-model="item" + @detail="openCraftModelDetailModal" + ></ProcessInfo> + </el-tab-pane> + <el-tab-pane label="鐗╂枡娓呭崟" name="鐗╂枡娓呭崟"> + <InputMaterialsList></InputMaterialsList> + <OutputMaterialsList></OutputMaterialsList> + </el-tab-pane> + </el-tabs> + </template> + <template #middleBlock3> + <SubTitle>浠诲姟璇︽儏</SubTitle> + <div class="task-detail"> + <TaskControl :task="activeTask" @should-reload="reloadAllData"></TaskControl> + </div> + <ColorInfo :order="order" :type="1"></ColorInfo> + <ColorInfo :order="order" :type="2"></ColorInfo> + </template> + <template #middleBlock4> + <SubTitle>浜哄憳淇℃伅</SubTitle> + <PersonInfo v-for="worker in workers" :key="worker.workerId" :person="worker"></PersonInfo> + </template> + <template #rightBlock1> + <div class="date-time"> + <CurrentDateTime></CurrentDateTime> + </div> + </template> + <template #rightBlock2> + <DeviceStatusInfo :plc="plcStore.plcInfo" :type="1"></DeviceStatusInfo> + <DeviceStatusInfo :device="deviceStore.deviceInfo" :type="2"></DeviceStatusInfo> + <DeviceNumberInfo></DeviceNumberInfo> + </template> + <template #rightBlock3> + <SubTitle>鐭ヨ瘑搴�</SubTitle> + <KnowledgeInfo></KnowledgeInfo> + <BigButton class="btn" bg-color="red">绾㈢伅鍛煎彨</BigButton> + </template> + </DashboardLayout> + <CraftDetailModal v-model="showCraftModelDetail" @close="showCraftModelDetail = false"></CraftDetailModal> +</template> +<script setup lang="ts"> +import { computed, ref, watch } from 'vue' +import ChannelCollapse from '@/views/dashboard/components/ChannelCollapse.vue' +import type { Worker, Order, Task } from '@/api/task' +import PersonInfo from '@/views/dashboard/components/PersonInfo.vue' +import ProcessInfo from '@/views/dashboard/components/ProcessInfo.vue' +import ColorInfo from '@/views/dashboard/components/ColorInfo.vue' +import DeviceStatusInfo from '@/views/dashboard/components/DeviceStatusInfo.vue' +import DeviceNumberInfo from '@/views/dashboard/components/DeviceNumberInfo.vue' +import KnowledgeInfo from '@/views/dashboard/components/KnowledgeInfo.vue' +import InputMaterialsList from '@/views/dashboard/components/InputMaterialsList.vue' +import OutputMaterialsList from '@/views/dashboard/components/OutputMaterialsList.vue' +import BigButton from '@/views/dashboard/components/BigButton.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' +import TaskControl from '@/views/dashboard/components/TaskControl.vue' +import SubTitle from '@/views/dashboard/components/SubTitle.vue' +import DashboardTitle from '@/views/dashboard/components/DashboardTitle.vue' +import { usePLCStore } from '@/stores/plc' +import { useDevicesStore } from '@/stores/devices' +import { useCraftModelStore } from '@/stores/craftModel' +import CraftDetailModal from '@/views/dashboard/components/CraftDetailModal.vue' + +defineOptions({ + name: 'DashboardView' +}) + +// 鑾峰彇褰撳墠楂樹寒浠诲姟鐨勫�肩彮浜轰俊鎭� +const taskStore = useTasksStore() +const workers = computed(() => { + return taskStore.activeTask?.Procedure?.procedure?.workers ?? [] +}) + +const process = computed(() => { + return { product: '浜у搧鍚嶇О', number: '111', procedure: '宸ヨ壓鍚嶇О', isUpdate: true } as any +}) + +const order = computed(() => { + return { + finishNumber: 0, + unit: '涓�', + amount: '10' + } as unknown as Order +}) + +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) + +function changeTab(tab: LabelValue) { + tasksStore.getChannels(tab.value) +} + +/** + * 瀹屾垚浠诲姟鎴栬�呬笅鍙戝弬鏁版垚鍔熷悗瑕佸埛鏂伴�氶亾鏁版嵁 + * @param task + */ +function reloadAllData(task: Task) { + tasksStore.reload(task.Channel) +} +// 鍚姩plc 杞 +const plcStore = usePLCStore() +plcStore.startPollingPLC() +// 鍚姩 璁惧 杞 +const deviceStore = useDevicesStore() +deviceStore.startPollingDevice() + +// 鍒囨崲浠诲姟鏃惰幏鍙栧搴斾换鍔$殑宸ヨ壓妯″瀷淇℃伅 +const craftModelStore = useCraftModelStore() +watch(activeTask, () => { + craftModelStore.getCraftModelList() +}) + +const showCraftModelDetail = ref(false) +function openCraftModelDetailModal() { + console.log(1) + showCraftModelDetail.value = true +} +</script> + +<style scoped lang="scss"> +$active-tab-color: #00dfdf; +.date-time { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding-top: 12px; +} +:deep(.el-tabs) { + height: 100%; +} +:deep(.el-tab-pane) { + height: 100%; +} +:deep(.el-tabs__content) { + height: calc(100% - 56px); +} +: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; +} +.btn { + width: 100%; + font-size: 20px; +} +</style> -- Gitblit v1.8.0