From bb7ae31d7066a838bd177bf21c20f13ef044950b Mon Sep 17 00:00:00 2001 From: riku <risaku@163.com> Date: 星期三, 27 十二月 2023 17:45:45 +0800 Subject: [PATCH] 编写后台任务模块逻辑,完成80% --- src/styles/element/base.scss | 78 +++++++ src/components/SlotEvents.ts | 63 +++++ src/components/button/FYReconfrimButton.vue | 46 ++++ src/views/fysp/evaluation/components/CompEvaTask.vue | 78 ++++-- src/components.d.ts | 1 src/utils/time-util.js | 31 ++ src/api/fysp/bgtaskApi.js | 33 +- src/views/fysp/evaluation/components/precheck/CompCheckSource.vue | 2 src/components/bg-task/FYBgTaskCard.vue | 200 ++++++++++++++---- src/assets/base.css | 4 src/main.js | 14 src/composables/timer.js | 42 +++ vite.config.js | 2 src/App.vue | 4 src/styles/element/index.scss | 11 15 files changed, 506 insertions(+), 103 deletions(-) diff --git a/src/App.vue b/src/App.vue index d8dfe8b..e2f76b6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -61,6 +61,9 @@ </script> <style scoped> +.el-container{ + /* min-height: 820px; */ +} .el-aside { width: initial; } @@ -81,6 +84,7 @@ .el-main__content { padding: var(--el-main-padding); max-height: calc(100vh - 60px - var(--el-main-padding) * 2); + /* background-color: aqua; */ /* overflow: auto; */ } .back-top { diff --git a/src/api/fysp/bgtaskApi.js b/src/api/fysp/bgtaskApi.js index e2dd4cd..e6d1045 100644 --- a/src/api/fysp/bgtaskApi.js +++ b/src/api/fysp/bgtaskApi.js @@ -14,7 +14,7 @@ /** * 鏂板缓涓�涓祴璇曚换鍔� - * @param {String} taskId + * @param {String} taskId * @returns {Promise} */ newTestTask(taskId) { @@ -22,17 +22,8 @@ }, /** - * 鏂板缓涓�涓祴璇曚换鍔� - * @param {String} taskId - * @returns {Promise} - */ - startTask(param) { - return $fysp.put(`bgTask/start`, param).then((res) => res.data); - }, - - /** * 鏂板缓骞惰繍琛屼竴涓祴璇曚换鍔� - * @param {String} taskId + * @param {String} taskId * @returns {Promise} */ startNewTestTask(taskId) { @@ -40,11 +31,29 @@ }, /** - * 寮哄埗鍏抽棴娴嬭瘯浠诲姟 + * 杩愯涓�涓换鍔� + * @param {String} taskId + * @returns {Promise} + */ + startTask(param) { + return $fysp.put(`bgTask/start`, param).then((res) => res.data); + }, + + /** + * 寮哄埗鍏抽棴浠诲姟 * @param {Object} param 鍚庡彴浠诲姟鏌ヨ鏉′欢 * @returns {Promise} */ shutDownTask(param) { return $fysp.put(`bgTask/shutDown`, param).then((res) => res.data); }, + + /** + * 绉婚櫎涓�涓换鍔� + * @param {Object} param 鍚庡彴浠诲姟鏌ヨ鏉′欢 + * @returns + */ + removeTask(param) { + return $fysp.put(`bgTask/remove`, param).then((res) => res.data); + } }; diff --git a/src/assets/base.css b/src/assets/base.css index ff2dd39..a14d6ab 100644 --- a/src/assets/base.css +++ b/src/assets/base.css @@ -67,4 +67,6 @@ body { /* background-color: rebeccapurple; */ margin: 0; -} + /* min-width: 1440px; */ + /* min-height: 820px; */ +} \ No newline at end of file diff --git a/src/components.d.ts b/src/components.d.ts index aff2c0b..42c74ea 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -72,6 +72,7 @@ FYOptionText: typeof import('./components/search-option/base/FYOptionText.vue')['default'] FYOptionTime: typeof import('./components/search-option/FYOptionTime.vue')['default'] FYOptionUserType: typeof import('./components/search-option/FYOptionUserType.vue')['default'] + FYReconfrimButton: typeof import('./components/button/FYReconfrimButton.vue')['default'] FYSearchBar: typeof import('./components/search-option/FYSearchBar.vue')['default'] FYTable: typeof import('./components/table/FYTable.vue')['default'] Header: typeof import('./components/core/Header.vue')['default'] diff --git a/src/components/SlotEvents.ts b/src/components/SlotEvents.ts new file mode 100644 index 0000000..78c251a --- /dev/null +++ b/src/components/SlotEvents.ts @@ -0,0 +1,63 @@ +import { cloneVNode, Comment, defineComponent, Fragment, h, Text, VNode } from 'vue'; + +/** + * 瀵逛簬鏂囨湰鎴朣VG锛岄渶瑕佺敤span鍖呰9涓�涓� + * @param s + */ +const wrapTextContent = (s: string | VNode): VNode => { + return h('span', null, s); +}; + +/** + * 鍒ゆ柇鏄惁涓篛bject + * @param val + */ +const isObject = (val: any) => val !== null && typeof val === 'object'; + +/** + * 鎵惧嚭绗竴涓悎娉曠殑瀛愬厓绱� + * @param node + */ +const findFirstLegitChild = (node: VNode[] | undefined): VNode | null => { + if (!node) return null; + for (const child of node) { + if (isObject(child)) { + switch (child.type) { + case Comment: + continue; + case Text: + case 'svg': + return wrapTextContent(child); + case Fragment: + return findFirstLegitChild(child.children as VNode[]); + default: + return child; + } + } + return wrapTextContent(child); + } + return null; +}; + +const NAME = 'SlotEvents'; + +const SlotEvents = defineComponent({ + name: NAME, + setup(_, { slots, attrs }) { + return () => { + const defaultSlot = slots.default?.(attrs); + if (!defaultSlot) return null; + if (defaultSlot.length > 1) { + console.warn(`${NAME}: 鍙渶瑕佷竴涓瓙鍏冪礌`); + return null; + } + const firstLegitChild = findFirstLegitChild(defaultSlot); + if (!firstLegitChild) { + console.warn(`${NAME}: 娌℃湁鍙敤鐨勫瓙鍏冪礌`); + return null; + } + return cloneVNode(firstLegitChild, attrs); + }; + } +}); +export default SlotEvents; diff --git a/src/components/bg-task/FYBgTaskCard.vue b/src/components/bg-task/FYBgTaskCard.vue index 7a00510..0c50ec5 100644 --- a/src/components/bg-task/FYBgTaskCard.vue +++ b/src/components/bg-task/FYBgTaskCard.vue @@ -1,67 +1,175 @@ <template> - <el-card class="bg-task-card" shadow="hover" :body-style="{ padding: '8px' }"> - <!-- <template #header> --> - <el-row style="background-color: aliceblue"> - <!-- <el-space> --> - <el-tag>{{ nameToLabel(model.type) }}</el-tag> - <el-text style="width: 80%" tag="b" size="large" truncated>{{ model.name }}</el-text> - <!-- <div>{{ model.name }}asdasdasdasdasdasdaasdasdasdasdasdasdaasdasdasdasdasdasdaasdasdasdasdasdasda</div> --> - <!-- </el-space> --> + <el-card class="m-b-8" shadow="always" :body-style="{ padding: '8px' }"> + <el-row> + <el-col :span="4"> + <div class="status-btn"> + <el-icon v-if="waiting" color="var(--el-color-info)" :size="50"><VideoPlay /></el-icon> + <el-icon v-else-if="running" color="var(--el-color-primary)" :size="50" class="is-loading" + ><Loading + /></el-icon> + <el-icon v-else-if="success" color="var(--el-color-success)" :size="50" + ><CircleCheck + /></el-icon> + <el-icon v-else-if="fail" color="var(--el-color-error)" :size="50" + ><CircleClose + /></el-icon> + <el-icon v-else color="var(--el-color-warning)" :size="50"><Warning /></el-icon> + <el-text type="info" size="small" style="position: absolute; bottom: 0">{{ + nameToLabel(model.status) + }}</el-text> + </div> + </el-col> + <el-col :span="20" class="p-l-8"> + <el-row justify="space-between"> + <el-text class="m-l-4px w-150px" tag="b" size="large" truncated>{{ model.name }}</el-text> + <el-tag>{{ nameToLabel(model.type) }}</el-tag> + </el-row> + <el-row class="p-v-8" align="bottom"> + <el-col :span="12"> + <span class="timer">{{ time }}</span> + <el-text type="info" size="small" tag="div">杩愯鏃堕暱</el-text> + </el-col> + <el-col :span="12"> + <el-text type="default" size="default" tag="div" + >寮�濮嬶細{{ $fm.formatYMDH(model.startTime) }}</el-text + > + <el-text type="default" size="default" tag="div" + >缁撴潫锛歿{ $fm.formatYMDH(model.endTime) }}</el-text + > + </el-col> + </el-row> + <el-row justify="end" align="bottom"> + <!-- <span class="f-s color-i">ID锛歿{ model.id }}</span> --> + <el-row> + <FYReconfrimButton v-if="waiting" @confirm="startTask" v-model="startConfirm"> + <el-button + plain + icon="VideoPlay" + type="primary" + size="small" + :loading="false" + @click="startConfirm = true" + >寮�濮嬩换鍔�</el-button + > + </FYReconfrimButton> + <FYReconfrimButton v-if="running" @confirm="stopTask" v-model="stopConfirm"> + <el-button + icon="VideoPause" + plain + type="danger" + size="small" + :loading="false" + @click="stopConfirm = true" + >寮哄埗缁撴潫</el-button + > + </FYReconfrimButton> + <FYReconfrimButton v-if="!running" @confirm="removeTask" v-model="removeConfirm"> + <el-button + icon="Delete" + plain + type="danger" + size="small" + :loading="false" + @click="removeConfirm = true" + >绉婚櫎浠诲姟</el-button + > + </FYReconfrimButton> + <el-button + v-if="success" + plain + type="success" + size="small" + :loading="false" + @click="gotoResult" + >鏌ョ湅缁撴灉<el-icon class="m-l-4"><Right /></el-icon + ></el-button> + </el-row> + </el-row> + </el-col> </el-row> - <!-- <div> - <el-text truncated>{{ model.name }}asdasdasdasdasdasdaasdasdasdasdasdasdaasdasdasdasdasdasdaasdasdasdasdasdasda</el-text> - </div> --> - - <!-- </template> --> - <div> - <el-text type="info" size="small">ID锛歿{ model.id }}</el-text> - </div> - - <div> - <el-text type="info" size="small">鐘舵�侊細{{ nameToLabel(model.status) }}</el-text> - </div> - <div> - <el-text type="info" size="small">寮�濮嬶細{{ model.startTime }}</el-text> - </div> - <div> - <el-text type="info" size="small">缁撴潫锛歿{ model.endTime }}</el-text> - </div> - <!-- <template #footer> --> - <el-row justify="end"> - <el-button type="primary" size="small" :loading="false" @click="$emit('start', model)" - >寮�濮嬩换鍔�</el-button - > - <el-button type="danger" size="small" :loading="false" @click="$emit('shutDown', model)" - >寮哄埗缁撴潫</el-button - > - <el-button type="danger" size="small" :loading="false" @click="$emit('remove', model)" - >绉婚櫎浠诲姟</el-button - > - <el-button type="danger" size="small" :loading="false" @click="$emit('gotoResult', model)" - >鏌ョ湅缁撴灉</el-button - > - </el-row> - <!-- </template> --> </el-card> </template> <script> -import { nTlBgTask } from '@/enum/bgTask'; +import { nTlBgTask, BG_TASK_STATUS } from '@/enum/bgTask'; +import { useTimer } from '../../composables/timer'; export default { + setup() { + const { time, startTimer, pauseTimer, stopTimer } = useTimer(); + return { time, startTimer, pauseTimer, stopTimer }; + }, props: { - model: Object + model: Object, + index: Number }, emits: ['start', 'shutDown', 'remove', 'gotoResult'], + data() { + return { + startConfirm: false, + stopConfirm: false, + removeConfirm: false + }; + }, + computed: { + waiting() { + return this.model.status == BG_TASK_STATUS.WAITING.name; + }, + running() { + return this.model.status == BG_TASK_STATUS.RUNNING.name; + }, + success() { + return this.model.status == BG_TASK_STATUS.SUCCESS.name; + }, + fail() { + return this.model.status == BG_TASK_STATUS.FAIL.name; + }, + shutdown() { + return this.model.status == BG_TASK_STATUS.SHUTDOWN.name; + } + }, methods: { nameToLabel(name) { const t = nTlBgTask(name); return t.label; + }, + startTask() { + this.$emit('start', this.index, (res) => { + if (res) { + this.startTimer(); + } + }); + }, + stopTask() { + this.$emit('shutDown', this.index, (res) => { + if (res) { + this.stopTimer(); + } + }); + }, + removeTask() { + this.$emit('remove', this.index, (res) => { + if (res) { + // this.stopTimer(); + } + }); + }, + gotoResult() { + this.$emit('gotoResult', this.index); } } }; </script> <style scoped> -.bg-task-card { - margin-bottom: 8px; +.status-btn { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + border: var(--el-border); + border-radius: var(--el-border-radius-base); +} +.timer { + font-size: 30px; } </style> diff --git a/src/components/button/FYReconfrimButton.vue b/src/components/button/FYReconfrimButton.vue new file mode 100644 index 0000000..6c3f3b4 --- /dev/null +++ b/src/components/button/FYReconfrimButton.vue @@ -0,0 +1,46 @@ +<template> + <el-popover :visible="modelValue" placement="top" :width="160"> + <p>{{ msg }}</p> + <div style="text-align: right; margin: 0"> + <el-button size="small" text @click="handleCancel">鍙栨秷</el-button> + <el-button size="small" type="primary" @click="handleConfirm">纭</el-button> + </div> + <template #reference> + <!-- <slots-events @click="visible = true"> --> + <slot></slot> + <!-- </slots-events> --> + </template> + </el-popover> +</template> +<script> +/** + * 浜屾纭鎸夐挳 + */ +export default { + props: { + modelValue: Boolean, + msg: { + type: String, + default: '纭鎿嶄綔锛�' + } + }, + emits: ['confirm', 'cancel', 'update:modelValue'], + data() { + return { + // visible: false + }; + }, + methods: { + handleConfirm() { + // this.visible = false; + this.$emit('confirm'); + this.$emit('update:modelValue', false); + }, + handleCancel() { + // this.visible = false; + this.$emit('cancel'); + this.$emit('update:modelValue', false); + } + } +}; +</script> diff --git a/src/composables/timer.js b/src/composables/timer.js new file mode 100644 index 0000000..eec906a --- /dev/null +++ b/src/composables/timer.js @@ -0,0 +1,42 @@ +import { ref, computed } from 'vue'; + +/** + * 绉掕〃璁℃椂鍣� + */ +export function useTimer() { + let interval; + const count = ref(0); + const running = ref(false); + const time = computed(() => { + const hour = Math.floor(count.value / 3600); + const min = Math.floor((count.value - hour * 3600) / 60); + const sec = Math.floor(count.value - hour * 3600 - min * 60); + + return `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}:${sec + .toString() + .padStart(2, '0')}`; + }); + + function startTimer() { + if (!running.value) { + interval = setInterval(() => { + count.value++; + }, 1000); + running.value = true + } + } + + function pauseTimer() { + if (interval) { + clearInterval(interval); + } + running.value = false + } + + function stopTimer() { + pauseTimer(); + count.value = 0; + } + + return { time, startTimer, pauseTimer, stopTimer, running }; +} diff --git a/src/main.js b/src/main.js index 96be02a..c56a19a 100644 --- a/src/main.js +++ b/src/main.js @@ -4,18 +4,20 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'; import { router } from './router'; import App from './App.vue'; - +import timeUtil from './utils/time-util'; // import 'element-plus/dist/index.css'; -import './assets/main.css' +import './assets/main.css'; -import "element-plus/theme-chalk/src/overlay.scss" -import "element-plus/theme-chalk/src/message.scss" -import "element-plus/theme-chalk/src/message-box.scss" -import "element-plus/theme-chalk/src/notification.scss" +import 'element-plus/theme-chalk/src/overlay.scss'; +import 'element-plus/theme-chalk/src/message.scss'; +import 'element-plus/theme-chalk/src/message-box.scss'; +import 'element-plus/theme-chalk/src/notification.scss'; const app = createApp(App); +app.config.globalProperties.$fm = timeUtil; + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component); } diff --git a/src/styles/element/base.scss b/src/styles/element/base.scss new file mode 100644 index 0000000..209bbe6 --- /dev/null +++ b/src/styles/element/base.scss @@ -0,0 +1,78 @@ +// +@mixin font-size($size) { + font-size: var($size); +} + +.f-s { + @include font-size(--el-font-size-small); +} + +.f-b { + @include font-size(--el-font-size-base); +} + +.f-m { + @include font-size(--el-font-size-medium); +} + +.f-l { + @include font-size(--el-font-size-large); +} + +@mixin color($value) { + color: var($value); +} + +.color-p { + @include color(--el-color-primary); +} + +.color-s { + @include color(--el-color-success); +} + +.color-w { + @include color(--el-color-warning); +} + +.color-d { + @include color(--el-color-danger); +} + +.color-e { + @include color(--el-color-error); +} + +.color-i { + @include color(--el-color-info); +} + +.b-color-aqua { + background-color: aqua; +} + +// +.p-l-8 { + padding-left: 8px; +} + +.p-v-8 { + padding: 8px 0; +} + +.m-b-8 { + margin-bottom: 8px; +} + +.m-l-4 { + margin-left: 4px; +} + +.w-150px { + width: 290px; +} + +// +.cursor-p { + cursor: pointer; +} diff --git a/src/styles/element/index.scss b/src/styles/element/index.scss index ae918df..e4b2eba 100644 --- a/src/styles/element/index.scss +++ b/src/styles/element/index.scss @@ -27,9 +27,9 @@ // You should use them in scss, because we calculate it by sass. // comment next lines to use default color // @forward "element-plus/theme-chalk/src/common/var.scss" with ( - // do not use same name, it will override. - // $colors: $--colors, - // $button-padding-horizontal: ("default": 50px) +// do not use same name, it will override. +// $colors: $--colors, +// $button-padding-horizontal: ("default": 50px) // ); // if you want to import all @@ -39,5 +39,6 @@ // @debug $--colors; // custom dark variables -@use "./dark.scss"; -@use "./animation.scss" +@use './dark.scss'; +@use './animation.scss'; +@use './base.scss'; \ No newline at end of file diff --git a/src/utils/time-util.js b/src/utils/time-util.js new file mode 100644 index 0000000..9f62dcb --- /dev/null +++ b/src/utils/time-util.js @@ -0,0 +1,31 @@ +import dayjs from "dayjs"; + +export default { + format(date, template) { + return dayjs(date).format(template) + }, + + formatH(date){ + if (date) { + return this.format(date, 'HH:mm:ss') + } else { + return '--:--:--' + } + }, + + formatYMD(date){ + if (date) { + return this.format(date, 'YYYY-MM-DD') + } else { + return '----/--/--' + } + }, + + formatYMDH(date){ + if (date) { + return this.format(date, 'YYYY-MM-DD HH:mm:ss') + } else { + return '----/--/-- --:--:--' + } + }, +} \ No newline at end of file diff --git a/src/views/fysp/evaluation/components/CompEvaTask.vue b/src/views/fysp/evaluation/components/CompEvaTask.vue index 57299b1..27bcec4 100644 --- a/src/views/fysp/evaluation/components/CompEvaTask.vue +++ b/src/views/fysp/evaluation/components/CompEvaTask.vue @@ -29,6 +29,7 @@ <template v-for="(v, i) in taskList" :key="i"> <FYBgTaskCard :model="v" + :index="i" @start="startTask" @shutDown="shutDownTask" @remove="removeTask" @@ -58,6 +59,21 @@ }; }, methods: { + newTestTask() { + this.fetchData((page, pageSize) => { + return bgtaskApi.newTestTask(`Test-Task-${++this.taskIndex}`).then((res) => { + this.taskList.push(res.data); + }); + }); + }, + startNewTestTask() { + this.fetchData((page, pageSize) => { + return bgtaskApi.startNewTestTask(`Test-Task-${++this.taskIndex}`).then((res) => { + this.taskList.push(res.data); + }); + }); + }, + _getParam(taskStatus) { return { type: taskStatus.type, @@ -75,47 +91,47 @@ }); }); }, - startTask(taskStatus) { + startTask(index, callback) { this.fetchData((page, pageSize) => { - return bgtaskApi.startTask(this._getParam(taskStatus)).then((res) => { - this.taskList = this.taskList.concat(res.data); + const param = this._getParam(this.taskList[index]); + return bgtaskApi.startTask(param).then((res) => { + this.taskList[index] = res.data; + callback(true); }); }); }, - newTestTask() { + shutDownTask(index, callback) { this.fetchData((page, pageSize) => { - return bgtaskApi.newTestTask(`Test-Task-${++this.taskIndex}`).then((res) => { - this.taskList = this.taskList.concat(res.data); - }); - }); - }, - startNewTestTask() { - this.fetchData((page, pageSize) => { - return bgtaskApi.startNewTestTask(`Test-Task-${++this.taskIndex}`).then((res) => { - this.taskList = this.taskList.concat(res.data); - }); - }); - }, - shutDownTask(taskStatus) { - this.fetchData((page, pageSize) => { - return bgtaskApi.shutDownTask(this._getParam(taskStatus)).then((res) => { - res.data.forEach((e) => { - let v = this.taskList.find((value) => { - return value.id == e.id; + const param = this._getParam(this.taskList[index]); + return bgtaskApi.shutDownTask(param).then((res) => { + if (index && res.data && res.data.length == 1) { + this.taskList[index] = res.data[0]; + } else { + res.data.forEach((e) => { + let v = this.taskList.find((value) => { + return value.id == e.id; + }); + const i = this.taskList.indexOf(v); + this.taskList[i] = e; }); - const i = this.taskList.indexOf(v); - this.taskList[i] = e; - }); + } + callback(true); }); }); }, - removeTask(taskStatus){ - + removeTask(index, callback) { + this.fetchData((page, pageSize) => { + const param = this._getParam(this.taskList[index]); + return bgtaskApi.removeTask(param).then((res) => { + if (res.data) { + this.taskList.splice(index, 1); + callback(true); + } + }); + }); }, - gotoResult(taskStatus) { - - } - }, + gotoResult(index) {} + } }; </script> <style scoped> diff --git a/src/views/fysp/evaluation/components/precheck/CompCheckSource.vue b/src/views/fysp/evaluation/components/precheck/CompCheckSource.vue index 1ddbc54..2e4c294 100644 --- a/src/views/fysp/evaluation/components/precheck/CompCheckSource.vue +++ b/src/views/fysp/evaluation/components/precheck/CompCheckSource.vue @@ -33,7 +33,7 @@ <el-col :span="5"> <el-button v-if="!v.pass" type="primary" size="small" @click="goto(v.path)"> 鍘诲畬鍠� - <el-icon style="margin-left: 4px"><Right /></el-icon> + <el-icon class="m-l-4"><Right /></el-icon> </el-button> </el-col> </el-form-item> diff --git a/vite.config.js b/vite.config.js index 7a0cca6..121fef6 100644 --- a/vite.config.js +++ b/vite.config.js @@ -20,7 +20,7 @@ // allow auto load markdown components under `./src/components/` extensions: ['vue', 'md'], // allow auto import and register components used in markdown - include: [/\.vue$/, /\.vue\?vue/, /\.md$/], + include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.ts$/], resolvers: [ ElementPlusResolver({ importStyle: 'sass' -- Gitblit v1.9.3