riku
2023-12-27 bb7ae31d7066a838bd177bf21c20f13ef044950b
编写后台任务模块逻辑,完成80%
已修改10个文件
已添加5个文件
609 ■■■■ 文件已修改
src/App.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fysp/bgtaskApi.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/base.css 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SlotEvents.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/bg-task/FYBgTaskCard.vue 200 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/button/FYReconfrimButton.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/composables/timer.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/element/base.scss 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/element/index.scss 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/time-util.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/evaluation/components/CompEvaTask.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/evaluation/components/precheck/CompCheckSource.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 {
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);
  }
};
src/assets/base.css
@@ -67,4 +67,6 @@
body {
  /* background-color: rebeccapurple; */
  margin: 0;
}
  /* min-width: 1440px; */
  /* min-height: 820px; */
}
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']
src/components/SlotEvents.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
import { cloneVNode, Comment, defineComponent, Fragment, h, Text, VNode } from 'vue';
/**
 * å¯¹äºŽæ–‡æœ¬æˆ–SVG,需要用span包裹一下
 * @param s
 */
const wrapTextContent = (s: string | VNode): VNode => {
  return h('span', null, s);
};
/**
 * åˆ¤æ–­æ˜¯å¦ä¸ºObject
 * @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;
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>
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>
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 };
}
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);
}
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;
}
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';
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 '----/--/-- --:--:--'
    }
  },
}
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>
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>
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'