riku
2024-10-10 46566d05cb156d40323078133191595d2a2f11c4
巡查任务管理界面开发中
已修改6个文件
已添加5个文件
676 ■■■■■ 文件已修改
src/api/fysp/taskApi.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/list-item/ItemMonitorObj.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/list-item/ItemScene.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/search-option/FYInfoSearch.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/menu.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/element/base.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/task/MonitorObjEdit.vue 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/task/TaskManage.vue 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/task/components/CompMonitorObj.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fysp/taskApi.js
@@ -9,6 +9,13 @@
  },
  /**
   * èŽ·å–æ€»ä»»åŠ¡çš„ç›‘ç®¡åœºæ™¯ç‰ˆæœ¬ä¿¡æ¯
   */
  fetchMonitorObjectVersion(taskId) {
    return $fysp.get(`monitorobjectversion/task/${taskId}`).then((res) => res.data);
  },
  /**
   * æŸ¥è¯¢æ€»ä»»åŠ¡
   * @param {Object} param
   * @returns
src/components.d.ts
@@ -9,8 +9,10 @@
  export interface GlobalComponents {
    BaseContentLayout: typeof import('./components/core/BaseContentLayout.vue')['default']
    BasePanelLayout: typeof import('./components/core/BasePanelLayout.vue')['default']
    CompInfoSearch: typeof import('./components/search-option/CompInfoSearch.vue')['default']
    CompQuickSet: typeof import('./components/search-option/CompQuickSet.vue')['default']
    Content: typeof import('./components/core/Content.vue')['default']
    copy: typeof import('./components/list-item/ItemScene copy.vue')['default']
    ElAside: typeof import('element-plus/es')['ElAside']
    ElAvatar: typeof import('element-plus/es')['ElAvatar']
    ElBacktop: typeof import('element-plus/es')['ElBacktop']
@@ -18,6 +20,7 @@
    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
@@ -65,12 +68,14 @@
    ElText: typeof import('element-plus/es')['ElText']
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
    ElTree: typeof import('element-plus/es')['ElTree']
    ElUpload: typeof import('element-plus/es')['ElUpload']
    Footer: typeof import('./components/core/Footer.vue')['default']
    FormCol: typeof import('./components/layout/FormCol.vue')['default']
    FYBgTaskCard: typeof import('./components/bg-task/FYBgTaskCard.vue')['default']
    FYBgTaskDialog: typeof import('./components/bg-task/FYBgTaskDialog.vue')['default']
    FYBgTaskItem: typeof import('./components/bg-task/FYBgTaskItem.vue')['default']
    FYForm: typeof import('./components/form/FYForm.vue')['default']
    FYInfoSearch: typeof import('./components/search-option/FYInfoSearch.vue')['default']
    FYList: typeof import('./components/table/FYList.vue')['default']
    FYOptionLocation: typeof import('./components/search-option/FYOptionLocation.vue')['default']
    FYOptionOnlineStatus: typeof import('./components/search-option/FYOptionOnlineStatus.vue')['default']
@@ -83,6 +88,7 @@
    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']
    ItemMonitorObj: typeof import('./components/list-item/ItemMonitorObj.vue')['default']
    ItemScene: typeof import('./components/list-item/ItemScene.vue')['default']
    ItemUser: typeof import('./components/list-item/ItemUser.vue')['default']
    MenuItems: typeof import('./components/core/MenuItems.vue')['default']
src/components/list-item/ItemMonitorObj.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
<template>
  <!-- <el-card shadow="hover"> -->
  <div class="wrapper">
    <div>
      <el-text>{{ item.displayid }}、</el-text>
      <el-text truncated class="w-250px">{{ item.sensename }}</el-text>
    </div>
    <!-- <div>
      <el-text>地址:{{ item.location }}</el-text>
    </div> -->
    <el-row justify="space-between" style="margin-top: 4px">
      <el-space>
        <el-tag type="info" effect="plain" size="small">
          {{ item.sceneType }}
        </el-tag>
        <el-tag type="info" effect="plain" size="small">
          è®¡åˆ’监管次数:{{ item.monitornum }}
        </el-tag>
      </el-space>
      <slot :item="item"></slot>
      <!-- <el-button size="small" type="success" @click="add">添加</el-button> -->
    </el-row>
  </div>
  <!-- </el-card> -->
</template>
<script setup>
/**
 * ç›‘管对象
 */
