zhangjie 1 year ago
parent
commit
4329dea19d
34 changed files with 704 additions and 553 deletions
  1. 2 0
      src/utils/request.js
  2. 0 27
      src/views/sop/components/CHECKBOX.vue
  3. 0 18
      src/views/sop/components/DATE.vue
  4. 0 17
      src/views/sop/components/NUMBER.vue
  5. 0 42
      src/views/sop/components/RADIO.vue
  6. 0 30
      src/views/sop/components/RADIO_WITH_INPUT.vue
  7. 0 36
      src/views/sop/components/SELECT.vue
  8. 0 8
      src/views/sop/components/SIGN.vue
  9. 0 162
      src/views/sop/components/TABLE.vue
  10. 0 16
      src/views/sop/components/TEXT.vue
  11. 0 12
      src/views/sop/components/TEXTAREA.vue
  12. 0 23
      src/views/sop/components/UPLOAD.vue
  13. 44 0
      src/views/sop/components/dynamic-form-item/CHECKBOX.vue
  14. 35 0
      src/views/sop/components/dynamic-form-item/DATE.vue
  15. 0 0
      src/views/sop/components/dynamic-form-item/DEVICE_IN_TABLE.vue
  16. 0 0
      src/views/sop/components/dynamic-form-item/DEVICE_OUT_TABLE.vue
  17. 0 0
      src/views/sop/components/dynamic-form-item/FORM_GROUP_TITLE.vue
  18. 35 0
      src/views/sop/components/dynamic-form-item/NUMBER.vue
  19. 0 0
      src/views/sop/components/dynamic-form-item/POP_SELECT.vue
  20. 46 0
      src/views/sop/components/dynamic-form-item/RADIO.vue
  21. 58 0
      src/views/sop/components/dynamic-form-item/RADIO_WITH_INPUT.vue
  22. 58 0
      src/views/sop/components/dynamic-form-item/SELECT.vue
  23. 5 0
      src/views/sop/components/dynamic-form-item/SIGN.vue
  24. 33 0
      src/views/sop/components/dynamic-form-item/TEXT.vue
  25. 33 0
      src/views/sop/components/dynamic-form-item/TEXTAREA.vue
  26. 11 16
      src/views/sop/components/dynamic-form-item/UPLOAD_IMAGE.vue
  27. 87 0
      src/views/sop/components/dynamic-form-item/index.vue
  28. 79 0
      src/views/sop/components/dynamic-form-item/table/edit-column-dialog.vue
  29. 143 0
      src/views/sop/components/dynamic-form-item/table/index.vue
  30. 0 95
      src/views/sop/components/my-form-item.vue
  31. 0 7
      src/views/sop/components/select-metadata.vue
  32. 3 3
      src/views/sop/sop-manage/quality-issue/index.vue
  33. 3 15
      src/views/sop/sop-manage/sop-step/index.vue
  34. 29 26
      src/views/work-hours/work-hours-manage/work-attendance-detail/index.vue

+ 2 - 0
src/utils/request.js

