From 8c84c7277018b259f5b99f26cbfd14603bc4e4c0 Mon Sep 17 00:00:00 2001 From: haoxuan <haoxuan> Date: 星期三, 01 十一月 2023 19:39:53 +0800 Subject: [PATCH] Merge branch 'dev' of http://192.168.5.5:10010/r/web/bulletin-board-style1 into dev --- src/views/dashboard/components/TaskControl.vue | 96 +++++ package-lock.json | 38 ++ src/components.d.ts | 1 src/stores/tasks.ts | 33 ++ src/api/device.ts | 9 src/api/index.ts | 32 ++ src/views/dashboard/components/TaskControlModal.vue | 573 +++++++++++++++++++++++++---------- src/components/icons/AlertLightIcon.vue | 36 ++ src/common/composable/useCountDown.ts | 11 src/views/dashboard/index.vue | 15 package.json | 4 src/stores/devices.ts | 23 + src/App.vue | 1 src/views/dashboard/components/DashboardTitle.vue | 51 +++ 14 files changed, 742 insertions(+), 181 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e1f76b..42e9c53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.1.0", "@vueuse/core": "^10.5.0", + "@xstate/vue": "^2.0.0", "axios": "^1.5.1", "dayjs": "^1.11.10", "element-plus": "^2.4.1", @@ -17,7 +18,8 @@ "pinia": "^2.1.7", "vue": "^3.3.4", "vue-hooks-plus": "^1.8.5", - "vue-router": "^4.2.5" + "vue-router": "^4.2.5", + "xstate": "^4.38.3" }, "devDependencies": { "@iconify-json/mdi": "^1.1.55", @@ -1473,6 +1475,24 @@ }, "peerDependenciesMeta": { "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@xstate/vue": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@xstate/vue/-/vue-2.0.0.tgz", + "integrity": "sha512-JlrJ3d+I6rZCcFBuu3O4GP+mGJfd11O9o69wRedzPMqZ+hxcMRBsih9L5kKnJHcU9CTmdJTT172oxTaYF7thzA==", + "peerDependencies": { + "@xstate/fsm": "^2.0.0", + "vue": "^3.0.0", + "xstate": "^4.31.0" + }, + "peerDependenciesMeta": { + "@xstate/fsm": { + "optional": true + }, + "xstate": { "optional": true } } @@ -4923,6 +4943,11 @@ "node": ">=12" } }, + "node_modules/xstate": { + "version": "4.38.3", + "resolved": "https://registry.npmmirror.com/xstate/-/xstate-4.38.3.tgz", + "integrity": "sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==" + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", @@ -5951,6 +5976,12 @@ "requires": {} } } + }, + "@xstate/vue": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@xstate/vue/-/vue-2.0.0.tgz", + "integrity": "sha512-JlrJ3d+I6rZCcFBuu3O4GP+mGJfd11O9o69wRedzPMqZ+hxcMRBsih9L5kKnJHcU9CTmdJTT172oxTaYF7thzA==", + "requires": {} }, "acorn": { "version": "8.10.0", @@ -8557,6 +8588,11 @@ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true }, + "xstate": { + "version": "4.38.3", + "resolved": "https://registry.npmmirror.com/xstate/-/xstate-4.38.3.tgz", + "integrity": "sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index e5cd1da..a630e65 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.1.0", "@vueuse/core": "^10.5.0", + "@xstate/vue": "^2.0.0", "axios": "^1.5.1", "dayjs": "^1.11.10", "element-plus": "^2.4.1", @@ -22,7 +23,8 @@ "pinia": "^2.1.7", "vue": "^3.3.4", "vue-hooks-plus": "^1.8.5", - "vue-router": "^4.2.5" + "vue-router": "^4.2.5", + "xstate": "^4.38.3" }, "devDependencies": { "@iconify-json/mdi": "^1.1.55", diff --git a/src/App.vue b/src/App.vue index 6cc3382..8217457 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,6 +4,7 @@ <script setup lang="ts"> import { RouterView } from 'vue-router' +import 'element-plus/es/components/message/style/css' </script> <style scoped></style> diff --git a/src/api/device.ts b/src/api/device.ts new file mode 100644 index 0000000..ceaaec2 --- /dev/null +++ b/src/api/device.ts @@ -0,0 +1,9 @@ +export interface Devices { + systemDeviceID: string + currentDeviceID: string + systemDeviceStatus: number + clusterStatus: string + clusterNodeQuantity: number + systemDeviceRunSince: number + deviceIDList: string[] +} diff --git a/src/api/index.ts b/src/api/index.ts index 708b8b2..c8e30e9 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,6 +1,7 @@ import { request } from '@/common/utils' import type { CraftParamsResponse, TasksGroupByChannel } from './task' import type { PLCResponse } from './plc' +import type { Devices } from './device' export interface BaseResponse<T = any> { code: number @@ -66,6 +67,11 @@ export interface SendProcessParamsParams { procedureId: number } + +/** + * 涓嬪彂宸ヨ壓鍙傛暟 + * @param params + */ export function sendProcessParams(params: SendProcessParamsParams) { return request<BaseResponse>({ url: `v1/task/sendProcessParams`, @@ -73,3 +79,29 @@ data: params }) } + +export interface FinishTaskParams { + id: number +} + +/** + * 缁撴潫浠诲姟 + * @param params + */ +export function finishTask(params: FinishTaskParams) { + return request<BaseResponse>({ + url: `v1/task/finish/${params.id}`, + method: 'put', + data: params + }) +} + +/** + * 鑾峰彇褰撳墠闈㈡澘缁戝畾鐨勮澶囧垪琛� + */ +export function getDeviceList() { + return request<BaseResponse<Devices>>({ + url: `/v1/device/list`, + method: 'get' + }) +} diff --git a/src/common/composable/useCountDown.ts b/src/common/composable/useCountDown.ts index a5dfa62..4adb7d6 100644 --- a/src/common/composable/useCountDown.ts +++ b/src/common/composable/useCountDown.ts @@ -19,7 +19,7 @@ } function useCountDown(seconds: number, options?: Options) { - const timer = new StepTimer(seconds * 1000, 1000) + let timer = new StepTimer(seconds * 1000, 1000) const remainingSeconds = ref<number>(seconds) @@ -65,6 +65,12 @@ timer.abort() } + function reset() { + timer.destroy() + remainingSeconds.value = seconds + timer = new StepTimer(seconds * 1000, 1000) + } + return { startCountdown, remainingSeconds, @@ -72,7 +78,8 @@ continueCountdown, stopCountdown, formattedTime, - countdownStatus + countdownStatus, + reset } } diff --git a/src/components.d.ts b/src/components.d.ts index cfba2f9..c544730 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -7,6 +7,7 @@ declare module 'vue' { export interface GlobalComponents { + AlertLightIcon: typeof import('./components/icons/AlertLightIcon.vue')['default'] BaseModal: typeof import('./components/BaseModal.vue')['default'] DashboardLayout: typeof import('./components/DashboardLayout.vue')['default'] ElButton: typeof import('element-plus/es')['ElButton'] diff --git a/src/components/icons/AlertLightIcon.vue b/src/components/icons/AlertLightIcon.vue new file mode 100644 index 0000000..3a70684 --- /dev/null +++ b/src/components/icons/AlertLightIcon.vue @@ -0,0 +1,36 @@ +<script setup lang="ts"></script> + +<template> + <svg + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="200px" + height="200px" + viewBox="0 0 200 200" + style=" + shape-rendering: geometricPrecision; + text-rendering: geometricPrecision; + image-rendering: optimizeQuality; + fill-rule: evenodd; + clip-rule: evenodd; + " + xmlns:xlink="http://www.w3.org/1999/xlink" + > + <g> + <path + style="opacity: 0.988" + fill="#d71d05" + d="M 87.5,-0.5 C 95.1667,-0.5 102.833,-0.5 110.5,-0.5C 146.888,7.72465 167.388,30.3913 172,67.5C 172.5,101.165 172.667,134.832 172.5,168.5C 123.833,168.5 75.1667,168.5 26.5,168.5C 26.3333,132.498 26.5,96.4985 27,60.5C 33.9986,27.0017 54.1653,6.66837 87.5,-0.5 Z M 101.5,40.5 C 103.678,52.6006 105.011,64.9339 105.5,77.5C 116.858,77.4139 128.191,77.9139 139.5,79C 123.945,100.223 108.778,121.723 94,143.5C 93.167,128.176 92.667,112.842 92.5,97.5C 80.4954,97.6665 68.4954,97.4999 56.5,97C 71.8692,78.4097 86.8692,59.5764 101.5,40.5 Z" + /> + </g> + <g> + <path + style="opacity: 0.995" + fill="#d81d05" + d="M 199.5,183.5 C 199.5,186.833 199.5,190.167 199.5,193.5C 197.167,195.167 195.167,197.167 193.5,199.5C 130.833,199.5 68.1667,199.5 5.5,199.5C 3.83333,197.167 1.83333,195.167 -0.5,193.5C -0.5,190.167 -0.5,186.833 -0.5,183.5C 1.67098,181.5 4.00432,179.666 6.5,178C 68.5,177.333 130.5,177.333 192.5,178C 194.996,179.666 197.329,181.5 199.5,183.5 Z" + /> + </g> + </svg> +</template> + +<style scoped lang="scss"></style> diff --git a/src/stores/devices.ts b/src/stores/devices.ts new file mode 100644 index 0000000..a961994 --- /dev/null +++ b/src/stores/devices.ts @@ -0,0 +1,23 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import type { Devices } from '@/api/device' +import { getDeviceList } from '@/api' + +export const useDevicesStore = defineStore('counter', () => { + const devices = ref<Devices>() + const deviceIDList = computed(() => devices?.value?.deviceIDList ?? []) + + function getDevicesInfo() { + getDeviceList().then( + (res) => { + devices.value = res?.data + }, + (err) => { + console.error(err) + devices.value = undefined + } + ) + } + + return { devices, deviceIDList, getDevicesInfo } +}) diff --git a/src/stores/tasks.ts b/src/stores/tasks.ts index 8f8599d..02f2694 100644 --- a/src/stores/tasks.ts +++ b/src/stores/tasks.ts @@ -25,7 +25,7 @@ offset: 0, limit: 3 } - getTaskList(params) + return getTaskList(params) .then((res) => { channels.value = res.data }) @@ -33,6 +33,36 @@ console.error(err) channels.value = [] }) + } + + /** + * 鏁版嵁鍔犺浇瀹屾垚鍚庤嚜鍔ㄩ�変腑涓�涓换鍔� + */ + function autoSelectTask(channel: number) { + const currentChannelTaskList = channels.value[channel].Tasks + if (currentChannelTaskList?.length) { + activeTask.value = currentChannelTaskList[0] + } else { + const firstNotEmptyChannel = Object.entries(channels.value).find((ele) => { + const taskList = (ele[1] as TasksResponse)?.Tasks + + return !!taskList.length + }) + + if (firstNotEmptyChannel) { + const channelNumber = +firstNotEmptyChannel[0] + activeTask.value = channels.value[channelNumber].Tasks[0] + } + } + } + + /** + * 鍒锋柊鎵�鏈夋暟鎹� + */ + function reload(channel: number) { + getChannels(currentType.value).then(() => { + autoSelectTask(channel) + }) } function moreChannelTasksBtn(channelNumber: number) { @@ -96,6 +126,7 @@ getChannels, moreBtnStatus, activeTask, + reload, setActiveTask, requestParamsMap, getParamsByChannel, diff --git a/src/views/dashboard/components/DashboardTitle.vue b/src/views/dashboard/components/DashboardTitle.vue new file mode 100644 index 0000000..ed14226 --- /dev/null +++ b/src/views/dashboard/components/DashboardTitle.vue @@ -0,0 +1,51 @@ +<template> + <div class="dashboard-title"> + <div class="title-text">鏅鸿兘宸ヤ綔鍙� 鈥� {{ props?.deviceInfo?.currentDeviceID ?? '' }}</div> + <div class="title-status"> + <div class="connection-info" @click="openSelectDeviceModal"> + <el-icon size="30" color="red"> + <AlertLightIcon></AlertLightIcon> + </el-icon> + </div> + <div class="connection-status"></div> + </div> + </div> +</template> +<script setup lang="ts"> +import type { Devices } from '@/api/device' +import AlertLightIcon from '@/components/icons/AlertLightIcon.vue' +import { ref } from 'vue' + +export interface DashBoardTitleProps { + deviceInfo: Devices +} + +const props = defineProps<DashBoardTitleProps>() + +const showModal = ref(false) + +function openSelectDeviceModal() { + showModal.value = true +} +</script> + +<style scoped lang="scss"> +.dashboard-title { + position: relative; +} +.title-text { + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; + font-weight: 700; +} +.title-status { + position: absolute; + top: 16px; + right: 40px; +} +.connection-info { + cursor: pointer; +} +</style> diff --git a/src/views/dashboard/components/TaskControl.vue b/src/views/dashboard/components/TaskControl.vue index 9a69334..47b9945 100644 --- a/src/views/dashboard/components/TaskControl.vue +++ b/src/views/dashboard/components/TaskControl.vue @@ -23,11 +23,30 @@ <template v-if="task?.Procedure.Status === 2 || task?.Procedure.Status === 3"> <BigButton class="btn" bg-color="#ff9933">鎵撳嵃</BigButton> <BigButton class="btn" bg-color="#00cc33">鎶ュ伐</BigButton> - <BigButton class="btn" bg-color="#ff0000">瀹屾垚</BigButton> + <el-popconfirm + width="340" + confirm-button-text="纭畾" + cancel-button-text="鍙栨秷" + :icon="CircleCloseFilled" + icon-color="red" + :hide-after="0" + :teleported="false" + title="璇风‘璁ゆ槸鍚﹀凡瀹屾垚姝ょ敓浜т换鍔�?" + placement="top" + @confirm="finishTaskProduce" + > + <template #reference> + <BigButton class="btn" bg-color="#ff0000">瀹屾垚</BigButton> + </template> + </el-popconfirm> </template> </div> </div> - <TaskControlModal v-model="showTaskControlModal" :task="task"></TaskControlModal> + <TaskControlModal + v-model="showTaskControlModal" + :task="task" + @produce-start="emit('shouldReload', task)" + ></TaskControlModal> </template> <script setup lang="ts"> import type { Task } from '@/api/task' @@ -35,11 +54,18 @@ import BigButton from '@/views/dashboard/components/BigButton.vue' import { useDateFormat } from '@vueuse/core' import TaskControlModal from '@/views/dashboard/components/TaskControlModal.vue' +import { CircleCloseFilled } from '@element-plus/icons-vue' +import { finishTask } from '@/api' +import { ElMessage } from 'element-plus' const props = defineProps<{ task?: Task }>() const { task } = toRefs(props) + +const emit = defineEmits<{ + shouldReload: [task: Task] +}>() const showTaskControlModal = ref(false) @@ -48,6 +74,35 @@ */ function startProduce() { showTaskControlModal.value = true +} + +/** + * 瀹屾垚浠诲姟 + */ +function finishTaskProduce() { + if (task?.value?.Procedure?.ID) { + finishTask({ id: task!.value.Procedure.ID }).then( + (res) => { + ElMessage({ + message: '鎿嶄綔鎴愬姛锛�', + type: 'success' + }) + emit('shouldReload', task.value) + }, + (err) => { + console.error(err) + ElMessage({ + message: '鎿嶄綔澶辫触锛�', + type: 'warning' + }) + } + ) + } else { + ElMessage({ + message: '褰撳墠璁惧娌℃湁宸ュ簭锛�', + type: 'warning' + }) + } } /** @@ -119,4 +174,41 @@ .finish-btn { background-color: #ff0000; } + +:deep(.el-popper) { + background-color: #133f97; + color: #fff; +} +:deep(.el-popconfirm__main) { + font-size: 25px; +} +:deep(.el-popconfirm__icon) { + font-size: 38px; +} +:deep(.el-popconfirm__main) { + margin-bottom: 20px; +} +:deep(.el-popconfirm__action) { + display: flex; + align-items: center; + justify-content: space-around; +} +:deep(.el-popconfirm__action .el-button) { + font-size: 22px; + height: 54px; + width: 140px; + color: #fff; + background-color: #0ae5ec; + &:hover { + background-color: #0ae5ec; + } + &.is-text { + color: #92a1c0; + background-color: #133f97; + border: 1px solid #0ae5ec; + &:hover { + background-color: #133f97; + } + } +} </style> diff --git a/src/views/dashboard/components/TaskControlModal.vue b/src/views/dashboard/components/TaskControlModal.vue index 247a850..e5980ab 100644 --- a/src/views/dashboard/components/TaskControlModal.vue +++ b/src/views/dashboard/components/TaskControlModal.vue @@ -1,141 +1,148 @@ <template> <div class="task-control-modal"> <BaseModal v-model="modelData" :wider="false"> - <template #title>鏂颁换鍔�</template> + <template #title> + {{ !['涓嬪彂鍙傛暟鎴愬姛', '涓嬪彂鍙傛暟澶辫触'].includes(state.value as string) ? '鏂颁换鍔�' : '鎻愮ず' }} + </template> <div class="modal-content"> - <div v-if="!messageError" class="content-title"> - <div class="content-title-item">褰撳墠浠诲姟锛歿{ task?.Procedure.procedure.procedureName || '' }}</div> - <div class="content-title-item"> - 鐢熶骇鏁伴噺锛� - <div class="leaf-shape box"> - {{ task?.Order?.amount || 0 }} + <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> - <div v-if="!!messageError" class="content-tips"> - <div class="error-t"> - <span v-if="messageError === '涓嬪彂鎴愬姛锛�'" class="el-icon-success color_success"></span> - <span v-else class="el-icon-error color_error"></span> - </div> - <div class="error-m"> - {{ messageError }} - </div> - <div class="font_size_20 color_fff" style="text-align: center; width: 100%; margin: 10px 0"> - <span v-if="messageError === '涓嬪彂鎴愬姛锛�'" style="font-size: 30px"></span> - <span v-else>璇烽噸璇�</span> - </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 v-else 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">瀹㈡埛鍚嶇О锛歿{ 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 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 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> - </div> - - <div class="title-auto-box"></div> - <div v-if="getCraftParamsErrMsg" class="process-err-tip"> - <div class="tip-icon"> - <span class="el-icon-error color_error"></span> - </div> - <div class="tip-content">鎻愮ず: {{ getCraftParamsErrMsg }}</div> - </div> - - <div v-if="countdown30s.countdownStatus.value === 'running'" class="countdown"> - {{ countdown30s.formattedTime.value }} - </div> - - <!-- <div v-if="showBtn === 2 || showBtn === 3" class="process-box">--> - <!-- <div--> - <!-- style="--> - <!-- color: red;--> - <!-- font-size: 26px;--> - <!-- width: 100%;--> - <!-- text-align: center;--> - <!-- margin-bottom: 15px;--> - <!-- line-height: 35px;--> - <!-- "--> - <!-- :class="showBtn === 3 && isLoading ? 'margin-top-10px' : 'margin-top-40px'"--> - <!-- >--> - <!-- <div v-if="showBtn === 2 || (showBtn === 3 && !isLoading)" class="gif-box">--> - <!-- <template v-if="showBtn === 2">--> - <!-- <div class="gif">--> - <!-- <img src="../../public/shan.gif" />--> - <!-- </div>--> - <!-- </template>--> - <!-- <template v-if="showBtn === 3 && !isLoading">--> - <!-- <div class="gif">--> - <!-- <span class="yuandian"></span>--> - <!-- </div>--> - <!-- </template>--> - <!-- <div class="gif-right">--> - <!-- <div>----- 鍓╀綑鏃堕棿 -----</div>--> - <!-- <div>--> - <!-- <span>00:{{ countdown30s.formattedTime }}</span>--> - <!-- </div>--> - <!-- </div>--> - <!-- </div>--> - <!-- {{ message }}--> - <!-- </div>--> - <!-- <template v-if="showBtn === 3 && isLoading">--> - <!-- <div class="progress-item">--> - <!-- <span>{{ (+num / 30) * 100 }}%</span>--> - <!-- <el-progress--> - <!-- style="width: calc(100% - 50px); float: right"--> - <!-- define-back-color="#CDC6C6"--> - <!-- color="#00cc66"--> - <!-- text-color="#fff"--> - <!-- :text-inside="true"--> - <!-- :stroke-width="20"--> - <!-- :percentage="(+num / 30) * 100"--> - <!-- ></el-progress>--> - <!-- </div>--> - <!-- </template>--> - <!-- </div>--> - </template> - </el-scrollbar> + </template> + </el-scrollbar> + </div> </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> - <div class="btn"> - <BigButton bg-color="#4765c0" @click="closeModal">鏆傜紦鐢熶骇</BigButton> - <BigButton - v-if="countdown30s.countdownStatus.value !== 'complete'" - color="#0d0d0d" - :disabled="countdown30s.countdownStatus.value === 'running'" - @click="startCountdown30s" - > - 鐢熶骇鍑嗗 - </BigButton> - <BigButton v-if="countdown30s.countdownStatus.value === 'complete'" bg-color="#4efefa" @click="startProduce"> - 寮�濮嬬敓浜� - </BigButton> - </div> + <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> @@ -143,13 +150,16 @@ <script setup lang="ts"> import type { CraftParam, Task } from '@/api/task' import { useDateFormat, useVModel } from '@vueuse/core' -import { computed, ref, toRefs, watch } from 'vue' +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 @@ -162,15 +172,13 @@ }) const emit = defineEmits<{ 'update:modelValue': [show: boolean] + /** 涓嬪彂鎴愬姛鍚庤Е鍙�, 鐢ㄤ簬澶栭儴鑾峰緱鍒锋柊鏁版嵁鐨勬椂鏈� */ + produceStart: [] }>() const modelData = useVModel(props, 'modelValue', emit) -function closeModal() { - modelData.value = false -} -const { task } = toRefs(props) -const messageError = ref('') +const { task } = toRefs(props) /** * 鏍煎紡鍖栨椂闂存埑 @@ -186,8 +194,8 @@ // 宸ヨ壓鍙傛暟 const craftParams = ref<CraftParam[]>() -// 鑾峰彇宸ヨ壓鍙傛暟澶辫触淇℃伅 -const getCraftParamsErrMsg = ref('') +// 鑾峰彇宸ヨ壓鍙傛暟缁撴灉淇℃伅 +const getCraftParamsTip = ref('') /** * 鑾峰彇褰撳墠灞曠ず鐨勪换鍔$殑宸ヨ壓鍙傛暟 @@ -195,19 +203,16 @@ function getTaskProduceParams(taskId?: number) { if (taskId) { craftParams.value = [] - getCraftParamsErrMsg.value = '' + getCraftParamsTip.value = '' getCraftParams({ id: taskId }).then( (res) => { craftParams.value = res.data.Params ?? [] - getCraftParamsErrMsg.value = '' - // TODO: 澶勭悊鍚勪釜鎸夐挳鏄鹃殣 - // this.getInfo() - console.log('processParams', craftParams.value) + getCraftParamsTip.value = '' }, (err) => { console.error(err) craftParams.value = [] - getCraftParamsErrMsg.value = '鑾峰彇宸ヨ壓鍙傛暟澶辫触锛�' + getCraftParamsTip.value = '鑾峰彇宸ヨ壓鍙傛暟澶辫触锛�' } ) } @@ -217,59 +222,218 @@ // 寮圭獥鏄剧ず鏃惰幏鍙栧伐鑹哄弬鏁� if (modelData.value) { getTaskProduceParams(task?.value?.Procedure?.ID) + } else { + reset() } }) -const countdown30s = useCountDown(3) +const countdown30s = useCountDown(30, { + onEnd: () => { + send('缁撴潫璁℃椂') + } +}) -function startCountdown30s() { - countdown30s.startCountdown() +// 寮圭獥鏃惰幏鍙栧畨鍏ㄧ敓浜ф彁绀烘枃鏈� +const { channels } = storeToRefs(useTasksStore()) +const safeProduce = ref('') +watch(modelData, () => { + if (modelData.value) { + safeProduce.value = channels?.value?.[task.value.Channel]?.Prompt?.safeProduce ?? '' + } +}) + +/** + * 閲嶇疆寮圭獥缂撳瓨鐘舵�� + */ +function reset() { + countdown30s.reset() + countdown3s.reset() + getCraftParamsTip.value = '' + deliveryTip.value = '' } /** - * 涓嬪彂宸ヨ壓鍙傛暟 + * 鎸夐挳鐘舵�佹満 + * 鍙互鍘� https://stately.ai/registry/new?mode=Design 鏌ョ湅鐘舵�佽浆鎹㈠浘 */ -function startProduce() { - if (task.value?.Procedure?.ID) { - message.value = '宸ヨ壓鍙傛暟涓嬪彂涓�...' - - isLoading.value = true - sendProcessParams({ - procedureId: task.value.Procedure.ID - }) - .then( - (res) => { - console.log(res) - messageError.value = '涓嬪彂鎴愬姛' - }, - (err) => { - console.error(err) - messageError.value = err.msg ? err.msg : '鎶辨瓑锛屽伐搴忎笅鍙戝け璐ワ紒' +const toggleMachine = createMachine({ + id: 'produce', + initial: '鍒濆鍖�', + predictableActionArguments: true, + states: { + 鍒濆鍖�: { + on: { + 寮�濮嬭鏃�: { target: '璁℃椂涓�' }, + 缁撴潫: { target: '鍒濆鍖�' } + } + }, + 璁℃椂涓�: { + on: { + 缁撴潫璁℃椂: { target: '鍑嗗鐢熶骇' }, + 鏆傜紦鐢熶骇: { target: '鍒濆鍖�' } + } + }, + 鍑嗗鐢熶骇: { + on: { + 寮�濮嬬敓浜�: { target: '涓嬪彂鍙傛暟涓�' }, + 鏆傜紦鐢熶骇: { target: '鍒濆鍖�' } + } + }, + 涓嬪彂鍙傛暟涓�: { + on: { + 鎴愬姛: { target: '涓嬪彂鍙傛暟鎴愬姛' }, + 澶辫触: { target: '涓嬪彂鍙傛暟澶辫触' }, + 鏆傜紦鐢熶骇: { target: '鍒濆鍖�' } + } + }, + 涓嬪彂鍙傛暟鎴愬姛: { + on: { + 缁撴潫: { + target: '鍒濆鍖�' } - ) - .finally(() => { - isLoading.value = false - }) + } + }, + 涓嬪彂鍙傛暟澶辫触: { + on: { + 鍐嶆涓嬪彂: { target: '鍑嗗鐢熶骇' } + } + } } +}) +const { state, send } = useMachine(toggleMachine) + +/** + * 鏆傜紦鐢熶骇, 鐩存帴鍏抽棴寮圭獥 + */ +function respiteProduce() { + modelData.value = false + send('鏆傜紦鐢熶骇') + reset() +} +/** + * 鐢熶骇鍑嗗 + */ +function prepareProduce() { + send('寮�濮嬭鏃�') + countdown30s.startCountdown() } -const { channels } = storeToRefs(useTasksStore()) -const safeProduce = computed(() => { - if (task?.value?.Channel) { - return channels?.value?.[task.value.Channel]?.Prompt?.safeProduce +// 鍙傛暟涓嬪彂鎴愬姛鎴栧け璐ョ粨鏋� +const deliveryTip = ref('') + +// 鍙傛暟涓嬪彂鎴愬姛鍚庡欢鏃�3绉掑悗鍏抽棴寮圭獥 +const countdown3s = useCountDown(3, { + onEnd: () => { + closeModal() } - return '' }) -const message = ref(safeProduce.value) -const isLoading = ref(false) + +/** + * 寮�濮嬬敓浜� , 涓嬪彂宸ヨ壓鍙傛暟 + */ +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: 400px; + 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; @@ -306,7 +470,7 @@ margin: 0 auto; padding: 10px 20px; width: calc(100% - 40px); - height: 400px; + height: 340px; } .info { display: flex; @@ -325,4 +489,73 @@ .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 index 7ece016..fb32de0 100644 --- a/src/views/dashboard/index.vue +++ b/src/views/dashboard/index.vue @@ -6,7 +6,9 @@ <template #leftBlock2> <ChannelCollapse :channels="channels"></ChannelCollapse> </template> - <template #middleBlock1>鏍囬</template> + <template #middleBlock1> + <DashboardTitle></DashboardTitle> + </template> <template #middleBlock2> <el-tabs v-model="activeMainTabName" class="main-info-tabs"> <el-tab-pane label="鍔犲伐淇℃伅" name="鍔犲伐淇℃伅"> @@ -21,7 +23,7 @@ <template #middleBlock3> <SubTitle>浠诲姟璇︽儏</SubTitle> <div class="task-detail"> - <TaskControl :task="activeTask"></TaskControl> + <TaskControl :task="activeTask" @should-reload="reloadAllData"></TaskControl> </div> <ColorInfo :order="order" :type="1"></ColorInfo> <ColorInfo :order="order" :type="2"></ColorInfo> @@ -47,7 +49,7 @@ <script setup lang="ts"> import { computed, ref } from 'vue' import ChannelCollapse from '@/views/dashboard/components/ChannelCollapse.vue' -import type { Worker, Order } from '@/api/task' +import type { Worker, Order, Task } from '@/api/task' import type { PLCResponse } from '@/api/plc' import PersonInfo from '@/views/dashboard/components/PersonInfo.vue' import ProcessInfo from '@/views/dashboard/components/ProcessInfo.vue' @@ -61,6 +63,7 @@ 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' defineOptions({ name: 'DashboardView' @@ -73,7 +76,7 @@ } as unknown as Worker }) const process = computed(() => { - return { product: '浜у搧鍚嶇О', number: '111', procedure: '宸ヨ壓鍚嶇О', isUpdate: true } + return { product: '浜у搧鍚嶇О', number: '111', procedure: '宸ヨ壓鍚嶇О', isUpdate: true } as any }) const order = computed(() => { @@ -113,6 +116,10 @@ function changeTab(tab: LabelValue) { tasksStore.getChannels(tab.value) } + +function reloadAllData(task: Task) { + tasksStore.reload(task.Channel) +} </script> <style scoped lang="scss"> -- Gitblit v1.8.0