Browse Source

feat: 报警问题管理三期

bxy 1 year ago
parent
commit
3072133065

+ 32 - 29
src/api/datamanagement/alert-default.ts

@@ -2,15 +2,18 @@ import { http } from '@/utils/http/axios';
 
 // 获取默认数据表格
 export interface TableQueryForm {
-  pageNumber: Number,     // 页码
-  pageSize: Number,       // 页大小
-  source?: Number,        // 问题单来源:1-AI检测、2-人工上报
-  issueType?: Number,     // 问题单类型
-  workspaceId?: Number[], // 工位id(地点=车间+工位?)
-  issueState?: Number,    // 问题单状态:1-待审核、2-待处理、3-待复核、4-已退回、5-已处理
-  startTime?: string,     // 开始时间
-  endTime?: string        // 结束时间
-};
+  pageNumber: number; // 页码
+  pageSize: number; // 页大小
+  source?: number; // 问题单来源:1-AI检测、2-人工上报
+  issueMainTypeList?: number[]; // 问题单类型(第一级)
+  issueTypeList?: number[]; // 问题单类型(第二级)
+  workspaceId?: number[]; // 工位id
+  issueState?: number[]; // 问题单状态
+  startTime?: string; // 开始时间
+  endTime?: string; // 结束时间
+  hide?: boolean; // 问题生效状态,false-已生效,true-未生效
+  order?: string; // 时间排序方式,desc-降序,asc-升序(默认降序)
+}
 export const getDefaultTableData = (body: TableQueryForm) => {
   return http.request({
     url: '/issueManagement/getIssueDefaultListPageByCondition',
@@ -37,10 +40,10 @@ export const deleteDefaultTableData = (ids: number[]) => {
 
 // 更新默认问题单列表状态(加急/隐藏)
 export interface UpdateList {
-  id: Number[],       // 问题单id,可批量操作
-  isHide?: Boolean,     // 是否隐藏
-  priority?: Number,  // 0-未加急,1-加急
-};
+  id: Number[]; // 问题单id,可批量操作
+  isHide?: Boolean; // 是否隐藏
+  priority?: Number; // 0-未加急,1-加急
+}
 export const updateDefaultTableData = (body: UpdateList) => {
   return http.request({
     url: '/issueManagement/updateIssueDefaultList',
@@ -51,51 +54,51 @@ export const updateDefaultTableData = (body: UpdateList) => {
 
 // 隐藏问题单
 export interface UpdateHide {
-  id: Number,     // 问题单id
-  hide: Boolean,  // 是否隐藏
-};
+  id: Number; // 问题单id
+  hide: Boolean; // 是否隐藏
+}
 export const updateDefaultHide = (body: UpdateHide) => {
   return http.request({
     url: '/issue/hide',
     method: 'post',
     data: body,
-  })
+  });
 };
 // 批量隐藏问题单
 export interface UpdateHides {
-  ids: Number[],     // 问题单id
-  hide: Boolean,  // 是否隐藏
-};
+  ids: Number[]; // 问题单id
+  hide: Boolean; // 是否隐藏
+}
 export const updateDefaultHideAll = (body: UpdateHides) => {
   return http.request({
     url: '/issue/hide/batch',
     method: 'post',
     data: body,
-  })
+  });
 };
 
 // 调整问题单优先级
 export interface UpdatePriority {
-  id: Number,        // 问题单id
-  priority: Number,  // 是否加急 0-未加急,1-加急
-};
+  id: Number; // 问题单id
+  priority: Number; // 是否加急 0-未加急,1-加急
+}
 export const updateDefaultPriority = (body: UpdatePriority) => {
   return http.request({
     url: '/issue/priority',
     method: 'post',
     data: body,
-  })
+  });
 };
 
 // 批量调整问题单优先级
 export interface UpdatePrioritys {
-  ids: Number[],        // 问题单id
-  priority: Number,  // 是否加急 0-未加急,1-加急
-};
+  ids: Number[]; // 问题单id
+  priority: Number; // 是否加急 0-未加急,1-加急
+}
 export const updateDefaultPriorityAll = (body: UpdatePrioritys) => {
   return http.request({
     url: '/issue/priority/batch',
     method: 'post',
     data: body,
-  })
+  });
 };

+ 22 - 29
src/api/datamanagement/alert-show.ts

@@ -1,14 +1,7 @@
 import { http } from '@/utils/http/axios';
+import { TableQueryForm } from './alert-default';
 
 // 获取展示数据表格