const props = defineProps({
  item: {
    type: Object,
    default: () => {}
  }
});
const emit = defineEmits(['add']);
function add() {
  emit('add', props.item);
}
</script>
<style scoped>
.wrapper {
  width: 300px;
  border: 1px solid var(--el-border-color);
  border-radius: var(--el-border-radius-base);
  padding: 4px 8px;
}
</style>
src/components/list-item/ItemScene.vue
@@ -10,14 +10,19 @@
    </div> -->
    <el-row justify="space-between" style="margin-top: 4px">
      <el-space>
        <el-tag type="primary" effect="plain" size="small">
        <el-tag type="info" effect="plain" size="small">
          {{ item.districtname }}
        </el-tag>
        <el-tag type="primary" effect="plain" size="small">
        <el-tag type="info" effect="plain" size="small">
          {{ item.type }}
        </el-tag>
        <el-tag :type="item.extension1 == '0' ? 'info' : 'success'" size="small">
          {{ onlineFormat(item.extension1) }}
        </el-tag>
      </el-space>
      <slot>
      <el-button size="small" type="success" @click="add">添加</el-button>
      </slot>
    </el-row>
  </div>
  <!-- </el-card> -->
@@ -35,6 +40,14 @@
function add() {
  emit('add', props.item);
}
function onlineFormat(s) {
  if (s == '0') {
    return '下线';
  } else {
    return '上线';
  }
}
</script>
<style scoped>
.wrapper {
src/components/search-option/FYInfoSearch.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,126 @@
<template>
  <FYSearchBar @search="search" :loading="loading">
    <template #options>
      <FYOptionText v-bind="$attrs" v-model:value="searchText" width="200px"></FYOptionText>
    </template>
  </FYSearchBar>
  <div>
    <el-scrollbar v-if="data.length > 0" :height="scrollHeight" class="item-box" :loading="loading">
      <el-space direction="vertical" alignment="start" fill style="width: 100%">
        <div v-for="(item, index) in data" :key="index">
          <slot :row="item"></slot>
        </div>
      </el-space>
    </el-scrollbar>
    <el-empty v-else description="暂无记录" />
  </div>
  <el-pagination
    v-if="pageShow && data.length > 0"
    size="small"
    ref="paginationRef"
    class="el-pagination"
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :page-sizes="pageSizes"
    :background="true"
    layout="total, sizes, prev, pager, next"
    :total="total"
  />
</template>
<script>
import { usePagination } from '@/composables/pagination';
/**
 * å¸¦æœ‰åˆ†é¡µé€»è¾‘的信息检索列表
 */
export default {
  setup() {
    // åˆ†é¡µé€»è¾‘
    const { currentPage, pageSize, addPageEvent } = usePagination();
    return { currentPage, pageSize, addPageEvent };
  },
  props: {
    // label: {
    //   type: String,
    //   default: '检索项'
    // },
    // placeholder: {
    //   type: String,
    //   default: '输入关键字检索'
    // },
    // æ•°æ®åˆ—表
    data: {
      type: Array,
      default: () => {
        return [];
      }
    },
    // æŸ¥è¯¢å‡½æ•°
    onSearch: {
      type: Function,
      default: () => {}
    },
    // æ˜¯å¦æ˜¾ç¤ºåˆ†é¡µ
    pageShow: {
      type: Boolean,
      default: true
    },
    // æ¯é¡µå¯é€‰æ•°é‡
    pageSizes: {
      type: Array,
      default: () => {
        return [10, 20, 50, 100];
      }
    },
    // æ€»æ•°æ®é‡
    total: {
      type: Number,
      default: 0
    },
    scrollHeight: {
      type: String,
      default: '38vh'
    }
  },
  data() {
    return {
      searchText: '',
      loading: false
    };
  },
  watch: {},
  methods: {
    async search() {
      this.loading = true;
      const param = {
        text: this.searchText,
        page: this.currentPage,
        pageSize: this.pageSize
      };
      await this.onSearch(param);
      this.loading = false;
    }
  },
  mounted() {
    this.addPageEvent(this.search);
  }
};
</script>
<style scoped>
.select-box {
  border: 1px solid var(--el-border-color);
  border-radius: var(--el-border-radius-base);
  padding: 0 8px;
}
.item-box {
  /* border: 1px solid var(--el-border-color);
  border-radius: var(--el-border-radius-base);
  margin-top: 20px; */
}
.el-pagination {
  /* background-color: var(--el-color-white); */
  border-top: 1px solid rgba(0, 0, 0, 0.096);
  /* background-color: aliceblue; */
}
</style>
src/constants/menu.js
@@ -15,6 +15,11 @@
  //   ]
  // },
  {
    path: '/fysp/task/manage',
    icon: 'CircleCheck',
    name: '监管任务'
  },
  {
    path: '/fysp/procheck',
    icon: 'CircleCheck',
    name: '问题审核'
src/router/index.js
@@ -60,6 +60,19 @@
    component: () => import('@/views/fysp/scenereport/StorageReport.vue')
  },
  {
    //监管任务
    name: 'taskmanage',
    path: '/fysp/task/manage',
    component: () => import('@/views/fysp/task/TaskManage.vue'),
    meta: { keepAlive: false }
  },
  {
    //监管任务场景编辑
    name: 'monitorObjEdit',
    path: '/fysp/task/edit',
    component: () => import('@/views/fysp/task/MonitorObjEdit.vue')
  },
  {
    //问题审核
    name: 'procheck',
    path: '/fysp/procheck',
src/styles/element/base.scss
@@ -71,7 +71,7 @@
  default: var(--el-component-size-default),
  large: var(--el-component-size-large)
);
$ws: (20px, 40px, 60px, 100px, 150px, 300px);
$ws: (20px, 40px, 60px, 100px, 150px, 250px, 300px);
@each $name, $value in $csize {
  .w-#{$name} {
    width: #{$value};
src/views/fysp/task/MonitorObjEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,175 @@
<template>
  <el-page-header @back="$router.back()" class="page-header">
    <template #content>
      <span> æ€»ä»»åŠ¡ç¼–è¾‘ </span>
    </template>
  </el-page-header>
  <el-divider />
  <el-row gutter="20">
    <el-col :span="16">
      <div>
        <el-text>已选场景</el-text>
      </div>
      <el-divider />
      <CompMonitorObj :data="curMonitorObjList" @tab-change="changeSceneType" :showDelete="true">
        <!-- <template #default="{ item }">
          <el-button size="small" type="danger" @click="deleteMov(item)">移除</el-button>
        </template> -->
      </CompMonitorObj>
    </el-col>
    <el-col :span="8">
      <div>
        <el-text>可选场景</el-text>
      </div>
      <el-divider />
      <FYInfoSearch
        label=""
        placeholder="请输入场景名称关键字"
        :data="showSceneList"
        :on-search="searchScene"
        :total="total"
        scroll-height="70vh"
        :page-show="false"
      >
        <template #default="{ row, click }">
          <ItemScene :item="row">
            <el-button-group>
              <el-button size="small" type="primary" @click="insertDialog = true">插入</el-button>
              <el-button size="small" type="primary" @click="addDialog = true">新增</el-button>
            </el-button-group>
          </ItemScene>
        </template>
      </FYInfoSearch>
    </el-col>
  </el-row>
  <el-dialog v-model="insertDialog" title="插入场景至空余编号" width="500">
    <div>以下为可选的空余编号</div>
    <el-radio-group v-model="selectedIndex" size="default">
      <el-radio-button v-for="item in valibleIndex" :key="item" :label="item" :value="item" />
    </el-radio-group>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="insertDialog = false">取消</el-button>
        <el-button type="primary" @click="insertDialog = false"> ç¡®è®¤ </el-button>
      </div>
    </template>
  </el-dialog>
  <el-dialog v-model="addDialog" title="新增场景编号顺延" width="500">
    <div>顺延编号为:{{ lastIndex }}</div>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="addDialog = false">取消</el-button>
        <el-button type="primary" @click="addDialog = false"> ç¡®è®¤ </el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script>
import CompMonitorObj from './components/CompMonitorObj.vue';
import svUserApi from '@/api/fysp/userApi';
import sceneApi from '@/api/fysp/sceneApi';
export default {
  components: { CompMonitorObj },
  props: {},
  data() {
    return {
      // ç›‘管场景
      curMonitorObjList: [],
      // è¡Œæ”¿åŒºåˆ’
      area: {},
      // æ‰€æœ‰åœºæ™¯
      sceneList: [],
      total: 0,
      // å½“前筛选的场景类型
      curSceneType: undefined,
      insertDialog: false,
      selectedIndex: undefined,
      addDialog: false
    };
  },
  computed: {
    // å½“前场景类型下的展示场景
    showSceneList() {
      return this.sceneList.filter((v) => {
        const index = this.curMonitorObjList.findIndex((o) => {
          return o.sguid == v.guid;
        });
        return index == -1 && v.type == this.curSceneType;
      });
    },
    showMonitorObjList() {
      return this.curMonitorObjList.filter((v) => {
        return v.sceneType == this.curSceneType;
      });
    },
    // å½“前场景类型下的可插入编号
    valibleIndex() {
      // åŽŸåˆ—è¡¨å·²ç»æŒ‰ç…§ç¼–å·é¡ºåºæŽ’åˆ—
      let index = 1;
      const indexList = [];
      this.showMonitorObjList.forEach((l) => {
        while (l.displayid > index) {
          indexList.push(index);
          index++;
        }
        index++;
      });
      return indexList;
    },
    lastIndex() {
      const len = this.showMonitorObjList.length;
      if (len > 0) {
        return this.showMonitorObjList[len - 1].displayid + 1;
      } else {
        return undefined;
      }
    }
  },
  methods: {
    // æŸ¥è¯¢
    searchScene({ text, page, pageSize }) {
      this.area.sceneName = text;
      return sceneApi.searchScene(this.area, 1, 10000).then((res) => {
        if (res.success) {
          // æŸ¥è¯¢ç»“æžœ
          this.sceneList = res.data;
          // æ€»æ•°æ®é‡
          this.total = res.head.totalCount;
        }
      });
    },
    changeSceneType(tabName) {
      this.curSceneType = tabName;
    },
    deleteMov(item) {
    },
    insertMov() {},
    addMov() {}
  },
  mounted() {
    // ç›‘管场景信息
    this.curMonitorObjList = JSON.parse(decodeURIComponent(this.$route.query.data));
    // æ ¹æ®æ€»ä»»åŠ¡èŽ·å–è¡Œæ”¿åŒºåˆ’ä¿¡æ¯
    const task = JSON.parse(decodeURIComponent(this.$route.query.task));
    this.area = {
      provincecode: task.provincecode,
      provincename: task.provincename,
      citycode: task.citycode,
      cityname: task.cityname,
      districtcode: task.districtcode,
      districtname: task.districtname,
      towncode: task.towncode,
      townname: task.townname,
      online: true
    };
    this.searchScene({ text: '' });
  }
};
</script>
<style scoped></style>
src/views/fysp/task/TaskManage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,182 @@
<template>
  <BaseContentLayout>
    <template #header>
      <FYSearchBar @search="search">
        <template #options>
          <!-- åŒºåŽ¿ -->
          <FYOptionLocation
            :allOption="true"
            :level="3"
            :checkStrictly="false"
            v-model:value="formSearch.locations"
          ></FYOptionLocation>
        </template>
        <!-- <template #buttons>
          <slot name="buttons"></slot>
        </template> -->
      </FYSearchBar>
    </template>
    <template #aside>
      <SideList :items="tasks" :loading="sideLoading" @item-click="chooseTask"></SideList>
    </template>
    <template #main>
      <ToolBar
        :title="curTask.title"
        :descriptions="taskStatus"
        :buttons="buttons"
        :loading="mainLoading"
      ></ToolBar>
      <el-scrollbar
        v-if="curMonitorObjList.length > 0"
        class="el-scrollbar"
        v-loading="mainLoading"
      >
        <div><el-text>监管计划</el-text></div>
        <el-divider></el-divider>
        <el-button type="primary" size="small" @click="editTask">场景调整</el-button>
        <div><el-text>监管场景</el-text></div>
        <CompMonitorObj :data="curMonitorObjList"></CompMonitorObj>
        <!-- <div><el-text>监管场景</el-text></div>
        <div>
          <el-space wrap>
            <ItemMonitorObj
              v-for="item in curMonitorObjList"
              :key="item.movid"
              :item="item"
            ></ItemMonitorObj>
          </el-space>
        </div> -->
      </el-scrollbar>
      <el-empty v-else description="暂无记录" v-loading="mainLoading" />
    </template>
  </BaseContentLayout>
</template>
<script>
import taskApi from '@/api/fysp/taskApi';
import CompMonitorObj from './components/CompMonitorObj.vue';
export default {
  components: { CompMonitorObj },
  data() {
    return {
      formSearch: {
        _locations: {},
        searchText: '',
        _scenetype: {},
        online: {}
      },
      // å·¦ä¾§èœå•栏加载状态
      sideLoading: false,
      // å³ä¾§å†…容栏加载状态
      mainLoading: false,
      // ä»»åŠ¡åˆ—è¡¨
      tasks: [],
      // å½“前任务的监管对象
      curMonitorObjList: [],
      //当前选中的任务
      curTask: {},
      //操作按钮
      buttons: [
        {
          name: '计划调整',
          color: 'success'
        },
        {
          name: '场景调整',
          color: 'warning'
        }
      ]
    };
  },
  computed: {
    // æ€»ä»»åŠ¡çŠ¶æ€ç»Ÿè®¡
    taskStatus() {
      return [
        { name: '场景数', value: 100 },
        { name: '未巡查', value: 0 },
        { name: '已巡查', value: 0 }
      ];
    }
  },
  methods: {
    search(formSearch) {
      this.sideLoading = true;
      this.mainLoading = true;
      this.curMonitorObjList = [];
      this.curTask = {};
      taskApi.getTopTask().then((res) => {
        const list = res.map((r) => {
          const t = this.getTaskType(r);
          return {
            type: t,
            title: r.name,
            categoly: this.$fm.formatYM(r.starttime),
            data: r
          };
        });
        this.tasks = list;
        if (list.length == 0) {
          this.sideLoading = false;
          this.mainLoading = false;
        }
      });
    },
    //获取任务的完成情况
    getTaskType(s) {
      let type = 0;
      switch (s.runingstatus) {
        case '未执行':
          type = 0;
          break;
        case '正在执行':
          type = 1;
          break;
        case '已结束':
          type = 2;
          break;
        default:
          type = 0;
          break;
      }
      return type;
    },
    chooseTask(task) {
      this.sideLoading = false;
      this.mainLoading = true;
      taskApi
        .fetchMonitorObjectVersion(task.data.tguid)
        .then((res) => {
          this.curMonitorObjList = res;
          this.curTask = task;
        })
        .finally(() => {
          this.mainLoading = false;
        });
    },
    editTask() {
      this.$router.push({
        name: 'monitorObjEdit',
        query: {
          data: encodeURIComponent(JSON.stringify(this.curMonitorObjList)),
          task: encodeURIComponent(JSON.stringify(this.curTask.data))
        }
      });
    }
  },
  mounted() {
    this.search();
  }
};
</script>
<style scoped>
.select-box {
  /* border: 1px solid var(--el-border-color);
  border-radius: var(--el-border-radius-base);
  padding: 0 8px; */
}
.el-scrollbar {
  height: calc((100vh - 60px * 2 - 20px * 2 - var(--height-toolbar)));
}
</style>
src/views/fysp/task/components/CompMonitorObj.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
<template>
  <el-tabs v-model="activeName" type="border-card" @tab-change="tabChange">
    <el-tab-pane
      v-for="item in tabDataList"
      :key="item.title"
      :label="item.title"
      :name="item.title"
    >
      <!-- <div> -->
      <el-space wrap>
        <ItemMonitorObj v-for="obj in item.children" :key="obj.movid" :item="obj">
          <template #default="{ item }">
            <!-- <slot :item="item"></slot> -->
            <el-button v-if="showDelete" size="small" type="danger" @click="deleteMov(item)"
              >移除</el-button
            >
          </template>
        </ItemMonitorObj>
      </el-space>
      <!-- </div> -->
    </el-tab-pane>
  </el-tabs>
</template>
<script>
const defaultTabName = '全部';
export default {
  props: {
    data: {
      type: Array,
      default: () => []
    },
    // æ˜¯å¦æ·»åŠ é»˜è®¤çš„å…¨éƒ¨é€‰é¡¹
    allOption: Boolean,
    showDelete: Boolean
  },
  emits: ['tabChange'],
  data() {
    return {
      activeName: defaultTabName
    };
  },
  computed: {
    tabDataList() {
      const itemMap = new Map();
      this.data.forEach((t) => {
        itemMap.has(t.sceneType) ? itemMap.get(t.sceneType).push(t) : itemMap.set(t.sceneType, [t]);
      });
      const list = [];
      if (this.allOption) {
        list.push({
          title: defaultTabName,
          children: this.data
        });
      }
      for (const [key, value] of itemMap) {
        list.push({
          title: key,
          children: value
        });
      }
      return list;
    }
  },
  watch: {
    tabDataList: {
      handler(nV, oV) {
        if (nV != oV && nV.length > 0) {
          this.activeName = nV[0].title;
          this.tabChange(this.activeName);
        }
      },
      immediate: true
    }
  },
  methods: {
    tabChange(tabName) {
      this.$emit('tabChange', tabName);
    },
    deleteMov(item) {
      const tab = this.tabDataList.find((v) => {
        return v.title == this.activeName;
      });
      const i = tab.children.indexOf(item);
      tab.children.splice(i, 1);
    }
  }
};
</script>
<style scoped></style>