Bläddra i källkod

增加调度任务页面
优化货位选择查询

wangzhengguang 3 månader sedan
förälder
incheckning
69c7c1d3ac
4 ändrade filer med 476 tillägg och 2 borttagningar
  1. 12 2
      src/components/PositionSelector.vue
  2. 447 0
      src/components/TransferTask.vue
  3. 14 0
      src/menu/AppMenus.vue
  4. 3 0
      src/router/router.js

+ 12 - 2
src/components/PositionSelector.vue

@@ -7,7 +7,7 @@
         <div style="font-size: 16px; font-weight: 500;">{{ title }}</div>
         <van-icon name="cross" @click="close" />
       </div>
-      <van-search v-model="searchKey" :placeholder="searchPlaceholder" @search="onSearch" />
+      <van-search v-model="searchKey" :placeholder="searchPlaceholder" @search="onSearch" @input="onInputChange" />
       <div style="flex: 1; overflow-y: auto;">
         <van-radio-group v-model="selectedId">
           <van-cell-group>
@@ -167,6 +167,16 @@ const onSearch = () => {
   getPositionData(true);
 };
 
+let timer = null;
+
+const onInputChange = val => {
+  // 防抖处理
+  clearTimeout(timer);
+  timer = setTimeout(() => {
+    onSearch(val);
+  }, 1000); // 延迟 300ms 后搜索
+};
+
 // 加载更多
 const loadMore = () => {
   if (loading.value || noMore.value) return;
@@ -294,4 +304,4 @@ defineExpose({
 :deep(.van-button) {
   margin-bottom: 8px;
 }
-</style>
+</style>

+ 447 - 0
src/components/TransferTask.vue

@@ -0,0 +1,447 @@
+<template>
+  <van-nav-bar title="调度任务" left-text="返回" right-text="刷新" @click-left="goBack" @click-right="refresh" />
+  <div class="content">
+    <van-form @submit="searchDatas">
+      <van-field
+        v-model="formDataSelect.workCategory"
+        label="任务类型"
+        placeholder="请选择任务类型"
+        is-link
+        readonly
+        @click="showWorkTypePicker = true"
+      />
+      <van-field
+        v-model="formData.positionBeginNo"
+        label="起始货位"
+        placeholder="请输入起始货位名称"
+        @keyup.enter="searchDatas"
+      />
+      <van-field
+        v-model="formData.positionEndNo"
+        label="终点货位"
+        placeholder="请输入终点货位名称"
+        @keyup.enter="searchDatas"
+      />
+      <van-field
+        v-model="formDataSelect.executeStatus"
+        label="执行状态"
+        placeholder="请选择执行状态"
+        is-link
+        readonly
+        @click="showExecuteStatusPicker = true"
+      />
+      <van-field
+        v-model="formDataSelect.success"
+        label="是否成功"
+        placeholder="请选择是否成功"
+        is-link
+        readonly
+        @click="showSuccessPicker = true"
+      />
+      <div class="form-buttons">
+        <van-button type="default" block @click="clearFilter">清空</van-button>
+        <van-button type="primary" block @click="searchDatas">查询</van-button>
+      </div>
+    </van-form>
+
+    <van-list
+      v-model:loading="loading"
+      :finished="finished"
+      :finished-text="'没有更多了'"
+      @load="loadMore"
+    >
+      <van-cell-group v-for="record in taskList" :key="record.schedulingTasksId" class="task-item">
+        <van-cell title="单据编号" :value="record.documentNo" />
+        <van-cell title="任务类型" :value="record.workCategory" />
+        <van-cell title="起始货位" :value="record.positionBeginName" />
+        <van-cell title="终点货位" :value="record.positionEndName" />
+        <van-cell title="执行状态" :value="record.executeStatus" />
+        <van-cell title="是否成功">
+          <template #default>
+            <van-tag v-if="record.success === true" color="#07c160">成功</van-tag>
+            <van-tag v-if="record.executeStatus === '未开始'" color="#07c160">未开始</van-tag>
+            <van-tag v-else color="#ff4d4f">失败</van-tag>
+          </template>
+        </van-cell>
+        <div class="task-operations">
+          <van-tag v-if="record.executeStatus === '已完成'" color="#07c160">已完成</van-tag>
+          <van-button v-if="record.executeStatus === '执行中'" type="danger" size="small" @click="cancelTask(record)">取消任务</van-button>
+          <van-button v-if="record.executeStatus === '未开始'" type="primary" size="small" @click="executeTask(record)">执行</van-button>
+        </div>
+      </van-cell-group>
+    </van-list>
+  </div>
+
+  <!-- 选择器弹窗 -->
+  <van-popup v-model:show="showWorkTypePicker" round position="bottom">
+    <van-picker
+      :columns="workTypeColumns"
+      @confirm="(value) => handleWorkTypeConfirm(value)"
+      @cancel="showWorkTypePicker = false"
+    />
+  </van-popup>
+  <van-popup v-model:show="showExecuteStatusPicker" round position="bottom">
+    <van-picker
+      :columns="executeStatusColumns"
+      @confirm="(value) => handleExecuteStatusConfirm(value)"
+      @cancel="showExecuteStatusPicker = false"
+    />
+  </van-popup>
+  <van-popup v-model:show="showSuccessPicker" round position="bottom">
+    <van-picker
+      :columns="successColumns"
+      @confirm="(value) => handleSuccessConfirm(value)"
+      @cancel="showSuccessPicker = false"
+    />
+  </van-popup>
+
+  <!-- 添加全局加载状态 -->
+  <van-loading v-if="executing" class="global-loading" type="spinner">执行中...</van-loading>
+  <van-overlay :show="executing" />
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useRouter } from 'vue-router';
+import { showToast,Loading } from 'vant';
+import { processException } from '../common/Common.js';
+import { ajaxApiGet, ajaxApiPost } from '../common/utils.js';
+
+const router = useRouter();
+
+const loading = ref(false);
+const finished = ref(false);
+const executing = ref(false); // 执行状态
+
+const taskList = ref([]);
+
+const formData = ref({
+  workCategory: null,
+  positionBeginNo: null,
+  positionEndNo: null,
+  executeStatus: null,
+  agvNo: null,
+  success: null,
+});
+
+const formDataSelect = ref({
+  workCategory: null,
+  positionBeginNo: null,
+  positionEndNo: null,
+  executeStatus: null,
+  agvNo: null,
+  success: null,
+});
+
+const totalSize = ref(0);
+const pagination = ref({
+  start: 1,
+  length: 10,
+});
+
+// 选择器状态
+const showWorkTypePicker = ref(false);
+const showExecuteStatusPicker = ref(false);
+const showSuccessPicker = ref(false);
+
+// 选择器列数据
+const workTypeColumns = ref([
+  { text: '入库', value: 'STOCKIN' },
+  { text: '出库', value: 'STOCKOUT' },
+  { text: '调拨', value: 'ADJUST' },
+  { text: '出库返回', value: 'STOCKOUTRETURN' },
+  { text: '全部', value: '' },
+]);
+
+const executeStatusColumns = ref([
+  { text: '全部', value: '' },
+  { text: '未开始', value: 'NotStarted' },
+  { text: '执行中', value: 'Executing' },
+  { text: '已完成', value: 'Completed' },
+]);
+
+const successColumns = ref([
+  { text: '全部', value: '' },
+  { text: '成功', value: true },
+  { text: '失败', value: false },
+]);
+
+// 返回
+const goBack = () => {
+  router.back();
+};
+
+// 刷新
+const refresh = () => {
+  pagination.value.start = 1;
+  taskList.value = [];
+  finished.value = false;
+  getTableDatas();
+};
+
+// 执行任务
+const executeTask = async record => {
+  executing.value = true;
+  try {
+    await executeTaskById(record.schedulingTasksId);
+    await searchDatas();
+    showToast({
+      type: 'success',
+      message: '执行成功!',
+      duration: 2000,
+    });
+  } catch (error) {
+    console.log('error',error);
+    showToast({
+      type: 'warning',
+      message: error,
+      duration: 3000,
+    });
+  } finally {
+    executing.value = false;
+  }
+};
+
+// 取消任务
+const cancelTask = async record => {
+  executing.value = true;
+  try {
+    await cancelTaskById(record.schedulingTasksId);
+    await searchDatas();
+    showToast({
+      type: 'success',
+      message: '取消成功!',
+      duration: 2000,
+    });
+  } catch (error) {
+    console.log('error',error);
+    showToast({
+      type: 'warning',
+      message: error,
+      duration: 3000,
+    });
+  } finally {
+    executing.value = false;
+  }
+};
+
+const isManualSearch = ref(false);
+
+// 条件变化查询
+const searchDatas = () => {
+  pagination.value.start = 1;
+  taskList.value = [];
+  finished.value = false;
+  // 添加一个标志防止自动加载
+  isManualSearch.value = true;
+  getTableDatas().finally(() => {
+    isManualSearch.value = false;
+  });
+};
+
+// 加载更多
+const loadMore = () => {
+  if (isManualSearch.value) return;
+  if (taskList.value.length >= totalSize.value) {
+    finished.value = true;
+    return;
+  }
+  pagination.value.start++;
+  getTableDatas();
+};
+
+// 清空条件
+const clearFilter = () => {
+  formData.value = {
+    workCategory: null,
+    positionBeginNo: null,
+    positionEndNo: null,
+    executeStatus: null,
+    agvNo: null,
+    success: null,
+  };
+  formDataSelect.value = {
+    workCategory: null,
+    positionBeginNo: null,
+    positionEndNo: null,
+    executeStatus: null,
+    agvNo: null,
+    success: null,
+  };
+  searchDatas();
+};
+
+// 处理任务类型选择
+const handleWorkTypeConfirm = value => {
+  formData.value.workCategory = value.selectedOptions[0].value;
+  formDataSelect.value.workCategory = value.selectedOptions[0].text;
+  showWorkTypePicker.value = false;
+  searchDatas();
+};
+
+// 处理执行状态选择
+const handleExecuteStatusConfirm = value => {
+  formData.value.executeStatus = value.selectedOptions[0].value;
+  formDataSelect.value.executeStatus = value.selectedOptions[0].text;
+  showExecuteStatusPicker.value = false;
+  searchDatas();
+};
+
+// 处理是否成功选择
+const handleSuccessConfirm = value => {
+  formData.value.success = value.selectedOptions[0].value;
+  formDataSelect.value.success = value.selectedOptions[0].text;
+  showSuccessPicker.value = false;
+  searchDatas();
+};
+
+onMounted(() => {
+  getTableDatas();
+});
+
+// 查询调度任务
+const getTableDatas = () => {
+  const start = (pagination.value.start - 1) * pagination.value.length;
+  const params = {
+    ...formData.value,
+    ...pagination.value,
+    start,
+  };
+  loading.value = true;
+  const url = '/api/SchedulingTasksResource/querySchedulingTasks';
+
+  // ✅ 返回 ajaxApiPost 的 Promise
+  return ajaxApiPost(url, params).then(
+    success => {
+      const { errorCode, errorMessage, datas, total } = success;
+      if (errorCode === 0) {
+        if (datas && datas.length > 0) {
+          if (pagination.value.start === 1) {
+            taskList.value = datas;
+          } else {
+            taskList.value = [...taskList.value, ...datas];
+          }
+          totalSize.value = total;
+        } else {
+          if (pagination.value.start === 1) {
+            taskList.value = [];
+          }
+          totalSize.value = 0;
+        }
+      } else {
+        if (pagination.value.start === 1) {
+          taskList.value = [];
+        }
+        totalSize.value = 0;
+        showToast({
+          type: 'warning',
+          message: errorMessage,
+          duration: 3000,
+        });
+      }
+      loading.value = false;
+    },
+    error => {
+      processException(error);
+      loading.value = false;
+    },
+  );
+};
+
+// 执行任务Api
+const executeTaskById = id => {
+  const url = `/api/SchedulingTasksResource/executeTaskById?schedulingTasksId=${id}`;
+  return ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage } = success;
+      if (errorCode !== 0) {
+        return Promise.reject(errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+      return Promise.reject(error);
+    },
+  );
+};
+
+// 取消任务Api
+const cancelTaskById = id => {
+  const url = `/api/SchedulingTasksResource/cancelWorkById?schedulingTasksId=${id}`;
+  return ajaxApiGet(url).then(
+    success => {
+      const { errorCode, errorMessage } = success;
+      if (errorCode !== 0) {
+        return Promise.reject(errorMessage);
+      }
+    },
+    error => {
+      processException(error);
+      return Promise.reject(error);
+    },
+  );
+};
+</script>
+
+<style scoped>
+.content {
+  padding: 16px;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+}
+
+.form-buttons {
+  display: flex;
+  gap: 12px;
+  margin-top: 16px;
+}
+
+.task-item {
+  margin-top: 16px;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.task-operations {
+  display: flex;
+  gap: 8px;
+  padding: 12px 16px;
+  background-color: #f9f9f9;
+  border-top: 1px solid #f0f0f0;
+  justify-content: flex-end;
+}
+
+:deep(.van-cell) {
+  font-size: 14px;
+}
+
+:deep(.van-cell__title) {
+  font-weight: 500;
+  color: #333;
+}
+
+:deep(.van-cell__value) {
+  color: #666;
+}
+
+:deep(.van-form-item) {
+  margin-bottom: 12px;
+}
+
+/* 全局加载样式 */
+.global-loading {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 2001;
+  padding: 10px 20px;
+  background: rgba(0, 0, 0, 0.7);
+  color: white;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+</style>

+ 14 - 0
src/menu/AppMenus.vue

@@ -42,6 +42,16 @@
           <p class="menu-desc">管理待返回出库任务</p>
         </template>
       </van-grid-item>
+
+      <van-grid-item class="menu-item" @click="toTask('transfer-task')">
+        <template #icon>
+          <van-icon name="newspaper-o" class="menu-icon" />
+        </template>
+        <template #text>
+          <span class="menu-title">调度任务</span>
+          <p class="menu-desc">管理调度任务</p>
+        </template>
+      </van-grid-item>
     </van-grid>
   </div>
 </template>
@@ -60,6 +70,10 @@ const toPage = page => {
   router.push('/warehouse-selector?page=' + page);
 };
 
+const toTask = page => {
+  router.push('/' + page);
+};
+
 </script>
 
 <style scoped>

+ 3 - 0
src/router/router.js

@@ -39,6 +39,8 @@ const StockOutScan = () => import('../components/StockOutScan.vue')
 // 调拨扫描
 const StockTransferScan = () => import('../components/StockTransferScan.vue')
 
+// 调拨任务
+const TransferTask = () => import('../components/TransferTask.vue')
 
 
 export default [
@@ -56,4 +58,5 @@ export default [
   { path: '/stock-in-scan', component: StockInScan, meta: { requiresAuth: true }, },
   { path: '/stock-out-scan', component: StockOutScan, meta: { requiresAuth: true }, },
   { path: '/stock-transfer-scan', component: StockTransferScan, meta: { requiresAuth: true }, },
+  { path: '/transfer-task', component: TransferTask, meta: { requiresAuth: true }, },
 ];