@@ -63,6 +63,7 @@ function createService() {
       if (response.data.code && response.data.code !== 200) {
         Message.error({
           content: response.data.message,
+          closeBtn: true,
           // icon: () => h(IconFaceFrownFill),
         });
       }
@@ -80,6 +81,7 @@ function createService() {
       }
       const err = (text) => {
         Message.error({
+          closeBtn: true,
           content:
             (error?.response?.data?.message?.includes('权限')
               ? error?.config?.url + ' '

+ 0 - 27
src/views/sop/components/CHECKBOX.vue

@@ -1,27 +0,0 @@
-<template>
-  <t-checkbox-group
-    style="min-height: 32px"
-    v-model="value"
-    :name="config.formName"
-    :options="options"
-    @change="change"
-  ></t-checkbox-group>
-</template>
-
-<script setup name="CHECKBOX">
-import { ref, computed } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(config.value || []);
-const change = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-const options = computed(() => {
-  return !config.options
-    ? []
-    : typeof config.options === 'string'
-    ? JSON.parse(config.options)
-    : config.options;
-});
-</script>
-
-<style></style>

+ 0 - 18
src/views/sop/components/DATE.vue

@@ -1,18 +0,0 @@
-<template>
-  <t-date-picker
-    v-model="value"
-    @change="change"
-    style="width: 100%"
-    value-type="time-stamp"
-    :disabled="!config.writable"
-  />
-</template>
-<script setup name="DATE">
-import { ref } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(config.value || '');
-const change = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-</script>
-<style></style>

+ 0 - 17
src/views/sop/components/NUMBER.vue

@@ -1,17 +0,0 @@
-<template>
-  <t-input-number
-    theme="normal"
-    v-model="value"
-    @input="onInput"
-    :disabled="!config.writable"
-  ></t-input-number>
-</template>
-<script setup name="NUMBER">
-import { ref } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(config.value || '');
-const onInput = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-</script>
-<style></style>

+ 0 - 42
src/views/sop/components/RADIO.vue

@@ -1,42 +0,0 @@
-<template>
-  <t-radio-group
-    style="min-height: 32px; width: 100%"
-    v-model="value"
-    allow-uncheck
-    :name="config.binding"
-    :options="options"
-    @change="change"
-    :disabled="config.disabled"
-  ></t-radio-group>
-</template>
-
-<script setup name="RADIO">
-import { ref, computed, onMounted } from 'vue';
-const emit = defineEmits(['ruleDispatch', 'initFormKey']);
-const { config, onChange } = defineProps(['config', 'onChange']);
-// onMounted(() => {
-//   let ruleItem = config.required
-//     ? {
-//         required: true,
-//         message: `${config.title}不能为空`,
-//         type: 'error',
-//         trigger: 'change',
-//       }
-//     : null;
-//   ruleItem && emit('ruleDispatch', [{ [config.formName]: [ruleItem] }]);
-//   emit('initFormKey', config.formName);
-// });
-const value = ref(config.value || '');
-const change = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-const options = computed(() => {
-  return !config.options
-    ? []
-    : typeof config.options === 'string'
-    ? JSON.parse(config.options)
-    : config.options;
-});
-</script>
-
-<style></style>

+ 0 - 30
src/views/sop/components/RADIO_WITH_INPUT.vue

@@ -1,30 +0,0 @@
-<template>
-  <t-radio-group
-    style="min-height: 32px; width: 100%"
-    v-model="value"
-    allow-uncheck
-    :name="config.binding"
-    :options="config.options || []"
-    @change="change"
-  >
-    <t-radio
-      :value="item.value"
-      v-for="item in config.options"
-      :key="item.value"
-      >{{ item.label }}</t-radio
-    >
-    <t-input v-model="inputValue" placeholder="请填写具体内容"></t-input>
-  </t-radio-group>
-</template>
-
-<script setup name="RADIOWITHINPUT">
-import { ref } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(config.value || '');
-const inputValue = ref('');
-const change = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-</script>
-
-<style></style>

+ 0 - 36
src/views/sop/components/SELECT.vue

@@ -1,36 +0,0 @@
-<template>
-  <t-select
-    v-model="value"
-    @change="change"
-    :options="options"
-    :multiple="config.code === 'MULTIPLE_SELECT'"
-    :disabled="!config.writable"
-  ></t-select>
-</template>
-<script setup name="SELECT">
-import { ref, onMounted } from 'vue';
-import { useRequest } from 'vue-request';
-import { request } from '@/utils/request.js';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(
-  config.code === 'MULTIPLE_SELECT' ? config.value || [] : config.value || ''
-);
-const options = ref([]);
-const change = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-const getOptionsApi = () => {
-  request({
-    url: config.dataGrid,
-    method: 'post',
-  });
-};
-const getOptions = async () => {
-  const { data } = useRequest(getOptionsApi);
-  //todo 将接口数据转换成options
-};
-onMounted(() => {
-  getOptions();
-});
-</script>
-<style></style>

+ 0 - 8
src/views/sop/components/SIGN.vue

@@ -1,8 +0,0 @@
-<template>
-  <div>手写签名组件待定...</div>
-</template>
-<script setup name="SIGN">
-import { ref } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-</script>
-<style></style>

+ 0 - 162
src/views/sop/components/TABLE.vue

@@ -1,162 +0,0 @@
-<template>
-  <div class="flow-table">
-    <!-- 当前示例包含:输入框、单选、多选、日期 等场景 -->
-    <t-table
-      ref="tableRef"
-      row-key="key"
-      :columns="columns"
-      :data="tableData"
-      :editable-row-keys="editableRowKeys"
-      bordered
-      @row-edit="onRowEdit"
-      @row-validate="onRowValidate"
-      @validate="onValidate"
-      size="small"
-    >
-      <template #key="{ row }">
-        <div class="flex items-center key-cell">
-          <Icon
-            name="delete"
-            class="delete-icon"
-            @click="deleteRow(row)"
-          ></Icon>
-          <span class="key-index">{{ row.key }}</span>
-        </div>
-      </template>
-    </t-table>
-    <t-button theme="primary" class="m-t-15px" @click="createOneRow">
-      <template #icon><Icon name="add"></Icon></template>
-      添加
-    </t-button>
-  </div>
-</template>
-
-<script setup name="TABLE">
-import { computed, ref, watch } from 'vue';
-import { Input, DatePicker } from 'tdesign-vue-next';
-import { Icon } from 'tdesign-icons-vue-next';
-import bus from '@/utils/bus';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const tableRef = ref();
-
-let tablePropList = config.tablePropList || [];
-const createEditOption = (code) => {
-  let obj = {};
-  if (code === 'TEXT') {
-    obj = {
-      component: Input,
-      props: {
-        clearable: true,
-        autoFocus: true,
-        autoWidth: true,
-        style: { width: '100%' },
-      },
-      rules: [{ required: true, message: '不能为空' }],
-      showEditIcon: false,
-      validateTrigger: 'blur',
-    };
-  } else if (code === 'DATE') {
-    obj = {
-      component: DatePicker,
-      rules: [{ required: true, message: '不能为空' }],
-      showEditIcon: false,
-      validateTrigger: 'blur',
-      props: {
-        style: { width: '100%' },
-      },
-    };
-  }
-  return obj;
-};
-
-const columns = computed(() => {
-  let arr = tablePropList.map((item) => {
-    return {
-      title: item.title,
-      colKey: item.tdName,
-      minWidth: 160,
-      edit: createEditOption(item.tdFormWidget.code),
-    };
-  });
-  let firstColumn = {
-    title: '',
-    coleKey: 'key',
-    cell: 'key',
-    align: 'left',
-    width: 80,
-  };
-  return [firstColumn, ...arr];
-});
-const tableData = ref([]);
-const resetKeys = () => {
-  tableData.value.forEach((item, index) => {
-    item.key = index + 1 + '';
-  });
-};
-
-const createOneRow = () => {
-  let rowData = config.tablePropList.reduce((row, item) => {
-    row[item.tdName] = '';
-    return row;
-  }, {});
-  tableData.value.push({ ...rowData });
-  resetKeys();
-};
-
-createOneRow();
-
-const onRowEdit = (params) => {
-  const { row, editedRow } = params;
-  for (let k in editedRow) {
-    row[k] = editedRow[k];
-  }
-};
-const onRowValidate = () => {};
-const onValidate = () => {};
-
-const editableRowKeys = computed(() => {
-  return tableData.value.map((item) => item.key);
-});
-const deleteRow = (row) => {
-  let index = tableData.value.findIndex((item) => item.key == row.key);
-  tableData.value.splice(index, 1);
-  resetKeys();
-};
-
-const validate = () => {
-  tableRef.value.validateTableData().then((result) => {
-    if (!Object.keys(result.result).length) {
-      onChange({ prop: config.formName, value: tableData.value });
-      bus.emit('tablePassed');
-    } else {
-      console.log('表格验证未通过', result.result);
-      bus.emit('tableFailed');
-      onChange({ prop: config.formName, value: '' });
-    }
-  });
-};
-bus.on('checkTable', () => {
-  validate();
-});
-</script>
-
-<style lang="less" name="TABLE">
-.flow-table {
-  width: 100%;
-  margin-bottom: 25px;
-  .key-cell {
-    .key-index {
-      font-size: 14px;
-    }
-    .delete-icon {
-      font-size: 18px;
-      color: #777;
-      margin-right: 10px;
-      cursor: pointer;
-      &:hover {
-        color: var(--td-brand-color);
-      }
-    }
-  }
-}
-</style>

+ 0 - 16
src/views/sop/components/TEXT.vue

@@ -1,16 +0,0 @@
-<template>
-  <t-input
-    v-model="value"
-    @input="onInput"
-    :disabled="!config.writable"
-  ></t-input>
-</template>
-<script setup name="TEXT">
-import { ref } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(config.value || '');
-const onInput = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-</script>
-<style></style>

+ 0 - 12
src/views/sop/components/TEXTAREA.vue

@@ -1,12 +0,0 @@
-<template>
-  <t-textarea v-model="value" @change="change"></t-textarea>
-</template>
-<script setup name="TEXTAREA">
-import { ref } from 'vue';
-const { config, onChange } = defineProps(['config', 'onChange']);
-const value = ref(config.value || '');
-const change = () => {
-  onChange({ prop: config.formName, value: value.value });
-};
-</script>
-<style></style>

+ 0 - 23
src/views/sop/components/UPLOAD.vue

@@ -1,23 +0,0 @@
-<template>
-  <t-upload
-    style="width: 100%"
-    v-model="files"
-    :abridge-name="[8, 6]"
-    theme="file-input"
-    placeholder="请选择文件,20MB以内"
-    :request-method="requestMethod"
-    :size-limit="{ size: 20, unit: 'MB' }"
-  ></t-upload>
-</template>
-
-<script setup name="UPLOAD">
-import { ref } from 'vue';
-const files = ref([]);
-const requestMethod = (file) => {
-  return new Promise((rs) => {
-    rs({ status: 'success' });
-  });
-};
-</script>
-
-<style></style>

+ 44 - 0
src/views/sop/components/dynamic-form-item/CHECKBOX.vue

@@ -0,0 +1,44 @@
+<template>
+  <t-checkbox-group
+    v-model="valueData"
+    :options="options"
+    :disabled="!config.writable"
+    style="min-height: 32px"
+    @change="emitChange"
+  ></t-checkbox-group>
+</template>
+
+<script setup name="CHECKBOX">
+import { ref, computed, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: Array },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref([]);
+const options = computed(() => {
+  const config = props.config || {};
+  return !config.options
+    ? []
+    : typeof config.options === 'string'
+    ? JSON.parse(config.options)
+    : config.options;
+});
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val || [];
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 35 - 0
src/views/sop/components/dynamic-form-item/DATE.vue

@@ -0,0 +1,35 @@
+<template>
+  <t-date-picker
+    v-model="valueData"
+    value-type="time-stamp"
+    :disabled="!config.writable"
+    style="width: 100%"
+    @change="emitChange"
+  />
+</template>
+<script setup name="DATE">
+import { ref, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: Number },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref(null);
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val || null;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 0 - 0
src/views/sop/components/DEVICE_IN_TABLE.vue → src/views/sop/components/dynamic-form-item/DEVICE_IN_TABLE.vue


+ 0 - 0
src/views/sop/components/DEVICE_OUT_TABLE.vue → src/views/sop/components/dynamic-form-item/DEVICE_OUT_TABLE.vue


+ 0 - 0
src/views/sop/components/FORM_GROUP_TITLE.vue → src/views/sop/components/dynamic-form-item/FORM_GROUP_TITLE.vue


+ 35 - 0
src/views/sop/components/dynamic-form-item/NUMBER.vue

@@ -0,0 +1,35 @@
+<template>
+  <t-input-number
+    v-model="valueData"
+    theme="normal"
+    :disabled="!config.writable"
+    style="width: 100%"
+    @change="emitChange"
+  ></t-input-number>
+</template>
+<script setup name="NUMBER">
+import { ref, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: Number },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref(null);
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val || undefined;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 0 - 0
src/views/sop/components/POP_SELECT.vue → src/views/sop/components/dynamic-form-item/POP_SELECT.vue


+ 46 - 0
src/views/sop/components/dynamic-form-item/RADIO.vue

@@ -0,0 +1,46 @@
+<template>
+  <t-radio-group
+    v-model="valueData"
+    allow-uncheck
+    :options="options"
+    :disabled="!config.writable"
+    style="min-height: 32px; width: 100%"
+    @change="emitChange"
+  ></t-radio-group>
+</template>
+
+<script setup name="RADIO">
+import { ref, computed, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: String },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref(null);
+const options = computed(() => {
+  const config = props.config || {};
+
+  return !config.options
+    ? []
+    : typeof config.options === 'string'
+    ? JSON.parse(config.options)
+    : config.options;
+});
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val || null;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 58 - 0
src/views/sop/components/dynamic-form-item/RADIO_WITH_INPUT.vue

@@ -0,0 +1,58 @@
+<template>
+  <t-radio-group
+    v-model="valueData"
+    allow-uncheck
+    :options="options"
+    style="min-height: 32px; width: 100%"
+    :disabled="!config.writable"
+    @change="emitChange"
+  >
+    <t-radio
+      v-for="item in config.options"
+      :value="item.value"
+      :key="item.value"
+      >{{ item.label }}</t-radio
+    >
+    <t-input
+      v-model="inputData"
+      :disabled="!config.writable"
+      placeholder="请填写具体内容"
+    ></t-input>
+  </t-radio-group>
+</template>
+
+<script setup name="RADIOWITHINPUT">
+import { ref, computed, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: String },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+const valueData = ref('');
+const inputData = ref('');
+
+const options = computed(() => {
+  const config = props.config || {};
+  return !config.options
+    ? []
+    : typeof config.options === 'string'
+    ? JSON.parse(config.options)
+    : config.options;
+});
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val || null;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 58 - 0
src/views/sop/components/dynamic-form-item/SELECT.vue

@@ -0,0 +1,58 @@
+<template>
+  <t-select
+    v-model="valueData"
+    :options="options"
+    :multiple="isMultiple"
+    :disabled="!config.writable"
+    @change="emitChange"
+  ></t-select>
+</template>
+<script setup name="SELECT">
+import { ref, onMounted, computed, watch } from 'vue';
+import { useRequest } from 'vue-request';
+import { request } from '@/utils/request.js';
+
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: [String, Array] },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref(null);
+const isMultiple = computed(() => {
+  return props.config.code === 'MULTIPLE_SELECT';
+});
+
+const options = ref([]);
+const getOptionsApi = () => {
+  request({
+    url: config.dataGrid,
+    method: 'post',
+  });
+};
+const getOptions = async () => {
+  const { data } = useRequest(getOptionsApi);
+  // TODO: 将接口数据转换成options
+  options.value = data;
+};
+
+onMounted(() => {
+  getOptions();
+});
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val || (isMultiple.value ? [] : '');
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 5 - 0
src/views/sop/components/dynamic-form-item/SIGN.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>手写签名组件待定...</div>
+</template>
+
+<script setup name="SIGN"></script>

+ 33 - 0
src/views/sop/components/dynamic-form-item/TEXT.vue

@@ -0,0 +1,33 @@
+<template>
+  <t-input
+    v-model="valueData"
+    :disabled="!config.writable"
+    @change="emitChange"
+  ></t-input>
+</template>
+<script setup name="TEXT">
+import { ref, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: String },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref('');
+
+const emitChange = () => {
+  emit('update:modelValue', valueData.value);
+  emit('change', valueData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 33 - 0
src/views/sop/components/dynamic-form-item/TEXTAREA.vue

@@ -0,0 +1,33 @@
+<template>
+  <t-textarea
+    v-model="valueData"
+    :disabled="!config.writable"
+    @change="emitChange"
+  ></t-textarea>
+</template>
+<script setup name="TEXTAREA">
+import { ref, watch } from 'vue';
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: String },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const valueData = ref('');
+
+const emitChange = () => {
+  emit('update:modelValue', tableData.value);
+  emit('change', tableData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 11 - 16
src/views/sop/components/UPLOAD_IMAGE.vue → src/views/sop/components/dynamic-form-item/UPLOAD_IMAGE.vue

@@ -10,18 +10,20 @@
     :upload-all-files-in-one-request="false"
     multiple
     :max="config.length || 3"
+    :disabled="!config.writable"
     :before-upload="handleBeforeUpload"
     :request-method="upload"
     @fail="handleFail"
-    @change="change"
+    @change="handleChange"
   >
   </t-upload>
 </template>
 <script setup name="UploadImage">
-import { ref, watch } from 'vue';
+import { ref } from 'vue';
 import { MessagePlugin } from 'tdesign-vue-next';
 import { uploadFiles } from '@/api/common';
 import { getFileMD5 } from '@/utils/crypto';
+
 const props = defineProps({
   theme: {
     type: String,
@@ -39,10 +41,10 @@ const props = defineProps({
   config: {
     type: Object,
   },
-  onChange: {
-    type: Function,
-  },
+  modelValue: { type: Array },
 });
+const emit = defineEmits(['update:modelValue', 'change']);
+
 const files = ref([]);
 const checkFileFormat = (fileType) => {
   const _file_format = '.' + fileType.split('.').pop().toLocaleLowerCase();
@@ -85,16 +87,9 @@ const upload = async (files) => {
     return { status: 'fail', error: '上传失败' };
   }
 };
-watch(files.value, () => {
-  onChange({
-    prop: props.config.formName,
-    value: files.value.map((item) => item.response),
-  });
-});
-const change = () => {
-  props.onChange({
-    prop: props.config.formName,
-    value: files.value.map((item) => item.response),
-  });
+const handleChange = () => {
+  const data = files.value.map((item) => item.response);
+  emit('update:modelValue', data);
+  emit('change', data);
 };
 </script>

+ 87 - 0
src/views/sop/components/dynamic-form-item/index.vue

@@ -0,0 +1,87 @@
+<template>
+  <t-form-item
+    :label="isBigTitle ? '' : config.title"
+    :name="config.formName"
+    class="my-form-item"
+  >
+    <div v-if="isBigTitle" class="top-label flex items-center">
+      <p>{{ config.title }}</p>
+    </div>
+    <component
+      v-else
+      :is="tabComp"
+      v-model="valueData"
+      :config="config"
+      @change="emitChange"
+    ></component>
+  </t-form-item>
+</template>
+<script setup name="DynamicFormItem">
+import { computed, ref, watch } from 'vue';
+import TEXT from './TEXT.vue';
+import SELECT from './SELECT.vue';
+import DATE from './DATE';
+import TABLE from './table/index.vue';
+import RADIO from './RADIO.vue';
+import CHECKBOX from './CHECKBOX.vue';
+import TEXTAREA from './TEXTAREA.vue';
+import UPLOADIMAGE from './UPLOAD_IMAGE.vue';
+import RADIOWITHINPUT from './RADIO_WITH_INPUT.vue';
+import NUMBER from './NUMBER.vue';
+
+import SIGN from './SIGN.vue';
+
+import DEVICEOUTTABLE from './DEVICE_OUT_TABLE.vue';
+import DEVICEINTABLE from './DEVICE_IN_TABLE.vue';
+
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: String },
+});
+const emit = defineEmits(['change']);
+
+const valueData = ref(null);
+
+const bigTitles = ['FORM_GROUP_TITLE', 'ONLY_TITLE'];
+const isBigTitle = computed(() => {
+  return bigTitles.includes(props.config.code);
+});
+
+const tabComps = {
+  SIGN: SIGN,
+  TEXT: TEXT,
+  NUMBER: NUMBER,
+  SINGLE_SELECT: SELECT,
+  MULTIPLE_SELECT: SELECT,
+  DATE: DATE,
+  TABLE: TABLE,
+  RADIO: RADIO,
+  CHECKBOX: CHECKBOX,
+  TEXTAREA: TEXTAREA,
+  FILE: UPLOADIMAGE,
+  RADIO_WITH_INPUT: RADIOWITHINPUT,
+  DEVICE_OUT_TABLE: DEVICEOUTTABLE,
+  DEVICE_IN_TABLE: DEVICEINTABLE,
+};
+const tabComp = computed(() => {
+  return tabComps[props.config.code];
+});
+
+const emitChange = () => {
+  emit('change', {
+    prop: props.config.formName,
+    value: valueData.value,
+  });
+};
+
+watch(
+  () => props.config.value,
+  (val, oldval) => {
+    if (val === oldval) return;
+    valueData.value = val;
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 79 - 0
src/views/sop/components/dynamic-form-item/table/edit-column-dialog.vue

@@ -0,0 +1,79 @@
+<template>
+  <my-dialog
+    :visible="visible"
+    :header="title"
+    :width="500"
+    attach="body"
+    :closeOnOverlayClick="false"
+    @close="emit('update:visible', false)"
+    @opened="dialogOpened"
+  >
+    <t-form
+      ref="formRef"
+      :data="formData"
+      :rules="rules"
+      :labelWidth="140"
+      colon
+    >
+      <t-form-item
+        v-for="item in columns"
+        :key="item.colKey"
+        :label="item.title"
+        :name="rules[item.colKey] ? item.colKey : undefined"
+      >
+        <SearchFormItem
+          :item="{ ...item.comp, prop: item.colKey, label: item.title }"
+          :params="formData"
+        />
+      </t-form-item>
+    </t-form>
+    <template #foot>
+      <t-button theme="default" @click="emit('update:visible', false)"
+        >取消</t-button
+      >
+      <t-button theme="primary" @click="save">保存</t-button>
+    </template>
+  </my-dialog>
+</template>
+<script setup name="EditColumnDialog">
+import { ref, computed } from 'vue';
+// import { MessagePlugin } from 'tdesign-vue-next';
+import SearchFormItem from '@/components/global/search-form/components/search-form-item.vue';
+
+const emit = defineEmits(['update:visible', 'success']);
+const props = defineProps({
+  visible: Boolean,
+  curRow: Object,
+  columns: Array,
+});
+
+const formRef = ref(null);
+const formData = ref({});
+
+const title = computed(() => {
+  return (props.curRow?.key ? '编辑' : '新增') + '数据';
+});
+
+const rules = computed(() => {
+  let rules = {};
+  props.columns
+    .filter((item) => !!item.rules)
+    .forEach((item) => {
+      rules[item.colKey] = item.rules;
+    });
+  return rules;
+});
+
+const dialogOpened = () => {
+  formData.value = { ...props.curRow };
+};
+
+const save = async () => {
+  const valid = await formRef.value.validate();
+  if (valid !== true) return;
+
+  // MessagePlugin.success('保存成功');
+  emit('update:visible', false);
+  emit('success', formData.value);
+};
+</script>

+ 143 - 0
src/views/sop/components/dynamic-form-item/table/index.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="flow-table">
+    <!-- 当前示例包含:输入框、单选、多选、日期 等场景 -->
+    <t-table
+      ref="tableRef"
+      size="small"
+      row-key="key"
+      :columns="tableColumns"
+      :data="tableData"
+      bordered
+    >
+      <template v-if="!readonly" #operate="{ row, rowIndex }">
+        <div class="table-operations">
+          <t-link theme="primary" hover="color" @click="handleEdit(row)">
+            修改
+          </t-link>
+          <t-link theme="primary" hover="color" @click="handleDelete(rowIndex)">
+            删除
+          </t-link>
+        </div>
+      </template>
+    </t-table>
+    <t-button
+      v-if="!readonly"
+      theme="primary"
+      class="m-t-15px"
+      @click="handleAdd"
+    >
+      <template #icon><Icon name="add"></Icon></template>
+      添加
+    </t-button>
+
+    <!-- EditColumnDialog -->
+    <edit-column-dialog
+      v-model:visible="showEditColumnDialog"
+      :curRow="curRow"
+      :columns="formColumns"
+      @success="columnConfirm"
+    ></edit-column-dialog>
+  </div>
+</template>
+
+<script setup name="TABLE">
+import { computed, ref, watch } from 'vue';
+import { Icon } from 'tdesign-icons-vue-next';
+import { randomCode } from '@/utils/tool';
+import EditColumnDialog from './edit-column-dialog.vue';
+
+const props = defineProps({
+  config: { type: Object },
+  modelValue: { type: Array },
+});
+const emit = defineEmits(['update:modelValue', 'change']);
+
+const tableRef = ref();
+const tableData = ref([]);
+const showEditColumnDialog = ref(false);
+const curRow = ref(null);
+
+const readonly = computed(() => {
+  return !prop.config.writable;
+});
+
+const tableColumns = computed(() => {
+  let ncolumns = props.config.tablePropList.map((item) => {
+    return {
+      title: item.title,
+      colKey: item.tdName,
+      comp: {
+        type: item.tdFormWidget.code.toLowerCase(),
+        attrs: {
+          clearable: true,
+        },
+      },
+      rules: [
+        {
+          required: true,
+          message: '不能为空',
+          type: 'error',
+          trigger: 'change',
+        },
+      ],
+    };
+  });
+  ncolumns.push({
+    title: '管理',
+    colKey: 'operate',
+    fixed: 'right',
+    width: 120,
+  });
+  return ncolumns;
+});
+const formColumns = computed(() => {
+  return props.columns.filter((item) => item.colKey !== 'operate');
+});
+
+const rowData = props.config.tablePropList.reduce((row, item) => {
+  row[item.tdName] = '';
+  return row;
+}, {});
+
+const handleAdd = () => {
+  curRow.value = { ...rowData };
+  showEditColumnDialog.value = true;
+};
+const handleEdit = (row) => {
+  curRow.value = row;
+  showEditColumnDialog.value = true;
+};
+const handleDelete = (index) => {
+  tableData.value.splice(index, 1);
+  emitChange();
+};
+
+const columnConfirm = (data) => {
+  if (data.key) {
+    const pos = tableData.value.findIndex((item) => item.key === data.key);
+    tableData.value.splice(pos, 1, data);
+  } else {
+    data.key = randomCode();
+    tableData.value.push(data);
+  }
+  emitChange();
+};
+
+const emitChange = () => {
+  emit('update:modelValue', tableData.value);
+  emit('change', tableData.value);
+};
+
+watch(
+  () => props.modelValue,
+  (val, oldval) => {
+    if (val === oldval) return;
+    tableData.value = val.map((item) => {
+      return { ...item, key: randomCode() };
+    });
+  },
+  {
+    immediate: true,
+  }
+);
+</script>

+ 0 - 95
src/views/sop/components/my-form-item.vue

@@ -1,95 +0,0 @@
-<template>
-  <t-form-item
-    :label="isBigTitle ? '' : config.title"
-    :name="config.formName"
-    :label-width="isBigTitle || !config.title ? 0 : transLabelWidth"
-    class="my-form-item"
-  >
-    <div v-if="isBigTitle" class="top-label flex items-center">
-      <p>{{ config.title }}</p>
-    </div>
-    <RenderTest></RenderTest>
-  </t-form-item>
-</template>
-<script setup lang="jsx" name="MyFormItem">
-import { defineComponent, ref, computed } from 'vue';
-import TEXT from './TEXT.vue';
-import SELECT from './SELECT.vue';
-import DATE from './DATE';
-import UPLOAD from './UPLOAD';
-import TABLE from './TABLE';
-import FORMGROUPTITLE from './FORM_GROUP_TITLE.vue';
-import RADIO from './RADIO.vue';
-import CHECKBOX from './CHECKBOX.vue';
-import TEXTAREA from './TEXTAREA.vue';
-import UPLOADIMAGE from './UPLOAD_IMAGE.vue';
-import RADIOWITHINPUT from './RADIO_WITH_INPUT.vue';
-import SIGN from './SIGN.vue';
-import DEVICEOUTTABLE from './DEVICE_OUT_TABLE.vue';
-import DEVICEINTABLE from './DEVICE_IN_TABLE.vue';
-import NUMBER from './NUMBER.vue';
-const { config, labelWidth } = defineProps(['config', 'labelWidth']);
-const emit = defineEmits(['change']);
-const onChange = (obj) => {
-  emit('change', obj);
-};
-const bigTitles = ref(['FORM_GROUP_TITLE', 'ONLY_TITLE']);
-const isBigTitle = computed(() => {
-  return bigTitles.value.includes(config.code);
-});
-const transLabelWidth = computed(() => {
-  if (config.title.length > 13) {
-    return config.title.length * 16;
-  } else {
-    return labelWidth;
-  }
-});
-const RenderTest = defineComponent({
-  render() {
-    if (!config) {
-      return <div>你不传config我怎么玩</div>;
-    }
-    switch (config.code) {
-      case 'TEXT':
-        return <TEXT config={config} onChange={onChange}></TEXT>;
-      case 'SINGLE_SELECT':
-        return <SELECT config={config} onChange={onChange}></SELECT>;
-      case 'MULTIPLE_SELECT':
-        return <SELECT config={config} onChange={onChange}></SELECT>;
-      case 'DATE':
-        return <DATE config={config} onChange={onChange}></DATE>;
-      case 'TABLE':
-        return <TABLE config={config} onChange={onChange}></TABLE>;
-      case 'FORM_GROUP_TITLE':
-        return <FORMGROUPTITLE config={config}></FORMGROUPTITLE>;
-      case 'RADIO':
-        return <RADIO config={config} onChange={onChange}></RADIO>;
-      case 'CHECKBOX':
-        return <CHECKBOX config={config} onChange={onChange}></CHECKBOX>;
-      case 'TEXTAREA':
-        return <TEXTAREA config={config} onChange={onChange}></TEXTAREA>;
-      case 'FILE':
-        return <UPLOADIMAGE config={config} onChange={onChange}></UPLOADIMAGE>;
-      case 'RADIO_WITH_INPUT':
-        return (
-          <RADIOWITHINPUT config={config} onChange={onChange}></RADIOWITHINPUT>
-        );
-      case 'DEVICE_OUT_TABLE':
-        return <DEVICEOUTTABLE onChange={onChange}></DEVICEOUTTABLE>;
-      case 'DEVICE_IN_TABLE':
-        return <DEVICEINTABLE onChange={onChange}></DEVICEINTABLE>;
-      case 'NUMBER':
-        return <NUMBER config={config} onChange={onChange}></NUMBER>;
-      case 'SIGN':
-        return <SIGN></SIGN>;
-      case 'ONLY_TITLE':
-        return <div></div>;
-      case '':
-        return <div></div>;
-      default:
-        return <div>你传的code是什么破玩意</div>;
-    }
-  },
-});
-</script>
-<style scoped lang="less"></style>

+ 0 - 7
src/views/sop/components/select-metadata.vue

@@ -59,13 +59,6 @@ const search = async () => {
   const res = await metadataListApi({ type: props.type }).catch(() => {});
   if (!res) return;
   optionList.value = res;
-
-  // --
-  // let types = [];
-  // res.forEach((item) => {
-  //   if (!types.includes(item.code)) types.push(item.code);
-  // });
-  // console.log(types);
 };
 onMounted(() => {
   search();

+ 3 - 3
src/views/sop/sop-manage/quality-issue/index.vue

@@ -68,12 +68,12 @@
               v-for="config in curFormConfig"
               :key="config.id"
             >
-              <MyFormItem
+              <dynamic-form-item
                 v-if="config.visable"
                 :config="config"
                 :labelWidth="labelWidth"
                 @change="itemValueChange"
-              ></MyFormItem>
+              ></dynamic-form-item>
             </t-col>
           </t-row>
         </t-form>
@@ -103,7 +103,7 @@ import { issuesFeedbackSaveApi } from '@/api/project-quality';
 import { flowFormPropertiesApi } from '@/api/sop';
 import { MessagePlugin } from 'tdesign-vue-next';
 
-import MyFormItem from '../../components/my-form-item.vue';
+import DynamicFormItem from '../../components/dynamic-form-item/index.vue';
 
 const props = defineProps({
   sop: {

+ 3 - 15
src/views/sop/sop-manage/sop-step/index.vue

@@ -90,7 +90,6 @@
           ref="form"
           class="sop-step-body"
           colon
-          :label-width="labelWidth"
           :rules="rules"
           :data="formData"
           labelAlign="top"
@@ -107,12 +106,11 @@
               v-for="config in curFormConfig"
               :key="config.id"
             >
-              <MyFormItem
+              <dynamic-form-item
                 v-if="config.visable"
                 :config="config"
-                :labelWidth="labelWidth"
                 @change="itemValueChange"
-              ></MyFormItem>
+              ></dynamic-form-item>
             </t-col>
           </t-row>
         </t-form>
@@ -144,7 +142,7 @@ import { ref, computed, watch } from 'vue';
 import { ErrorCircleFilledIcon } from 'tdesign-icons-vue-next';
 import { MessagePlugin } from 'tdesign-vue-next';
 
-import MyFormItem from '../../components/my-form-item.vue';
+import DynamicFormItem from '../../components/dynamic-form-item/index.vue';
 // import { useRouter, useRoute } from 'vue-router';
 import {
   sopFlowViewApi,
@@ -391,16 +389,6 @@ const getFormData = () => {
 // 填报-提交
 const submitHandle = async (approve = 'START') => {
   if (approve === 'START') {
-    // bus.emit('checkTable');
-    // let tablePassed = false;
-    // bus.on('tablePassed', () => {
-    //   tablePassed = true;
-    //   console.log('table通过了');
-    // });
-    // bus.on('tableFailed', () => {
-    //   tablePassed = false;
-    //   console.log('table不通过');
-    // });
     const valid = await form.value[0].validate();
     if (valid !== true) return;
   }

+ 29 - 26
src/views/work-hours/work-hours-manage/work-attendance-detail/index.vue

@@ -10,7 +10,7 @@
     </SearchForm>
 
     <div class="flex-1 page-wrap">
-      <p class="page-wrap-tips">
+      <p v-if="statisticsInfo" class="page-wrap-tips">
         <t-space :size="0">
           <span>
             <ErrorCircleFilledIcon /> 考勤总计:{{ statisticsInfo.total }}
@@ -55,7 +55,7 @@
 </template>
 
 <script setup name="WorkAttendanceDetail">
-import { reactive, ref } from 'vue';
+import { reactive, ref, computed } from 'vue';
 import { omit } from 'lodash';
 import { ErrorCircleFilledIcon } from 'tdesign-icons-vue-next';
 
@@ -119,7 +119,7 @@ const fields = ref([
     prop: 'supplierId',
     label: '所属供应商',
     type: 'select',
-    labelWidth: 120,
+    labelWidth: 100,
     colSpan: 5,
     cell: 'supplier',
   },
@@ -144,22 +144,23 @@ const fields = ref([
   {
     prop: 'abnormal',
     type: 'number',
-    label: '考勤异常数>',
-    labelWidth: 120,
+    label: '考勤异常数',
+    labelWidth: 100,
     colSpan: 5,
     attrs: {
       theme: 'column',
       decimalPlaces: 0,
       max: 1000000,
       min: 0,
-      style: 'width: 120px',
+      style: 'width: 100%',
+      label: '>',
     },
   },
   {
     prop: 'type',
-    type: 'number',
+    type: 'select',
     label: '考勤类型',
-    labelWidth: 120,
+    labelWidth: 100,
     colSpan: 5,
     options: dictToOptionList(ATTENDANCE_TYPE),
     attrs: {
@@ -168,9 +169,9 @@ const fields = ref([
   },
   {
     prop: 'status',
-    type: 'number',
+    type: 'select',
     label: '考勤结果',
-    labelWidth: 120,
+    labelWidth: 100,
     colSpan: 5,
     options: dictToOptionList(ATTENDANCE_RESULT),
     attrs: {
@@ -197,19 +198,19 @@ const computedParams = computed(() => {
 });
 
 const columns = [
-  { colKey: 'service', title: '服务单元' },
-  { colKey: 'sopNo', title: 'SOP流水号' },
-  { colKey: 'custom', title: '客户名称' },
-  { colKey: 'province', title: '省份', minWidth: 60 },
-  { colKey: 'city', title: '城市', minWidth: 60 },
-  { colKey: 'userName', title: '姓名', width: 150 },
-  { colKey: 'roleName', title: '项目角色' },
-  { colKey: 'supplier', title: '所属供应商' },
-  { colKey: 'signDate', title: '考勤日期', width: 120 },
-  { colKey: 'type', title: '考勤类型', cell: 'type', width: 80 },
-  { colKey: 'signTime', title: '打卡时间', cell: 'sign-time', width: 170 },
-  { colKey: 'address', title: '打卡地址' },
-  { colKey: 'status', title: '考勤结果', cell: 'status', width: 80 },
+  { colKey: 'service', title: '服务单元', width: 140 },
+  { colKey: 'sopNo', title: 'SOP流水号', width: 200 },
+  { colKey: 'custom', title: '客户名称', width: 140 },
+  { colKey: 'province', title: '省份', minWidth: 100 },
+  { colKey: 'city', title: '城市', minWidth: 100 },
+  { colKey: 'userName', title: '姓名', width: 140 },
+  { colKey: 'roleName', title: '项目角色', width: 140 },
+  { colKey: 'supplier', title: '所属供应商', width: 140 },
+  { colKey: 'signDate', title: '考勤日期', width: 180 },
+  { colKey: 'type', title: '考勤类型', cell: 'type', width: 100 },
+  { colKey: 'signTime', title: '打卡时间', cell: 'sign-time', width: 180 },
+  { colKey: 'address', title: '打卡地址', minWidth: 200 },
+  { colKey: 'status', title: '考勤结果', cell: 'status', width: 100 },
 ];
 const { pagination, tableData, search, onChange } = useFetchTable(
   workAttendanceDetailListApi,
@@ -217,9 +218,11 @@ const { pagination, tableData, search, onChange } = useFetchTable(
     params: computedParams,
   }
 );
-let statisticsInfo = ref({});
+let statisticsInfo = ref(null);
 const getStatisticsInfo = async () => {
-  const res = await workStatisticsDetailInfoApi(params);
-  statisticsInfo.value = res || {};
+  const res = await workStatisticsDetailInfoApi(computedParams.value).catch(
+    () => {}
+  );
+  statisticsInfo.value = res || null;
 };
 </script>