-export interface TableQueryForm {
-  pageNumber: Number,     // 页码
-  pageSize: Number,       // 页大小
-  source?: Number,        // 问题单来源:1-AI检测、2-人工上报
-  issueType?: Number,     // 问题单类型
-  workspaceId?: Number[], // 工位id/地点
-  issueState?: Number,    // 问题单状态:1-待审核、2-待处理、3-待复核、4-已退回、5-已处理
-};
 export const getShowTableData = (body: TableQueryForm) => {
   return http.request({
     url: '/issueManagement/getIssueDisplayListPageByCondition',
@@ -19,14 +12,14 @@ export const getShowTableData = (body: TableQueryForm) => {
 
 // 添加展示表格接口(添加btn)
 export interface AddForm {
-  source: Number,         // 问题单来源:1-AI检测、2-人工上报
-  issueType: Number,      // 问题单类型
-  description: String,    // 问题描述
-  pictures: Array<string>,// 图片
-  workspaceId: Number,    // 工位id/地点
-  createdAt: String,      // 问题时间(默认是createdAt)
-  issueState: Number,     // 问题单状态:1-待审核、2-待处理、3-待复核、4-已退回、5-已处理
-};
+  source: Number; // 问题单来源:1-AI检测、2-人工上报
+  issueType: Number; // 问题单类型
+  description: String; // 问题描述
+  pictures: Array<string>; // 图片
+  workspaceId: Number; // 工位id/地点
+  createdAt: String; // 问题时间(默认是createdAt)
+  issueState: Number; // 问题单状态:1-待审核、2-待处理、3-待复核、4-已退回、5-已处理
+}
 export const addShowTableData = (body: AddForm) => {
   return http.request({
     url: '/issueManagement/addIssueDisplay',
@@ -45,16 +38,16 @@ export const deleteShowTableData = (ids: number[]) => {
 
 // 编辑展示表格接口(edit)
 export interface EditForm {
-  id: Number,             // 问题单Id,表格data获取
-  source: Number,         // 问题单来源:1-AI检测、2-人工上报
-  issueType: Number,      // 问题单类型
-  description: String,    // 问题描述
-  pictures: Array<string>,// 图片
-  workspaceId: Number,    // 工位id/地点
-  createdAt: String,      // 问题时间(默认是createdAt)
-  issueState: Number,     // 问题单状态:1-待审核、2-待处理、3-待复核、4-已退回、5-已处理
-  isHide?: Boolean,       // 问题单是否隐藏(根据UI编辑/添加表单没有这个选项。改变隐藏显示另有接口) 
-};
+  id: Number; // 问题单Id,表格data获取
+  source: Number; // 问题单来源:1-AI检测、2-人工上报
+  issueType: Number; // 问题单类型
+  description: String; // 问题描述
+  pictures: Array<string>; // 图片
+  workspaceId: Number; // 工位id/地点
+  createdAt: String; // 问题时间(默认是createdAt)
+  issueState: Number; // 问题单状态:1-待审核、2-待处理、3-待复核、4-已退回、5-已处理
+  isHide?: Boolean; // 问题单是否隐藏(根据UI编辑/添加表单没有这个选项。改变隐藏显示另有接口)
+}
 export const EditShowTableData = (body: EditForm) => {
   return http.request({
     url: '/issueManagement/editIssueDisplay',
@@ -65,9 +58,9 @@ export const EditShowTableData = (body: EditForm) => {
 
 // 更新展示表格状态(show/hide)
 export interface UpdateList {
-  id: Number[],         // 问题单id,可批量操作
-  isHide?: Boolean,     // 是否隐藏
-};
+  id: Number[]; // 问题单id,可批量操作
+  isHide?: Boolean; // 是否隐藏
+}
 export const updateShowTableData = (body: UpdateList) => {
   return http.request({
     url: '/issueManagement/updateIssueDisplayList',

+ 25 - 9
src/views/datamanager/alertformdata/components/common/AlertTable.vue

@@ -10,9 +10,10 @@
       @select="handleShiftSelect"
       @selection-change="handleSelectionChange"
       @row-click="handleRowClick"
+      @sort-change="handleTimeSort"
+      :default-sort="{ prop: 'createdAt', order: 'descending' }"
     >
       <el-table-column type="selection" width="55"></el-table-column>
-      <el-table-column type="index" width="55"></el-table-column>
       <el-table-column label="问题来源" prop="source" width="150">
         <template #default="{ row }">
           {{ getNameBySource(row.source) }}
@@ -42,7 +43,12 @@
           {{ getNameByWorkid(row.workshopId, row.workspaceId, locationOptions) }}
         </template>
       </el-table-column>
-      <el-table-column label="时间" prop="createdAt" width="180"></el-table-column>
+      <el-table-column
+        label="时间"
+        prop="createdAt"
+        width="180"
+        sortable="custom"
+      ></el-table-column>
       <el-table-column label="负责人" prop="personNameInCharge" width="100"></el-table-column>
       <el-table-column label="处理状态" prop="issueState">
         <template #default="{ row }">
@@ -51,10 +57,6 @@
       </el-table-column>
       <el-table-column label="操作" width="160" fixed="right">
         <template #default="{ row }">
-          <!-- <img src="/src/assets/images/alert/show-on.png" alt="" title="点击隐藏问题展示" v-if="!row.isHide"
-            @click="handleShow(row)">
-          <img src="/src/assets/images/alert/show-off.png" alt="" title="点击恢复问题展示" v-if="row.isHide"
-            @click="handleShow(row)"> -->
           <el-tooltip
             effect="dark"
             :content="row.isHide ? '点此设置为生效问题' : '点此设置为无效问题'"
@@ -131,11 +133,13 @@
 
   const { aiOptions, manualOptions, getAIOptions, getManualOptions, getNameByType } =
     useIssueType();
+  console.log(aiOptions.value, manualOptions.value);
+
   const { locationOptions, getLocationOptions, getNameByWorkid } = useWorkLocation();
 
   const multipleTableRef = ref<InstanceType<typeof ElTable>>();
 
-  interface DataSourceItem {
+  export interface DataSourceItem {
     index: Number;
     id: Number;
     source: Number; // 问题单来源:1-AI检测、2-人工上报
@@ -159,7 +163,7 @@
 
   const props = defineProps<Props>();
 
-  const emits = defineEmits(['update:selection']);
+  const emits = defineEmits(['update:selection', 'update:timeSort']);
 
   const startPoint = ref<number | undefined>();
   const endPoint = ref<number | undefined>();
@@ -232,6 +236,13 @@
     props.onDelete(row);
   };
 
+  const handleTimeSort = (column) => {
+    let curTimeSort = '';
+    if (column.order === 'ascending') curTimeSort = 'asc';
+    if (column.order === 'descending') curTimeSort = 'desc';
+    emits('update:timeSort', curTimeSort);
+  };
+
   const getSplicedDes = (val) => {
     if (val.length < 20) return val;
     else {
@@ -263,7 +274,12 @@
     multipleTableRef.value!.clearSelection();
   };
 
-  defineExpose({ clearAll });
+  // 改变当前打开详情的表格行的选中状态
+  const updateCurRowChosen = (row, status: boolean) => {
+    multipleTableRef.value!.toggleRowSelection(row, status);
+  };
+
+  defineExpose({ clearAll, updateCurRowChosen });
 
   onBeforeMount(() => {
     getAIOptions();

+ 63 - 37
src/views/datamanager/alertformdata/components/common/DetailDialog.vue

@@ -29,33 +29,42 @@
             />
           </div>
           <div class="img-box" v-for="(imagePath, index) in imagePaths" :key="index">
-            <img
+            <el-image
+              style="width: 200px; height: 200px; border: solid 1px #ccc"
               :src="imagePath"
-              alt=""
-              style="object-fit: contain; width: 200px; height: 200px; border: solid 1px #ccc"
-              @mouseenter="handleOpenPre(imagePath, index)"
+              :preview-src-list="imagePaths"
+              :zoom-rate="1.2"
+              :max-scale="7"
+              :min-scale="0.2"
+              :initial-index="index"
+              fit="cover"
             />
-            <div class="cover-box" v-if="isCoverVisible(index)" @mouseleave="handleClosePre(index)">
-              <el-icon :size="40" color="#e6e6e6" style="cursor: pointer" @click="handlePreview">
-                <ZoomIn />
-              </el-icon>
-            </div>
           </div>
         </div>
       </div>
-    </el-dialog>
-    <el-dialog v-model="dialogVisible">
-      <img w-full :src="dialogImageUrl" alt="" />
+      <template #footer>
+        <div class="dialog-footer">
+          <span class="footer-tip">提示:可切换查看问题列表当前分页内数据</span>
+          <el-button @click="handlePrevious" :disabled="!hasPrevious">上一个</el-button>
+          <el-button type="primary" @click="handleNext" :disabled="!hasNext">下一个</el-button>
+          <el-checkbox
+            class="footer-checkbox"
+            v-model="curHasBeenChosen"
+            label="是否选中"
+            @change="handleChooseStatus"
+          />
+        </div>
+      </template>
     </el-dialog>
 
-    <el-dialog v-model="videoDialogVisible" class="video-dialog">
+    <el-dialog v-model="videoDialogVisible" class="video-dialog" align-center>
       <video
         type="video/mp4"
         muted="true"
         preload="auto"
         :controls="true"
         autoplay
-        style="height: 464px; object-fit: contain"
+        style="height: 620px; object-fit: contain"
       >
         <source :src="videoPaths![0]" />
       </video>
@@ -64,46 +73,43 @@
 </template>
 
 <script setup lang="ts">
-  import { ref } from 'vue';
-  import { ZoomIn } from '@element-plus/icons-vue';
+  import { computed, ref } from 'vue';
 
   const props = defineProps({
+    hasBeenChosen: Boolean,
     description: String,
     imagePaths: Array<string>,
     videoPaths: Array<string>,
+    hasPrevious: Boolean,
+    hasNext: Boolean,
   });
 
-  const emits = defineEmits(['close']);
+  const emits = defineEmits(['close', 'update:previous', 'update:next', 'update:choose']);
   const visible = ref(true);
-
-  const showPreview = ref<boolean[]>(new Array(props.imagePaths?.values.length).fill(false));
-  const dialogVisible = ref(false);
-  const dialogImageUrl = ref('');
   const videoDialogVisible = ref(false);
 
-  const handleOpenPre = (val, index) => {
-    dialogImageUrl.value = val;
-    showPreview.value[index] = true;
-  };
+  const curHasBeenChosen = computed(() => {
+    return props.hasBeenChosen;
+  });
 
-  const handleClosePre = (index) => {
-    showPreview.value[index] = false;
+  const handleClose = () => {
+    emits('close');
   };
 
-  const isCoverVisible = (index) => {
-    return showPreview.value[index];
+  const handlePrevious = () => {
+    emits('update:previous');
   };
 
-  const handlePreview = () => {
-    dialogVisible.value = true;
+  const handleNext = () => {
+    emits('update:next');
   };
 
-  const handleClose = () => {
-    emits('close');
+  const handleChooseStatus = () => {
+    emits('update:choose', curHasBeenChosen.value);
   };
 </script>
 
-<style scoped>
+<style scoped lang="less">
   :deep(.el-dialog) {
     padding: 0;
     background: #ffffff;
@@ -130,9 +136,23 @@
     }
 
     .el-dialog__body {
-      height: 564px;
+      height: 472px;
       padding: 40px;
-      overflow: scroll;
+      overflow: auto;
+    }
+
+    .el-dialog__footer {
+      height: 92px;
+      padding: 28px 48px;
+
+      .footer-tip {
+        margin-right: 40px;
+        color: rgba(0, 0, 0, 0.3);
+      }
+
+      .footer-checkbox {
+        margin-left: 40px;
+      }
     }
   }
 
@@ -189,9 +209,15 @@
   }
 
   :deep(.video-dialog) {
+    .el-dialog__header,
+    .el-dialog__footer {
+      display: none;
+    }
+
     .el-dialog__body {
+      height: 100%;
+      padding: 0;
       display: flex;
-      align-items: center;
       justify-content: center;
     }
   }

+ 89 - 47
src/views/datamanager/alertformdata/components/common/QueryForm.vue

@@ -17,21 +17,15 @@
             />
           </el-select>
         </el-form-item>
-        <el-form-item label="类型:" prop="issueType">
-          <!-- <el-select
-            v-model="queryForm.issueType"
-            placeholder="全部"
-            clearable
-            :disabled="typeDisable"
-          >
-            <el-option v-for="item in options" :label="item.name" :value="item.id" />
-          </el-select> -->
+        <el-form-item label="类型:" prop="issueTypeList">
           <el-cascader
             v-model="issueTypeValue"
             :options="options"
             :props="issueMainTypeProp"
-            :show-all-levels="false"
             clearable
+            collapse-tags
+            :show-all-levels="false"
+            popper-class="special-cascader"
             :disabled="typeDisable"
             @change="handleIssueMainTypeChange"
           />
@@ -40,14 +34,15 @@
           <el-cascader
             v-model="workLocation"
             :options="locationOptions"
-            :props="location"
+            :props="locationProp"
             clearable
             collapse-tags
             :show-all-levels="false"
+            popper-class="special-cascader"
             @change="handleCascaderChange"
           />
         </el-form-item>
-        <el-form-item label="状态:" prop="issueState">
+        <el-form-item label="处理状态:" prop="issueState">
           <el-select v-model="tempState" clearable @change="handleIssueStateChange">
             <el-option
               v-for="item in issueStateOptions"
@@ -57,6 +52,16 @@
             />
           </el-select>
         </el-form-item>
+        <el-form-item label="生效状态:" prop="hide">
+          <el-select v-model="queryForm.hide" clearable>
+            <el-option
+              v-for="item in hideStateOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
         <el-form-item label="日期:">
           <el-date-picker
             v-model="dateRange"
@@ -85,8 +90,9 @@
 
 <script setup lang="ts">
   import type { FormInstance } from 'element-plus';
-  import { computed, reactive, ref } from 'vue';
-  import { sourceOptions, issueStateOptions } from './constant.question';
+  import { reactive, ref } from 'vue';
+  import { sourceOptions, hideStateOptions, issueStateOptions } from './constant.question';
+  import { TableQueryForm } from '@/api/datamanagement/alert-default';
 
   interface Props {
     isShowTab: boolean; // true展示数据,false默认数据
@@ -97,20 +103,8 @@
   const props = defineProps<Props>();
   const emits = defineEmits(['onSearch', 'onReset', 'onExport']);
 
-  interface QueryModel {
-    pageNumber: number;
-    pageSize: number;
-    source?: number; // 来源
-    issueMainType?: number; // 一级类型
-    issueType?: number; // 类型
-    workspaceId?: number[]; // 地点=工位id
-    issueState?: number[]; // 状态
-    startTime?: string; // 开始时间(默认)
-    endTime?: string; // 结束时间(默认)
-  }
-
   const formRef = ref<FormInstance>();
-  const queryForm = reactive<QueryModel>({
+  const queryForm = reactive<TableQueryForm>({
     pageNumber: 1,
     pageSize: 10,
   });
@@ -126,20 +120,17 @@
 
   const options = ref<MainOption[]>([]);
   const typeDisable = ref(true);
+  const issueTypeValue = ref([]); // 问题类型,级联选择器
+  const issueMainTypeProp = { multiple: true, expandTrigger: 'hover' as const };
+  const workShopIds = ref([]);
   const workLocation = ref([]); // 级联选择器,为二维数组(提取workspaceId)
-  const location = { multiple: true }; // 级联选择器(打开多选)
+  const locationProp = { multiple: true, expandTrigger: 'hover' as const }; // 级联选择器(打开多选)
   const tempState = ref(''); // 状态,字符串转number[]
   const dateRange = ref([]); // 时间段,拆分成startTime/endTime
   const defaultTime = ref<[Date, Date]>([
     new Date(2000, 1, 1, 0, 0, 0),
     new Date(2000, 2, 1, 23, 59, 59),
   ]);
-  const issueTypeValue = ref([]); // 问题类型,级联选择器
-  const issueMainTypeProp = computed(() => {
-    if (props.isShowTab) return { expandTrigger: 'hover' as const, checkStrictly: false };
-    else return { expandTrigger: 'hover' as const, checkStrictly: true };
-  });
-  const workShopIds = ref([]);
 
   const handleSearch = () => {
     emits('onSearch', queryForm);
@@ -154,7 +145,7 @@
     dateRange.value = [];
     Reflect.deleteProperty(queryForm, 'startTime');
     Reflect.deleteProperty(queryForm, 'endTime');
-    Reflect.deleteProperty(queryForm, 'issueMainType');
+    Reflect.deleteProperty(queryForm, 'issueMainTypeList');
     formRef.value?.resetFields();
     emits('onReset', queryForm);
   };
@@ -173,19 +164,20 @@
     } else {
       typeDisable.value = true;
       options.value = [];
-      queryForm.issueMainType = undefined;
-      queryForm.issueType = undefined;
+      queryForm.issueMainTypeList = undefined;
+      queryForm.issueTypeList = undefined;
     }
   };
 
   const handleIssueMainTypeChange = () => {
-    if (issueTypeValue.value) {
-      queryForm.issueMainType = issueTypeValue.value[0];
-      queryForm.issueType = issueTypeValue.value[1];
-    } else {
-      Reflect.deleteProperty(queryForm, 'issueMainType');
-      Reflect.deleteProperty(queryForm, 'issueType');
-    }
+    const arrMain = [];
+    const arrSub = [];
+    issueTypeValue.value.forEach((item) => {
+      arrMain.push(item[0]);
+      arrSub.push(item[1]);
+    });
+    queryForm.issueMainTypeList = [...new Set(arrMain)];
+    queryForm.issueTypeList = arrSub;
   };
 
   const handleCascaderChange = () => {
@@ -212,20 +204,70 @@
       Reflect.deleteProperty(queryForm, 'endTime');
     }
   };
+
+  // TODO: cascader点击任意位置选中,DOM方式实现一级,CSS方式无法穿透
+  // TODO: el-cascader组件需添加 @expand-change="handleLocationExpandChange"(当前暂时删除)
+  // const curLocationLevel = ref(0);
+  // const handleLocationExpandChange = (event) => {
+  //   curLocationLevel.value = event.length;
+  //   nextTick(() => {
+  //     setCascaderDomEvent();
+  //   });
+  // };
+
+  // const setCascaderDomEvent = () => {
+  //   const cascaderDom = document.querySelectorAll('.special-cascader .el-cascader-menu__list');
+
+  //   if (cascaderDom.length >= curLocationLevel.value - 1) {
+  //     const optionDom = cascaderDom[curLocationLevel.value] as Element;
+  //     optionDom.querySelectorAll('.el-cascader-node__label').forEach((label: Element) => {
+  //       const nextDom = label.nextElementSibling;
+  //       label.addEventListener('click', cascaderCheckEvent);
+  //       if (!nextDom) {
+  //         (label as HTMLElement).style.cursor = 'pointer';
+  //       }
+  //     });
+  //   }
+  //   if (curLocationLevel.value) {
+  //     const optionDom = cascaderDom[curLocationLevel.value - 1] as Element;
+  //     optionDom.querySelectorAll('.el-cascader-node__label').forEach((label: Element) => {
+  //       const nextDom = label.nextElementSibling;
+  //       label.addEventListener('click', cascaderCheckEvent);
+  //       if (!nextDom) {
+  //         (label as HTMLElement).style.cursor = 'pointer';
+  //       }
+  //     });
+  //   }
+  // };
+
+  // const cascaderCheckEvent = (event: MouseEvent) => {
+  //   // 查找对应的输入框
+  //   let brother = (event.target as HTMLElement).previousElementSibling; // 获取前一个兄弟元素
+  //   const input = brother?.querySelector('input[type="checkbox"], input[type="radio"]'); // 支持复选框和单选框
+  //   if (input) {
+  //     (input as HTMLInputElement).click(); // 模拟点击复选框
+  //   }
+
+  //   // 如果有子菜单,展开/收起子菜单
+  //   const childMenu = (event.target as HTMLElement).nextElementSibling;
+  //   if (childMenu && childMenu.classList.contains('el-cascader-menu__list')) {
+  //     // 这里可以根据需求添加展开/收起的逻辑
+  //   }
+  // };
 </script>
 
-<style scoped lang="scss">
+<style scoped lang="less">
   .el-form {
     display: flex;
     justify-content: space-between;
   }
 
   :deep(.el-form--inline .el-form-item) {
-    margin-right: 0;
+    margin-right: 10px;
   }
 
   :deep(.el-form-item__label) {
-    padding: 0;
+    padding-left: 10px;
   }
 
   .select-group {

+ 4 - 9
src/views/datamanager/alertformdata/components/common/QuestionFormBase.vue

@@ -179,18 +179,13 @@
     type UploadUserFile,
   } from 'element-plus';
   import { Plus, CloseBold } from '@element-plus/icons-vue';
-  import { IssueState, sourceOptions, issueStateOptionsAdd } from './constant.question';
+  import { Source, IssueState, sourceOptions, issueStateOptionsAdd } from './constant.question';
   import { useIssueType } from '../../hooks/useIssueType';
   import { useWorkLocation } from '../../hooks/useWorkLocation';
   import { getHeaders } from '@/utils/http/axios';
   import urlJoin from 'url-join';
   import { useGlobSetting } from '@/hooks/setting';
 
-  enum SourceType {
-    ai = 1,
-    manual = 2,
-  }
-
   interface Props {
     initialData?: FormModel;
   }
@@ -240,10 +235,10 @@
     formData.issueType = undefined;
   };
   const options = computed(() => {
-    if (Number(formData.source) === SourceType.ai && aiOptions.value.length > 0) {
+    if (Number(formData.source) === Source.ai && aiOptions.value.length > 0) {
       return aiOptions.value;
     }
-    if (Number(formData.source) === SourceType.manual && manualOptions.value.length > 0) {
+    if (Number(formData.source) === Source.manual && manualOptions.value.length > 0) {
       return manualOptions.value;
     }
     return [];
@@ -368,7 +363,7 @@
   });
 </script>
 
-<style scoped>
+<style scoped lang="less">
   :deep(.el-drawer__header) {
     position: relative;
 

+ 6 - 0
src/views/datamanager/alertformdata/components/common/constant.question.ts

@@ -17,6 +17,12 @@ export const getNameBySource = (source: Source) => {
   return sourceNameMap[source] || '-';
 };
 
+// 问题生效状态
+export const hideStateOptions = [
+  { label: '已生效', value: false },
+  { label: '未生效', value: true },
+];
+
 // 问题状态
 export enum IssueState {
   toAuth0 = 0,

+ 67 - 26
src/views/datamanager/alertformdata/components/default/Default.vue

@@ -59,6 +59,7 @@
         :on-show="handleShow"
         :on-delete="handleDelete"
         @update:selection="handlePop"
+        @update:time-sort="handleChangeTimeSort"
       />
     </div>
     <div class="pagination-box">
@@ -72,10 +73,16 @@
     </div>
     <DetailDialog
       v-if="isDetailDialogShow"
+      :has-been-chosen="detailRowChosen"
       :description="detailDescription"
       :image-paths="detailPictures"
       :video-paths="detailVideos"
+      :has-previous="hasPreviousRow"
+      :has-next="hasNextRow"
       @close="closeDetailDialog"
+      @update:previous="handleChangePrevious"
+      @update:next="handleChangeNext"
+      @update:choose="handleChangeChoose"
     />
   </div>
 </template>
@@ -85,13 +92,13 @@
   import { ElMessage, ElMessageBox } from 'element-plus';
   import axios, { AxiosRequestConfig } from 'axios';
   import QueryForm from '../common/QueryForm.vue';
-  import AlertTable from '../common/AlertTable.vue';
+  import AlertTable, { DataSourceItem } from '../common/AlertTable.vue';
   import DetailDialog from '../common/DetailDialog.vue';
   import Pagination from '../common/Pagination.vue';
-  // import { useIssueType } from '../../hooks/useIssueType';
   import { useWorkLocation } from '../../hooks/useWorkLocation';
   import { useIssueMainType } from '../../hooks/useIssueMainType';
   import {
+    TableQueryForm,
     getDefaultTableData,
     deleteDefaultTableData,
     copyToShowTableData,
@@ -108,7 +115,6 @@
   import { getHeaders } from '@/utils/http/axios';
 
   const userStore = useUserStore();
-  // const { aiOptions, manualOptions, getAIOptions, getManualOptions } = useIssueType();
   const { locationOptions, getLocationOptions } = useWorkLocation();
   const { aiMainOptions, manualMainOptions, getAIMainOptions, getManualMainOptions } =
     useIssueMainType();
@@ -116,11 +122,11 @@
   const { urlPrefix } = useGlobSetting();
 
   const alertTableRef = ref<typeof AlertTable>();
-  const tableData = ref([]);
+  const tableData = ref<DataSourceItem[]>([]);
   const showActionBar = ref(false);
   const chooseNum = ref(0);
+  const chooseRow = ref<DataSourceItem[]>([]); // 被选中的数据行
   const chooseId = ref<number[]>([]);
-  // const cancelHideFlag = ref(true);   // true取消隐藏 / false全部隐藏
   const cancelUrgentFlag = ref(true); // true取消加急 / false全部加急
   const isActiveHide = ref(false);
   const isActiveCancelHide = ref(false);
@@ -130,13 +136,20 @@
   const isActiveCopy = ref(false);
   // 详情
   const isDetailDialogShow = ref(false);
+  const detailRowChosen = ref(false); // 当前行是否被选中
+  const detailRow = ref(); // 当前行
+  const detailCurRowIndex = ref(0); // 当前行index
+  const detailPreviousRow = ref(); // 上一行
+  const detailNextRow = ref(); // 下一行
+  const hasPreviousRow = ref(false); // 是否有上一行
+  const hasNextRow = ref(false); // 是否有下一行
   const detailDescription = ref('');
   const detailPictures = ref<string[]>([]);
   const detailVideos = ref<string[]>([]);
   // 分页
   const total = ref(0);
 
-  const query = ref({
+  const query = ref<TableQueryForm>({
     pageNumber: 1,
     pageSize: 10,
   });
@@ -200,16 +213,19 @@
     }
   };
 
+  // 表格排序切换
+  const handleChangeTimeSort = (curTimeSort) => {
+    query.value.order = curTimeSort;
+    getTableData();
+  };
+
   // 多选
   const handlePop = (selection) => {
+    chooseRow.value = selection;
     chooseId.value = [];
-    // cancelHideFlag.value = true;
     cancelUrgentFlag.value = true;
     selection.forEach((item) => {
       if (chooseId.value.indexOf(item.id) === -1) chooseId.value.push(item.id);
-      // 只要有一个item.isHide === false 说明多选的这些选项不是全都隐藏/失效,不用变成“取消隐藏”/全部生效
-      // if (item.isHide === false) cancelHideFlag.value = false;
-      // 只要有一个item.priority === 0 说明不是全都加急状态,不用变成“取消加急”
       if (item.priority === 0) cancelUrgentFlag.value = false;
     });
     chooseNum.value = selection.length;
@@ -222,6 +238,11 @@
     alertTableRef.value?.clearAll();
     showActionBar.value = false;
   };
+  // 改变该行的选中状态
+  const handleChangeChoose = (status) => {
+    alertTableRef.value?.updateCurRowChosen(detailRow.value, !status);
+    updateDetailDialog(detailRow.value);
+  };
 
   // 全部隐藏
   const handleHideAll = () => {
@@ -352,11 +373,36 @@
   const closeDetailDialog = () => {
     isDetailDialogShow.value = false;
   };
+  // 更新detailCurRowIndex、detailPreviousRow、detailNextRow、hasPreviousRow、hasNextRow
+  const updateDetailDialog = (curRow) => {
+    detailDescription.value = curRow.description;
+    detailPictures.value = curRow.pictures;
+    detailVideos.value = curRow.videos;
+    detailCurRowIndex.value = tableData.value.findIndex((item) => item.id === curRow.id);
+    detailPreviousRow.value = tableData.value[detailCurRowIndex.value - 1];
+    detailNextRow.value = tableData.value[detailCurRowIndex.value + 1];
+    if (detailPreviousRow.value) hasPreviousRow.value = true;
+    else hasPreviousRow.value = false;
+    if (detailNextRow.value) hasNextRow.value = true;
+    else hasNextRow.value = false;
+    if (chooseRow.value.findIndex((item) => item.id === curRow.id) !== -1)
+      detailRowChosen.value = true;
+    else detailRowChosen.value = false;
+  };
   const handleDetail = (row) => {
     isDetailDialogShow.value = true;
-    detailDescription.value = row.description;
-    detailPictures.value = row.pictures;
-    detailVideos.value = row.videos;
+    detailRow.value = row;
+    updateDetailDialog(detailRow.value);
+  };
+  // 上一个
+  const handleChangePrevious = () => {
+    detailRow.value = detailPreviousRow.value;
+    updateDetailDialog(detailRow.value);
+  };
+  // 下一个
+  const handleChangeNext = () => {
+    detailRow.value = detailNextRow.value;
+    updateDetailDialog(detailRow.value);
   };
 
   // 单个加急priority=1/取消加急priority=0
@@ -431,34 +477,31 @@
     return userStore.checkPermission('question_mock_edit_admin');
   };
 
-  onMounted(() => {
-    getTableData();
-  });
+  const hasDevModePermisson = () => {
+    return userStore.checkPermission('control_activation');
+  };
 
   const devMode = ref(true);
   getDevMode().then((res) => {
     devMode.value = res;
   });
-
   const switchDevMode = () => {
     SDM();
     devMode.value = !devMode.value;
   };
 
-  const hasDevModePermisson = () => {
-    return userStore.checkPermission('control_activation');
-  };
+  onMounted(() => {
+    getTableData();
+  });
 
   onBeforeMount(() => {
-    // getAIOptions();
-    // getManualOptions();
     getLocationOptions();
     getAIMainOptions();
     getManualMainOptions();
   });
 </script>
 
-<style scoped lang="scss">
+<style scoped lang="less">
   .box {
     display: flex;
     flex-direction: column;
@@ -466,8 +509,6 @@
 
   .table-list {
     flex: 1;
-    // height: calc(100vh - 350px);
-    // overflow-y: scroll;
 
     .action-bar {
       display: flex;
@@ -519,7 +560,7 @@
     margin-top: 10px;
   }
 </style>
-<style lang="scss">
+<style lang="less">
   .deleteMessage {
     padding: 20px 24px;
     box-shadow: 0px 12px 48px 16px rgba(0, 0, 0, 0.03), 0px 9px 28px 0px rgba(0, 0, 0, 0.05),

+ 60 - 16
src/views/datamanager/alertformdata/components/show/Show.vue

@@ -9,9 +9,7 @@
         @on-search="handleSearch"
         @on-reset="handleReset"
       />
-      <div class="button-group">
-        <el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
-      </div>
+      <el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
     </div>
     <div class="table-list">
       <div v-if="showActionBar" class="action-bar">
@@ -39,6 +37,7 @@
         :on-show="handleShow"
         :on-delete="handleDelete"
         @update:selection="handlePop"
+        @update:time-sort="handleChangeTimeSort"
       />
     </div>
     <div class="pagination-box">
@@ -52,10 +51,16 @@
     </div>
     <DetailDialog
       v-if="isDetailDialogShow"
+      :has-been-chosen="detailRowChosen"
       :description="detailDescription"
       :image-paths="detailPictures"
       :video-paths="detailVideos"
+      :has-previous="hasPreviousRow"
+      :has-next="hasNextRow"
       @close="closeDetailDialog"
+      @update:previous="handleChangePrevious"
+      @update:next="handleChangeNext"
+      @update:choose="handleChangeChoose"
     />
     <AddDrawer v-if="isAddDrawer" @close="handleAddDrawerClose" />
     <EditDrawer v-if="isEditDrawer" :initial-data="rowData" @close="handleEditDrawerClose" />
@@ -67,7 +72,7 @@
   import { ElMessage, ElMessageBox } from 'element-plus';
   import { Plus } from '@element-plus/icons-vue';
   import QueryForm from '../common/QueryForm.vue';
-  import AlertTable from '../common/AlertTable.vue';
+  import AlertTable, { DataSourceItem } from '../common/AlertTable.vue';
   import DetailDialog from '../common/DetailDialog.vue';
   import Pagination from '../common/Pagination.vue';
   import AddDrawer from '../common/AddDrawer.vue';
@@ -80,23 +85,30 @@
     updateShowTableData,
     deleteShowTableData,
   } from '@/api/datamanagement/alert-show';
+  import { TableQueryForm } from '@/api/datamanagement/alert-default';
 
-  // const { aiOptions, manualOptions, getAIOptions, getManualOptions } = useIssueType();
   const { locationOptions, getLocationOptions } = useWorkLocation();
   const { aiMainOptions, manualMainOptions, getAIMainOptions, getManualMainOptions } =
     useIssueMainType();
 
   const alertTableRef = ref<typeof AlertTable>();
-  const tableData = ref([]);
+  const tableData = ref<DataSourceItem[]>([]);
   const showActionBar = ref(false);
   const chooseNum = ref(0);
+  const chooseRow = ref<DataSourceItem[]>([]); // 被选中的数据行
   const chooseId = ref<number[]>([]);
-  // const cancelHideFlag = ref(true);   // true取消隐藏 / false全部隐藏
   const isActiveHide = ref(false);
   const isActiveCancelHide = ref(false);
   const isActiveDelete = ref(false);
   // 详情
   const isDetailDialogShow = ref(false);
+  const detailRowChosen = ref(false); // 当前行是否被选中
+  const detailRow = ref(); // 当前行
+  const detailCurRowIndex = ref(0); // 当前行index
+  const detailPreviousRow = ref(); // 上一行
+  const detailNextRow = ref(); // 下一行
+  const hasPreviousRow = ref(false); // 是否有上一行
+  const hasNextRow = ref(false); // 是否有下一行
   const detailDescription = ref('');
   const detailPictures = ref<string[]>([]);
   const detailVideos = ref<string[]>([]);
@@ -107,7 +119,7 @@
   // 分页
   const total = ref(0);
 
-  const query = ref({
+  const query = ref<TableQueryForm>({
     pageNumber: 1,
     pageSize: 10,
   });
@@ -122,14 +134,18 @@
     getTableData();
   };
 
+  // 表格排序切换
+  const handleChangeTimeSort = (curTimeSort) => {
+    query.value.order = curTimeSort;
+    getTableData();
+  };
+
   // 多选
   const handlePop = (selection) => {
+    chooseRow.value = selection;
     chooseId.value = [];
-    // cancelHideFlag.value = true;
     selection.forEach((item) => {
       if (chooseId.value.indexOf(item.id) === -1) chooseId.value.push(item.id);
-      // 只要有一个item.isHide === false 说明多选的这些选项不是全都隐藏/失效,不用变成“取消隐藏”/全部生效
-      // if (item.isHide === false) cancelHideFlag.value = false;
     });
     chooseNum.value = selection.length;
     showActionBar.value = chooseNum.value > 0 ? true : false;
@@ -141,6 +157,11 @@
     alertTableRef.value?.clearAll();
     showActionBar.value = false;
   };
+  // 改变该行的选中状态
+  const handleChangeChoose = (status) => {
+    alertTableRef.value?.updateCurRowChosen(detailRow.value, !status);
+    updateDetailDialog(detailRow.value);
+  };
 
   // 全部隐藏
   const handleHideAll = () => {
@@ -216,11 +237,36 @@
   const closeDetailDialog = () => {
     isDetailDialogShow.value = false;
   };
+  // 更新detailCurRowIndex、detailPreviousRow、detailNextRow、hasPreviousRow、hasNextRow
+  const updateDetailDialog = (curRow) => {
+    detailDescription.value = curRow.description;
+    detailPictures.value = curRow.pictures;
+    detailVideos.value = curRow.videos;
+    detailCurRowIndex.value = tableData.value.findIndex((item) => item.id === curRow.id);
+    detailPreviousRow.value = tableData.value[detailCurRowIndex.value - 1];
+    detailNextRow.value = tableData.value[detailCurRowIndex.value + 1];
+    if (detailPreviousRow.value) hasPreviousRow.value = true;
+    else hasPreviousRow.value = false;
+    if (detailNextRow.value) hasNextRow.value = true;
+    else hasNextRow.value = false;
+    if (chooseRow.value.findIndex((item) => item.id === curRow.id) !== -1)
+      detailRowChosen.value = true;
+    else detailRowChosen.value = false;
+  };
   const handleDetail = (row) => {
     isDetailDialogShow.value = true;
-    detailDescription.value = row.description;
-    detailPictures.value = row.pictures;
-    detailVideos.value = row.videos;
+    detailRow.value = row;
+    updateDetailDialog(detailRow.value);
+  };
+  // 上一个
+  const handleChangePrevious = () => {
+    detailRow.value = detailPreviousRow.value;
+    updateDetailDialog(detailRow.value);
+  };
+  // 下一个
+  const handleChangeNext = () => {
+    detailRow.value = detailNextRow.value;
+    updateDetailDialog(detailRow.value);
   };
 
   // 添加
@@ -304,8 +350,6 @@
   });
 
   onBeforeMount(() => {
-    // getAIOptions();
-    // getManualOptions();
     getLocationOptions();
     getAIMainOptions();
     getManualMainOptions();