zhangjie 1 жил өмнө
parent
commit
5790f206eb

+ 12 - 0
src/assets/icons/icon-clear.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="10.5px" height="11.375px" viewBox="0 0 10.5 11.375" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-清空</title>
+    <g id="SOP管理" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="10.4-教务处SOP管理-筛选条件-状态" transform="translate(-861.75, -419.875)">
+            <g id="icon-清空" transform="translate(860, 419)">
+                <rect id="clear-(Background)" opacity="0" x="0" y="0" width="14" height="14"></rect>
+                <path d="M6.125,3.5 L7.875,3.5 L7.875,1.75 L6.125,1.75 L6.125,3.5 Z M8.75,1.75 L8.75,3.5 L11.375,3.5 C11.8582625,3.5 12.25,3.891755 12.25,4.375 L12.25,6.125 C12.25,6.56623625 11.9233625,6.93119875 11.498725,6.99131125 L12.1072875,11.251275 C12.182625,11.778375 11.7735625,12.25 11.241125,12.25 L2.75888375,12.25 C2.2264025,12.25 1.817375,11.778375 1.8926775,11.251275 L2.50124,6.99131125 C2.0766025,6.93119875 1.75,6.56623625 1.75,6.125 L1.75,4.375 C1.75,3.891755 2.141755,3.5 2.625,3.5 L5.25,3.5 L5.25,1.75 C5.25,1.266755 5.641755,0.875 6.125,0.875 L7.875,0.875 C8.358245,0.875 8.75,1.266755 8.75,1.75 Z M10.6161125,6.125 L11.375,6.125 L11.375,4.375 L7.875,4.375 L6.125,4.375 L2.625,4.375 L2.625,6.125 L3.3838875,6.125 L10.6161125,6.125 Z M10.6161125,7 L3.3838875,7 L2.75888375,11.375 L4.375,11.375 L4.375,9.625 L5.25,9.625 L5.25,11.375 L6.5625,11.375 L6.5625,9.625 L7.4375,9.625 L7.4375,11.375 L8.75,11.375 L8.75,9.625 L9.625,9.625 L9.625,11.375 L11.241125,11.375 L10.6161125,7 Z" id="clear" fill="#165DFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 1 - 1
src/style/tdesign-reset.less

@@ -62,7 +62,7 @@
   }
 }
 .t-button {
-  .svg-icon {
+  &:not(.t-button--icon-only) .svg-icon {
     margin-right: 8px;
     width: 13px;
     height: 13px;

+ 81 - 0
src/views/sop/components/select-filter/config.js

@@ -0,0 +1,81 @@
+const fieldCodes = [
+  'NUMBER',
+  'DATE',
+  'TEXT',
+  'SINGLE_SELECT',
+  'MULTIPLE_SELECT',
+  'CHECKBOX',
+  'TEXTAREA',
+  'TABLE',
+  'RADIO',
+  'FILE',
+  'DEVICE_IN_TABLE',
+];
+
+export const operatorConfig = [
+  {
+    label: '等于',
+    value: 'EQ',
+    range: [
+      'NUMBER',
+      'TEXT',
+      'SINGLE_SELECT',
+      'MULTIPLE_SELECT',
+      'CHECKBOX',
+      'RADIO',
+    ],
+  },
+  {
+    label: '不等于',
+    value: 'NE',
+    range: [
+      'NUMBER',
+      'TEXT',
+      'SINGLE_SELECT',
+      'MULTIPLE_SELECT',
+      'CHECKBOX',
+      'RADIO',
+    ],
+  },
+  {
+    label: '大于',
+    value: 'GT',
+    range: ['NUMBER', 'DATE'],
+  },
+  {
+    label: '小于',
+    value: 'LT',
+    range: ['NUMBER', 'DATE'],
+  },
+  {
+    label: '大于等于',
+    value: 'GE',
+    range: ['NUMBER', 'DATE'],
+  },
+  {
+    label: '小于等于',
+    value: 'LE',
+    range: ['NUMBER', 'DATE'],
+  },
+  {
+    label: '选择范围',
+    value: 'RANGE',
+    range: ['DATE'],
+  },
+  {
+    label: '模糊查询',
+    value: 'LIKE',
+    range: ['TEXT', 'TEXTAREA'],
+  },
+];
+
+export function getOperatorByCode(code) {
+  return operatorConfig
+    .filter((item) => item.range.includes(code))
+    .map((item) => {
+      return {
+        label: item.label,
+        value: item.value,
+      };
+    });
+}

+ 92 - 0
src/views/sop/components/select-filter/field-value.vue

@@ -0,0 +1,92 @@
+<template>
+  <t-input-number
+    v-if="isNumber"
+    v-model="fieldVal"
+    theme="column"
+    :decimalPlaces="0"
+    style="width: 100%"
+    @change="emitChange"
+  ></t-input-number>
+  <t-input
+    v-if="isInput"
+    v-model="fieldVal"
+    placeholder="请输入"
+    style="width: 100%"
+    @change="emitChange"
+  ></t-input>
+  <t-select
+    v-if="isSelect"
+    v-model="fieldVal"
+    multiple
+    :options="field.options || []"
+    placeholder="请选择"
+    style="width: 100%"
+    @change="emitChange"
+  ></t-select>
+  <template v-if="isDate">
+    <t-date-range-picker
+      v-if="field.operator === 'RANGE'"
+      v-model="fieldVal"
+      value-type="time-stamp"
+      style="width: 100%"
+      @change="emitChange"
+    />
+    <t-date-picker
+      v-else
+      v-model="fieldVal"
+      value-type="time-stamp"
+      style="width: 100%"
+      @change="emitChange"
+    />
+  </template>
+</template>
+
+<script setup lang="jsx" name="FieldValue">
+import { ref, computed, watch } from 'vue';
+
+const emit = defineEmits(['update:modelValue', 'change']);
+const props = defineProps({
+  modelValue: { type: [Number, String, Array], default: undefined },
+  field: {
+    type: Object,
+    default() {
+      return {
+        code: '',
+        operator: '',
+        options: [],
+      };
+    },
+  },
+});
+let fieldVal = ref(null);
+
+const isNumber = computed(() => {
+  return ['NUMBER'].includes(props.field.code);
+});
+const isDate = computed(() => {
+  return ['DATE'].includes(props.field.code);
+});
+const isInput = computed(() => {
+  return ['TEXT', 'TEXTAREA'].includes(props.field.code);
+});
+const isSelect = computed(() => {
+  return ['SINGLE_SELECT', 'MULTIPLE_SELECT', 'CHECKBOX', 'RADIO'].includes(
+    props.field.code
+  );
+});
+
+function emitChange() {
+  emit('update:modelValue', fieldVal.value);
+  emit('change', fieldVal.value);
+}
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    fieldVal.value = val;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 216 - 0
src/views/sop/components/select-filter/index.vue

@@ -0,0 +1,216 @@
+<template>
+  <t-popup class="select-filter" placement="bottom-left" :visible="visible">
+    <t-button variant="outline" @click="toggleVisible">
+      <template #icon><svg-icon name="filter" color="#262626" /></template>
+      筛选条件
+    </t-button>
+    <template #content>
+      <div class="filter-box">
+        <div class="filter-header">
+          <p>筛选出符合以下所有内容</p>
+        </div>
+        <div class="filter-body">
+          <t-button theme="primary" size="small" @click="addHandle">
+            <template #icon
+              ><svg-icon name="add-circle" color="#fff" /></template
+            >添加过滤条件
+          </t-button>
+          <div class="filter-list">
+            <div v-for="item in dataList" :key="item.key" class="filter-item">
+              <div class="filter-field">
+                <t-select
+                  v-model="item.fieldId"
+                  filterable
+                  @change="fieldChange(item)"
+                >
+                  <t-option
+                    v-for="item in metadata"
+                    :key="item.fieldId"
+                    :label="item.fieldTitle"
+                    :value="item.fieldId"
+                  />
+                </t-select>
+              </div>
+              <div class="filter-operator">
+                <t-select
+                  v-model="item.operator"
+                  @change="operatorChange(item)"
+                >
+                  <t-option
+                    v-for="item in item.operatorList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </t-select>
+              </div>
+              <div class="filter-value">
+                <field-value
+                  v-model="item.fieldValue"
+                  :field="item"
+                ></field-value>
+              </div>
+              <div class="filter-action">
+                <t-button
+                  shape="square"
+                  variant="text"
+                  @click="deleteHandle(item)"
+                >
+                  <template #icon
+                    ><svg-icon name="delete" color="#262626"
+                  /></template>
+                </t-button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="filter-footer">
+          <t-button variant="outline" size="small" @click="clearHandle">
+            <template #icon><svg-icon name="clear" color="#262626" /></template
+            >清除
+          </t-button>
+          <t-space :size="10">
+            <t-button theme="default" size="small" @click="toggleVisible"
+              >取消</t-button
+            >
+            <t-button theme="primary" size="small" @click="confirmHandle"
+              >确定</t-button
+            >
+          </t-space>
+        </div>
+      </div>
+    </template>
+  </t-popup>
+</template>
+
+<script setup name="SelectFilter">
+import { randomCode } from '@/utils/tool';
+import { ref, watch, computed, reactive } from 'vue';
+import { getOperatorByCode } from './config';
+import FieldValue from './field-value.vue';
+import { MessagePlugin } from 'tdesign-vue-next';
+
+const emit = defineEmits(['confirm']);
+const props = defineProps({
+  metadata: {
+    type: Array,
+    default() {
+      return [];
+    },
+  },
+});
+
+const visible = ref(false);
+const toggleVisible = () => {
+  visible.value = !visible.value;
+};
+
+const dataList = ref([]);
+
+const fieldChange = (row) => {
+  const metad = props.metadata.find((item) => item.fieldId === row.fieldId);
+  row.code = metad.code;
+  row.operatorList = getOperatorByCode(metad.code);
+  row.fieldValue = null;
+  row.operator = '';
+
+  // value
+  const selections = ['SINGLE_SELECT', 'MULTIPLE_SELECT', 'CHECKBOX', 'RADIO'];
+  if (selections.includes(row.code)) row.fieldValue = [];
+};
+const operatorChange = (row) => {
+  row.fieldValue = null;
+  if (row.operator === 'RANGE') row.fieldValue = [];
+};
+
+const addHandle = () => {
+  dataList.value.push(
+    reactive({
+      key: randomCode(),
+      code: 'TEXT',
+      fieldId: null,
+      operator: '',
+      fieldValue: null,
+      operatorList: [],
+      options: [],
+    })
+  );
+};
+const deleteHandle = (row) => {
+  dataList.value = dataList.value.filter((item) => item.key !== row.key);
+};
+const clearHandle = () => {
+  dataList.value = [];
+  emit('confirm', dataList.value);
+  toggleVisible();
+};
+const confirmHandle = () => {
+  if (
+    dataList.value.some(
+      (item) => !item.fieldId || !item.operator || !item.fieldValue
+    )
+  ) {
+    MessagePlugin.error('有字段未完成设置!');
+    return;
+  }
+  emit('confirm', dataList.value);
+  toggleVisible();
+};
+</script>
+
+<style lang="less" scoped>
+.filter-box {
+  width: 640px;
+
+  .filter-header {
+    height: 32px;
+    margin-bottom: 6px;
+    color: @dark-text-color-2;
+    padding: 6px 12px 0;
+  }
+  .filter-body {
+    min-height: 200px;
+    padding: 0 12px 12px;
+  }
+  .filter-list {
+    margin-top: 6px;
+  }
+  .filter-item {
+    font-size: 0;
+
+    &:not(:first-child) {
+      margin-top: 12px;
+    }
+
+    & > div {
+      display: inline-block;
+      vertical-align: top;
+      font-size: 14px;
+
+      &:not(:first-child) {
+        margin-left: 6px;
+      }
+    }
+  }
+  .filter-field {
+    width: 180px;
+  }
+  .filter-operator {
+    width: 120px;
+  }
+  .filter-value {
+    width: 260px;
+  }
+  .filter-action {
+    width: 32px;
+    text-align: center;
+  }
+  .filter-footer {
+    border-top: 1px solid @light-border-color;
+    padding: 12px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+}
+</style>

+ 20 - 14
src/components/common/select-metadata/index.vue → src/views/sop/components/select-metadata.vue

@@ -23,9 +23,9 @@
         >
           <t-option
             v-for="item in optionList"
-            :key="item.value"
-            :label="item.label"
-            :value="item.value"
+            :key="item.fieldId"
+            :label="item.fieldTitle"
+            :value="item.fieldId"
           />
         </t-select>
       </div>
@@ -41,6 +41,7 @@ import { metadataListApi } from '@/api/sop';
 const emit = defineEmits(['update:modelValue', 'change']);
 const props = defineProps({
   modelValue: { type: [Number, String, Array], default: '' },
+  type: { type: String, default: 'OFFICE_SOP_FLOW' },
 });
 const attrs = useAttrs();
 
@@ -53,17 +54,16 @@ let selected = ref('');
 let optionList = ref([]);
 const search = async () => {
   optionList.value = [];
-  const res = await metadataListApi({ type: 'OFFICE_SOP_FLOW' }).catch(
-    () => {}
-  );
+  const res = await metadataListApi({ type: props.type }).catch(() => {});
   if (!res) return;
+  optionList.value = res;
 
-  optionList.value = res.map((item) => {
-    return {
-      label: item.fieldTitle,
-      value: item.fieldName,
-    };
-  });
+  // --
+  // let types = [];
+  // res.forEach((item) => {
+  //   if (!types.includes(item.code)) types.push(item.code);
+  // });
+  // console.log(types);
 };
 onMounted(() => {
   search();
@@ -72,9 +72,9 @@ onMounted(() => {
 const onChange = () => {
   const selectedData = isMultiple.value
     ? optionList.value.filter(
-        (item) => selected.value && selected.value.includes(item.value)
+        (item) => selected.value && selected.value.includes(item.fieldName)
       )
-    : optionList.value.filter((item) => selected.value === item.value);
+    : optionList.value.filter((item) => selected.value === item.fieldName);
   emit('update:modelValue', selected.value);
   emit('change', isMultiple.value ? selectedData : selectedData[0]);
 };
@@ -113,5 +113,11 @@ watch(
   :deep(.t-select__list) {
     padding: 0;
   }
+  :deep(.t-input__prefix) {
+    display: none !important;
+  }
+  :deep(.t-input) {
+    display: flex !important;
+  }
 }
 </style>

+ 33 - 11
src/views/sop/sop-manage/office-sop/index.vue

@@ -12,6 +12,8 @@
       <select-metadata
         v-model="params.formWidgetMetadataList"
         multiple
+        type="OFFICE_SOP_FLOW"
+        @change="metadataChange"
       ></select-metadata>
       <t-button variant="outline" @click="handleRefresh">
         <template #icon><svg-icon name="refresh" color="#262626" /></template>
@@ -38,6 +40,15 @@
       <template #service="{ item, params }">
         <select-service-unit v-model="params[item.prop]"></select-service-unit>
       </template>
+      <template #buttons>
+        <t-space :size="16">
+          <select-filter
+            :metadata="formWidgetMetadataList"
+            @confirm="filterConfirm"
+          ></select-filter>
+          <t-button theme="primary" @click="search">搜索</t-button>
+        </t-space>
+      </template>
     </SearchForm>
 
     <div class="flex-1 page-wrap">
@@ -157,6 +168,8 @@ import { ref, reactive, computed } from 'vue';
 import useFetchTable from '@/hooks/useFetchTable';
 import { sopListApi, sopBatchCancelApi } from '@/api/sop';
 import { timestampFilter } from '@/utils/filter';
+import SelectFilter from '../../components/select-filter/index.vue';
+import SelectMetadata from '../../components/select-metadata.vue';
 import SopStepDialog from '../sop-step/sop-step-dialog.vue';
 import QualityIssueDialog from '../quality-issue/quality-issue-dialog.vue';
 import PlanChangeDialog from '../plan-change/plan-change-dialog.vue';
@@ -212,17 +225,9 @@ const fields = ref([
     cell: 'service',
   },
   {
-    type: 'buttons',
-    colSpan: 3,
-    children: [
-      {
-        type: 'button',
-        text: '搜索',
-        onClick: () => {
-          search();
-        },
-      },
-    ],
+    prop: 'buttons',
+    colSpan: 4,
+    labelWidth: 16,
   },
 ]);
 const params = reactive({
@@ -241,6 +246,23 @@ const {
   onChange,
 } = useFetchTable(sopListApi, { params: transParams });
 
+const formWidgetMetadataList = ref([]);
+const metadataChange = (vals) => {
+  formWidgetMetadataList.value = vals;
+};
+
+const filterData = ref([]);
+const filterConfirm = (data) => {
+  // console.log(data);
+  filterData.value = data.map((item) => {
+    return {
+      fieldId: item.fieldId,
+      operator: item.operator,
+      fieldValue: item.fieldValue,
+    };
+  });
+};
+
 const handleSort = () => {
   // TODO:
 };

+ 2 - 0
src/views/sop/sop-manage/student-sop/index.vue

@@ -12,6 +12,7 @@
       <select-metadata
         v-model="params.formWidgetMetadataList"
         multiple
+        type="CLOUD_MARK_SOP_FLOW"
       ></select-metadata>
       <t-button variant="outline" @click="handleRefresh">
         <template #icon><svg-icon name="refresh" color="#262626" /></template>
@@ -150,6 +151,7 @@ import { ref, reactive, computed } from 'vue';
 import useFetchTable from '@/hooks/useFetchTable';
 import { sopListApi, sopBatchCancelApi } from '@/api/sop';
 import { timestampFilter } from '@/utils/filter';
+import SelectMetadata from '../../components/select-metadata.vue';
 import SopStepDialog from '../sop-step/sop-step-dialog.vue';
 import QualityIssueDialog from '../quality-issue/quality-issue-dialog.vue';
 import PlanChangeDialog from '../plan-change/plan-change-dialog.vue';