gaoluyang
2025-07-02 fba474712a68a9c85ac4bd5006f16194e13c17bb
Merge remote-tracking branch 'origin/dev' into dev
已修改4个文件
已添加7个文件
1647 ■■■■■ 文件已修改
src/api/equipment/management/index.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/PIMTable.vue 432 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/Pagination.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/table.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/useFormData.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/useModal.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/usePaginationApi.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/index.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/archiveManagement/index.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipment/management/index.vue 676 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipment/management/mould/managementDialog.vue 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipment/management/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
// è®¾å¤‡ç®¡ç†
import request from '@/utils/request'
// /equipmentManagement/list
// æŸ¥è¯¢è®¾å¤‡ç®¡ç†åˆ—表
export function getManagementList(query) {
  return request({
    url: '/equipmentManagement/list',
    method: 'get',
    params: query
  })
}
// /equipmentManagement/addOrEditEquipment
// æ·»åŠ æˆ–ç¼–è¾‘è®¾å¤‡
export function addOrEditEquipment(data) {
  return request({
    url: '/equipmentManagement/addOrEditEquipment',
    method: 'post',
    data
  })
}
// /equipmentManagement/delEquipment
// åˆ é™¤è®¾å¤‡
export function delEquipment(data) {
  return request({
    url: '/equipmentManagement/delEquipment',
    method: 'delete',
    data
  })
}
src/components/Table/PIMTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,432 @@
<template>
  <el-table
    ref="multipleTable"
    v-loading="tableLoading"
    :border="border"
    :data="tableData"
    :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
    :height="height"
    :highlight-current-row="highlightCurrentRow"
    :row-class-name="rowClassName"
    :row-style="rowStyle"
    :row-key="rowKey"
    style="width: 100%"
    tooltip-effect="dark"
    :expand-row-keys="expandRowKeys"
    :show-summary="isShowSummary"
    :summary-method="summaryMethod"
    @row-click="rowClick"
    @current-change="currentChange"
    @selection-change="handleSelectionChange"
    @expand-change="expandChange"
    class="lims-table"
  >
    <el-table-column
      align="center"
      type="selection"
      width="55"
      v-if="isSelection"
    />
    <el-table-column align="center" label="序号" type="index" width="60" />
    <el-table-column
      v-for="(item, index) in column"
      :key="index"
      :column-key="item.columnKey"
      :filter-method="item.filterHandler"
      :filter-multiple="item.filterMultiple"
      :filtered-value="item.filteredValue"
      :filters="item.filters"
      :fixed="item.fixed"
      :label="item.label"
      :prop="item.prop"
      show-overflow-tooltip
      :align="item.align"
      :sortable="!!item.sortable"
      :type="item.type"
      :width="item.width"
    >
      <template
        v-if="item.hasOwnProperty('colunmTemplate')"
        #[item.colunmTemplate]="scope"
      >
        <slot
          v-if="item.theadSlot"
          :name="item.theadSlot"
          :index="scope.$index"
          :row="scope.row"
        />
      </template>
      <template #default="scope">
        <!-- æ’æ§½ -->
        <div v-if="item.dataType == 'slot'">
          <slot
            v-if="item.slot"
            :index="scope.$index"
            :name="item.slot"
            :row="scope.row"
          />
        </div>
        <!-- è¿›åº¦æ¡ -->
        <div v-else-if="item.dataType == 'progress'">
          <el-progress :percentage="Number(scope.row[item.prop])" />
        </div>
        <!-- å›¾ç‰‡ -->
        <div v-else-if="item.dataType == 'image'">
          <img
            :src="javaApi + '/img/' + scope.row[item.prop]"
            alt=""
            style="width: 40px; height: 40px; margin-top: 10px"
          />
        </div>
        <!-- tag -->
        <div v-else-if="item.dataType == 'tag'">
          <el-tag
            v-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'string'
            "
            :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(scope.row[item.prop], item.formatType)"
          >
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
          <el-tag
            v-for="(tag, index) in dataTypeFn(
              scope.row[item.prop],
              item.formatData
            )"
            v-else-if="
              typeof dataTypeFn(scope.row[item.prop], item.formatData) ===
              'object'
            "
            :key="index"
            :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(tag, item.formatType)"
          >
            {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }}
          </el-tag>
          <el-tag
            v-else
            :title="formatters(scope.row[item.prop], item.formatData)"
            :type="formatType(scope.row[item.prop], item.formatType)"
          >
            {{ formatters(scope.row[item.prop], item.formatData) }}
          </el-tag>
        </div>
        <!-- æŒ‰é’® -->
        <div v-else-if="item.dataType == 'action'">
          <template v-for="(o, key) in item.operation" :key="key">
            <el-button
              v-show="o.type != 'upload'"
              size="small"
              v-if="o.showHide ? o.showHide(scope.row) : true"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :plain="o.plain"
              type="primary"
              :style="{
                color:
                  o.name === '删除' || o.name === 'delete'
                    ? '#f56c6c'
                    : o.color,
              }"
              link
              @click="o.clickFun(scope.row)"
              :key="key"
            >
              {{ o.name }}
            </el-button>
            <el-upload
              :action="
                javaApi +
                o.url +
                '?id=' +
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id)
              "
              ref="uploadRef"
              size="small"
              :multiple="o.multiple ? o.multiple : false"
              :limit="1"
              :disabled="o.disabled ? o.disabled(scope.row) : false"
              :accept="
                o.accept
                  ? o.accept
                  : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
              "
              v-if="o.type == 'upload'"
              style="display: inline-block; width: 50px"
              v-show="o.showHide ? o.showHide(scope.row) : true"
              :headers="uploadHeader"
              :before-upload="(file) => beforeUpload(file, scope.$index)"
              :on-change="
                (file, fileList) => handleChange(file, fileList, scope.$index)
              "
              :on-error="
                (error, file, fileList) =>
                  onError(error, file, fileList, scope.$index)
              "
              :on-success="
                (response, file, fileList) =>
                  handleSuccessUp(response, file, fileList, scope.$index)
              "
              :on-exceed="onExceed"
              :show-file-list="false"
            >
              <el-button
                :size="o.size ? o.size : 'small'"
                link
                type="primary"
                :disabled="o.disabled ? o.disabled(scope.row) : false"
                >{{ o.name }}</el-button
              >
            </el-upload>
          </template>
        </div>
        <!-- å¯ç‚¹å‡»çš„æ–‡å­— -->
        <div
          v-else-if="item.dataType == 'link'"
          class="cell link"
          style="width: 100%"
          @click="goLink(scope.row, item.linkMethod)"
        >
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
        </div>
        <!-- é»˜è®¤çº¯å±•示数据 -->
        <div v-else class="cell" style="width: 100%">
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
          <span v-else>{{
            formatters(scope.row[item.prop], item.formatData)
          }}</span>
        </div>
      </template>
    </el-table-column>
  </el-table>
  <pagination
    v-if="page.total > 0"
    :total="page.total"
    :layout="page.layout"
    :page="page.current"
    :limit="page.size"
    @pagination="paginationSearch"
  />
</template>
<script setup>
import pagination from "./Pagination.vue";
import { ref, inject, getCurrentInstance } from "vue";
import { ElMessage } from "element-plus";
// èŽ·å–å…¨å±€çš„ uploadHeader
const { proxy } = getCurrentInstance();
const uploadHeader = proxy.uploadHeader;
const javaApi = proxy.javaApi;
const emit = defineEmits(["pagination", "expand-change", "selection-change"]);
// Filters
const typeFn = (val, row) => {
  return typeof val === "function" ? val(row) : val;
};
const formatters = (val, format) => {
  return typeof format === "function" ? format(val) : val;
};
// Props(使用 defineProps çš„非 TS å½¢å¼ï¼‰
const props = defineProps({
  tableLoading: {
    type: Boolean,
    default: false,
  },
  height: {
    type: [Number, String],
    default: "calc(100vh - 22em)",
  },
  expandRowKeys: {
    type: Array,
    default: () => [],
  },
  summaryMethod: {
    type: Function,
    default: () => {},
  },
  rowClick: {
    type: Function,
    default: () => {},
  },
  currentChange: {
    type: Function,
    default: () => {},
  },
  border: {
    type: Boolean,
    default: true,
  },
  isSelection: {
    type: Boolean,
    default: false,
  },
  isShowSummary: {
    type: Boolean,
    default: false,
  },
  highlightCurrentRow: {
    type: Boolean,
    default: false,
  },
  headerCellStyle: {
    type: Object,
    default: () => ({}),
  },
  column: {
    type: Array,
    default: () => [],
  },
  rowClassName: {
    type: Function,
    default: () => "",
  },
  rowStyle: {
    type: [Object, Function],
    default: () => ({}),
  },
  tableData: {
    type: Array,
    default: () => [],
  },
  rowKey: {
    type: String,
    default: undefined,
  },
  page: {
    type: Object,
    default: () => ({
      total: 0,
      current: 0,
      size: 10,
      layout: "total, sizes, prev, pager, next, jumper",
    }),
  },
  total: {
    type: Number,
    default: 0,
  },
});
// Data
const uploadRefs = ref([]);
const currentFiles = ref({});
const uploadKeys = ref({});
const indexMethod = (index) => {
  return (props.page.current - 1) * props.page.size + index + 1;
};
// ç‚¹å‡» link äº‹ä»¶
const goLink = (row, linkMethod) => {
  if (!linkMethod) {
    return ElMessage.warning("请配置 link äº‹ä»¶");
  }
  const parentMethod = getParentMethod(linkMethod);
  if (typeof parentMethod === "function") {
    parentMethod(row);
  } else {
    console.warn(`父组件中未找到方法: ${linkMethod}`);
  }
};
// èŽ·å–çˆ¶ç»„ä»¶æ–¹æ³•ï¼ˆç¤ºä¾‹å®žçŽ°ï¼‰
const getParentMethod = (methodName) => {
  const parentMethods = inject("parentMethods", {});
  return parentMethods[methodName];
};
const dataTypeFn = (val, format) => {
  if (typeof format === "function") {
    return format(val);
  } else return val;
};
const formatType = (val, format) => {
  if (typeof format === "function") {
    return format(val);
  } else return "";
};
// æ–‡ä»¶å˜åŒ–处理
const handleChange = (file, fileList, index) => {
  if (fileList.length > 1) {
    const earliestFile = fileList[0];
    uploadRefs.value[index]?.handleRemove(earliestFile);
  }
  currentFiles.value[index] = file;
};
// æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
const beforeUpload = (rawFile, index) => {
  currentFiles.value[index] = {};
  if (rawfile.size > 1024 * 1024 * 10 * 10) {
    ElMessage.error("上传文件不超过10M");
    return false;
  }
  return true;
};
// ä¸Šä¼ æˆåŠŸ
const handleSuccessUp = (response, file, fileList, index) => {
  if (response.code == 200) {
    if (uploadRefs[index]) {
      uploadRefs[index].clearFiles();
    }
    currentFiles[index] = file;
    ElMessage.success("上传成功");
    resetUploadComponent(index);
  } else {
    ElMessage.error(response.message);
  }
};
const resetUploadComponent = (index) => {
  uploadKeys[index] = Date.now();
};
// ä¸Šä¼ å¤±è´¥
const onError = (error, file, fileList, index) => {
  ElMessage.error("文件上传失败,请重试");
  if (uploadRefs.value[index]) {
    uploadRefs.value[index].clearFiles();
  }
};
// æ–‡ä»¶æ•°é‡è¶…限提示
const onExceed = () => {
  ElMessage.warning("超出文件个数");
};
const paginationSearch = ({ page, limit }) => {
  emit("pagination", { page: page, limit: limit });
};
const expandChange = (row, expandedRows) => {
  emit("expand-change", row, expandedRows);
};
const handleSelectionChange = (newSelection) => {
  emit("selection-change", newSelection);
};
</script>
<style scoped lang="scss">
.cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-right: 0 !important;
  padding-left: 0 !important;
}
</style>
src/components/Table/Pagination.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,100 @@
<template>
  <div :class="{ hidden }" class="pagination-container">
    <el-pagination
      :background="background"
      v-model:current-page="currentPage"
      v-model:page-size="pageSize"
      :layout="layout"
      :page-sizes="pageSizes"
      :pager-count="pagerCount"
      :total="total"
      v-bind="$attrs"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>
<script setup>
import { computed } from 'vue'
import { scrollTo } from '@/utils/scroll-to'
const props = defineProps({
  total: {
    type: Number,
    required: true
  },
  page: {
    type: Number,
    default: 1
  },
  limit: {
    type: Number,
    default: 20
  },
  pageSizes: {
    type: Array,
    default: () => [10, 20, 30, 50, 100]
  },
  pagerCount: {
    type: Number,
    default: () => (document.body.clientWidth < 992 ? 5 : 7)
  },
  layout: {
    type: String,
    default: 'total, sizes, prev, pager, next, jumper'
  },
  background: {
    type: Boolean,
    default: true
  },
  autoScroll: {
    type: Boolean,
    default: true
  },
  hidden: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
const currentPage = computed({
  get: () => props.page,
  set: (val) => emit('update:page', val)
})
const pageSize = computed({
  get: () => props.limit,
  set: (val) => emit('update:limit', val)
})
const handleSizeChange = (val) => {
  if (currentPage.value * val > props.total) {
    currentPage.value = 1
  }
  emit('pagination', { page: currentPage.value, limit: val })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
const handleCurrentChange = (val) => {
  emit('pagination', { page: val, limit: pageSize.value })
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
</script>
<style scoped>
.pagination-container {
  background: #fff;
  padding: 16px 0;
  margin-top: 0;
}
.pagination-container.hidden {
  display: none;
}
</style>
src/components/Table/table.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<template>
  <el-table :data="tableData" :column="columnss" style="width: 100%">
    <el-table-column type="index" width="50">
      <template #default="scope">
        {{ getRowIndex(scope.$index) }}
      </template>
    </el-table-column>
    <el-table-column
      v-for="(item, index) in columnss"
      :key="index"
      :prop="item.prop"
      :label="item.label"
      :align="item.align || 'center'"
      :min-width="item.minWidth || '100px'"
      :width="item.width"
      :fixed="item.fixed || false"
      :show-overflow-tooltip="showOverflowTooltip || true"
    >
    </el-table-column>
  </el-table>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from "vue";
const props = defineProps({
  tableData: {
    type: Array,
    default: () => [],
  },
  columnss: {
    type: Array,
    default: () => [],
  },
});
const getRowIndex = (index) => {
  return index + 1;
};
</script>
<style scoped></style>
src/hooks/useFormData.js
@@ -4,7 +4,8 @@
 */
import { ref, reactive, computed, nextTick } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { clone } from "lodash";
import { deepClone } from "@/utils/index.js"
/**
 * åˆ›å»ºè¡¨å•数据管理功能
@@ -324,8 +325,8 @@
}
// å‘后兼容的默认导出
export default function useFormDataSimple(initData) {
  const form = reactive(clone(initData, true));
export default function useFormDatas(initData) {
  const form = reactive(deepClone(initData, true));
  function resetForm() {
    const initData2 = JSON.parse(JSON.stringify(initData));
src/hooks/useModal.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
import { ref } from "vue";
export function useModal(options) {
  const id = ref();
  const visible = ref(false);
  const loading = ref(false);
  const modalOptions = ref({});
  const openModal = (e) => {
    id.value = e;
    modalOptions.value = {
      title: e ? `修改${options.title}` : `新增${options.title}`,
      content: "确定执行此操作吗?",
      confirmText: "确定",
      cancelText: "取消",
    };
    visible.value = true;
  };
  // å…³é—­æ¨¡æ€æ¡†
  const closeModal = () => {
    visible.value = false;
    loading.value = false;
  };
  // ç¡®è®¤æ“ä½œ
  const handleConfirm = async (callback) => {
    loading.value = true;
    callback();
    closeModal();
  };
  return {
    id,
    visible,
    loading,
    modalOptions,
    openModal,
    closeModal,
    handleConfirm,
  };
}
src/hooks/usePaginationApi.jsx
@@ -1,8 +1,8 @@
import { ref, reactive, watchEffect, unref } from "vue";
import useFormData from "./useFormData.js";
// import { message } from "@/utils/message";
import useFormData from "@/hooks/useFormData";
import { deepClone, isEqual } from "@/utils/index.js"
import { ElMessage } from 'element-plus'
import { clone, isEqual } from "lodash";
/**
 * åˆ†é¡µapi
 * @param api æŽ¥å£
@@ -20,7 +20,7 @@
) {
  const dataList = ref([]);
  const { form: filters, resetForm } = useFormData(initalFilters);
  let lastFilters = clone(initalFilters);
  let lastFilters = deepClone(initalFilters);
  const sorter = reactive(sorters || {});
  const others = ref({});
  const loading = ref(true);
@@ -79,14 +79,14 @@
    // å¦‚果这次和上次的filter不同,那么就重置页码
    if (!isEqual(unref(filters), lastFilters)) {
      pagination.currentPage = 1;
      lastFilters = clone(unref(filters));
      lastFilters = deepClone(unref(filters));
    }
    loading.value = true;
    api({
      ...getFinalParams(),
      current: pagination.currentPage,
      size: pagination.pageSize
    }).then(({ code, data, ...rest }) => {
    }).then(({ code, data, msg, ...rest }) => {
      if (code == 200) {
        // pagination.currentPage = meta.current_page;
        // pagination.pageSize = meta.per_page;
@@ -97,7 +97,7 @@
        loading.value = false;
      } else {
        loading.value = false;
        // message(data.msg, { type: "error" });
        ElMessage({ message: msg, type: "error" });
      }
    });
  }
@@ -120,7 +120,7 @@
  }
  watchEffect(() => {
    pagination.align = paginationAlign.value;
    pagination.align = paginationAlign.value
  });
  // onMounted(() => {
src/utils/index.js
@@ -388,3 +388,6 @@
  return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
}
 
export function isEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}
src/views/archiveManagement/index.vue
@@ -118,12 +118,12 @@
            </el-input>
          </div>
        </el-col>
        <el-col :offset="10" :span="2">
        <el-col :offset="8" :span="3">
          <el-button :icon="Delete" type="danger" @click="delHandler"
            >删除</el-button
          >
        </el-col>
        <el-col :span="2">
        <el-col :span="3">
          <el-button
            :disabled="!tableSwitch"
            :icon="Plus"
@@ -142,6 +142,7 @@
        :table-data="tableData"
        @edit="handleEdit"
        @selection-change="handleSelectionChange"
        style="height: calc(65vh);"
      >
      </ETable>
      <Pagination
@@ -288,6 +289,7 @@
      treeId: queryParams.treeId,
      current: queryParams.current,
      size: queryParams.pageSize,
      searchAll: queryParams.searchAll,
    });
    if (res.code !== 200) {
@@ -556,7 +558,10 @@
};
// ===== ç”Ÿå‘½å‘¨æœŸ =====
onMounted(getList);
onMounted(()=>{
  getList();
  getArchiveListData();
});
</script>
<style lang="scss" scoped>
.custom-tree-node {
src/views/equipment/management/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,676 @@
<template>
  <div class="app-container">
    <el-form :inline="true" :model="queryParams" class="search-form">
      <el-form-item v-if="shouldShowSearch" label="搜索">
        <el-input
          v-model="queryParams.searchAll"
          :placeholder="searchPlaceholder"
          clearable
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="search">查询</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-card>
      <!-- æ ‡ç­¾é¡µ -->
      <el-tabs
        v-model="activeTab"
        class="info-tabs"
        @tab-click="handleTabClick"
      >
        <el-tab-pane
          v-for="tab in tabs"
          :key="tab.name"
          :label="tab.label"
          :name="tab.name"
        />
      </el-tabs>
      <!-- æ“ä½œæŒ‰é’®åŒº -->
      <el-row :gutter="24" class="table-toolbar">
        <el-button :icon="Plus" type="primary" @click="handleAdd"
          >新建</el-button
        >
        <el-button :icon="Delete" type="danger" @click="handleDelete"
          >删除</el-button
        >
        <!-- <el-button
          v-show="canExport"
          :icon="Download"
          type="info"
          @click="handleExport"
          >导出</el-button
        > -->
      </el-row>
      <!-- è¡¨æ ¼ç»„ä»¶ -->
      <div>
        <data-table
          :border="true"
          :columns="columns"
          :loading="loading"
          style="width: 100%; height: calc(100vh - 29em)"
          :show-selection="true"
          :table-data="tableData"
          @edit="handleEdit"
          @viewRow="handleView"
          @selection-change="handleSelectionChange"
          :operations="['edit', 'viewRow']"
          :operationsWidth="200"
        >
          <!-- å­—段名称列的自定义插槽 - æ˜¾ç¤ºä¸ºæ ‡ç­¾ -->
          <template
            v-if="tabName === 'coalQualityMaintenance'"
            #fieldIds="{ row }"
          >
            <template
              v-if="
                typeof row.fieldIds === 'string' && row.fieldIds.includes(',')
              "
            >
              <el-tag
                v-for="(field, index) in row.fieldIds.split(',')"
                :key="index"
                size="small"
                style="margin-right: 4px; margin-bottom: 2px"
                type="primary"
              >
                {{ getFieldDisplayName(field.trim()) }}
              </el-tag>
            </template>
            <template v-else>
              <el-tag size="small" type="primary">
                {{ getFieldDisplayName(row.fieldIds) || "--" }}
              </el-tag>
            </template>
          </template>
        </data-table>
      </div>
      <pagination
        v-if="total > 0"
        :layout="'total, prev, pager, next, jumper'"
        :limit="pageSizes"
        :page="pageNum"
        :total="total"
        @pagination="handPagination"
      />
      <managementDialog
      ></managementDialog>
    </el-card>
  </div>
</template>
<script setup>
import {
  computed,
  getCurrentInstance,
  onMounted,
  reactive,
  ref,
  nextTick,
} from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Delete, Download, Plus } from "@element-plus/icons-vue";
// ===== ç»„件导入 =====
import DataTable from "@/components/Table/ETable.vue";
import Pagination from "@/components/Pagination";
import managementDialog from "./mould/managementDialog.vue";
// ===== API æœåС坼入 =====
import { delSupply, getSupply } from "@/api/basicInformation/supplier.js";
import { useDelete } from "@/hooks/useDelete.js";
import { testUserList } from "@/api/tool/publicInterface.js";
const { proxy } = getCurrentInstance();
// ===== å“åº”式状态管理 =====
const dialogFormVisible = ref(false);
const form = ref({});
const title = ref("");
const copyForm = ref({});
const addOrEdit = ref("add");
// æ•°æ®ç¼“存映射
const userList = ref([]);
const userMap = ref({}); // ç”¨æˆ·ID -> ç”¨æˆ·åæ˜ å°„表
const addressMap = ref({}); // åœ°å€ID -> åœ°å€ä¿¡æ¯æ˜ å°„表
const coalFieldList = ref([]); // ç…¤è´¨å­—段列表
// é¡µé¢çŠ¶æ€æŽ§åˆ¶
const tabName = ref("management");
const loading = ref(false);
const activeTab = ref("management");
// åˆ†é¡µçŠ¶æ€ç®¡ç†
const pageNum = ref(1);
const pageSizes = ref(10);
const total = ref(0);
// è¡¨æ ¼çŠ¶æ€ç®¡ç†
const selectedRows = ref([]);
const tableData = ref([]);
const columns = ref();
// æŸ¥è¯¢å‚æ•°
const queryParams = reactive({});
// åœ°å€é€‰æ‹©æ•°æ®
const addressSelectOptions = ref([]);
// ===== é…ç½®å¸¸é‡ =====
// æ ‡ç­¾é¡µé…ç½®
const tabs = reactive([
  { name: "management", label: "供应商信息" },
  { name: "customer", label: "客户信息" },
]);
// ===== å·¥å…·å‡½æ•° =====
/**
 * æž„建地址映射表
 * @param {Array} areaData - åœ°å€æ•°æ®
 * @description é€’归构建地址映射表,支持多级地址查找
 */
const buildAddressMap = (areaData) => {
  const buildMap = (list, pathList = []) => {
    list.forEach((item) => {
      const currentPath = [...pathList, item.label];
      addressMap.value[item.id] = {
        name: item.label,
        fullPath: currentPath.join(" / "),
      };
      if (item.children && item.children.length > 0) {
        buildMap(item.children, currentPath);
      }
    });
  };
  buildMap(areaData);
};
/**
 * æ ¼å¼åŒ–地址数组为显示字符串
 * @param {Array} addressIds - åœ°å€ID数组
 * @returns {string} æ ¼å¼åŒ–后的地址字符串
 * @description å°†åœ°å€ID数组转换为可读的地址字符串
 */
const formatAddressArray = (addressIds) => {
  if (
    !addressMap.value ||
    Object.keys(addressMap.value).length === 0 ||
    !addressIds ||
    !Array.isArray(addressIds) ||
    addressIds.length === 0 ||
    addressIds.every((id) => !id)
  ) {
    return "--";
  }
  const addressNames = addressIds.map(
    (id) => addressMap.value[id]?.name || "--"
  );
  if (addressNames.every((name) => name === "--")) {
    return "--";
  }
  return addressNames.filter((name) => name !== "--").join(" / ");
};
/**
 * èŽ·å–ç”¨æˆ·åˆ—è¡¨æ•°æ®å¹¶æž„å»ºæ˜ å°„è¡¨
 * @description èŽ·å–ç”¨æˆ·æ•°æ®å¹¶æž„å»ºID到用户名的映射关系
 */
const getUserList = async () => {
  try {
    const res = await testUserList();
    console.log("获取用户列表数据:", res);
    console.log("userMap:", userMap.value);
    if (res && res.data) {
      userList.value = res.data;
      userList.value.forEach((user) => {
        userMap.value[user.userId] = user.nickName;
      });
    }
  } catch (error) {
    console.error("获取用户列表失败:", error);
  }
};
/**
 * æ ¹æ®å­—段ID获取字段显示名称
 * @param {string|number} fieldId - å­—段ID
 * @returns {string} å­—段显示名称
 * @description é€šè¿‡å­—段ID匹配对应的字段名称
 */
const getFieldDisplayName = (fieldId) => {
  if (!fieldId) return "--";
  const numId = parseInt(fieldId);
  const matchedField = coalFieldList.value.find((item) => item.id === numId);
  return matchedField ? matchedField.fieldName : numId;
};
/**
 * å½“前标签页是否支持导出功能
 */
const canExport = computed(() => {
  return ["management"].includes(tabName.value);
});
/**
 * æœç´¢æ¡†å ä½ç¬¦æ–‡æœ¬
 */
const searchPlaceholder = computed(() => {
  const placeholderMap = {
    management: "供应商/统一识别码/详细地址",
  };
  return placeholderMap[tabName.value] || "请输入搜索信息";
});
/**
 * æ˜¯å¦æ˜¾ç¤ºæœç´¢æ¡†
 */
const shouldShowSearch = computed(() => {
  return [
    "management",
  ].includes(tabName.value);
});
/**
 * å½“前选中行数量
 */
const selectedCount = computed(() => selectedRows.value.length);
// ===== è¡¨æ ¼åˆ—配置 =====
/**
 * ä¾›åº”商表格列配置
 */
const management = ref([
  { prop: "equipmentId", label: "设备编号", minWidth: 100 },
  { prop: "equipmentName", label: "设备名称", minWidth: 100 },
  { prop: "quantity", label: "数量", minWidth: 100 },
  { prop: "specification", label: "规格型号", minWidth: 100 },
  { prop: "usageStatus", label: "使用状态", minWidth: 100 },
  { prop: "usingDepartment", label: "使用部门", minWidth: 100 },
  { prop: "userId", label: "使用人", minWidth: 100 },
  { prop: "purchaseDate", label: "采购日期", minWidth: 100 },
  { prop: "purchasePrice", label: "采购价格", minWidth: 100 },
]);
// ===== äº‹ä»¶å¤„理函数 =====
/**
 * æ ‡ç­¾é¡µåˆ‡æ¢äº‹ä»¶å¤„理
 * @param {Object} tab - æ ‡ç­¾é¡µå¯¹è±¡
 * @description å¤„理标签页切换,重置表单和状态,加载对应数据
 */
const handleTabClick = (tab) => {
  // é‡ç½®è¡¨å•和状态
  form.value = {};
  addOrEdit.value = "add";
  loading.value = true;
  tabName.value = tab.props.name;
  tableData.value = [];
  pageNum.value = 1;
  pageSizes.value = 10;
  total.value = 0;
  queryParams.searchAll = "";
  // æ ¹æ®æ ‡ç­¾é¡µç±»åž‹è®¾ç½®å¯¹åº”的列配置
  const tabConfig = {
    management: () => {
      columns.value = management.value;
      getList();
    },
  };
  // æ‰§è¡Œå¯¹åº”的配置函数
  const configFn = tabConfig[tabName.value];
  if (configFn) {
    configFn();
  }
};
/**
 * é‡ç½®æŸ¥è¯¢æ¡ä»¶
 * @description é‡ç½®æŸ¥è¯¢å‚数并重新加载数据
 */
const resetQuery = () => {
  Object.keys(queryParams).forEach((key) => {
    if (key !== "pageNum" && key !== "pageSizes") {
      queryParams[key] = "";
    }
  });
  getList();
};
/**
 * æœç´¢åŠŸèƒ½
 * @description é‡ç½®é¡µç å¹¶æ‰§è¡Œæœç´¢
 */
const search = () => {
  pageNum.value = 1;
  getList();
};
/**
 * æ–°å¢žæŒ‰é’®ç‚¹å‡»å¤„理
 */
const handleAdd = () => {
  addOrEdit.value = "add";
  handleAddEdit(tabName.value);
};
/**
 * æ–°å¢ž/编辑弹窗处理
 * @param {string} currentTabName - å½“前标签页名称
 * @description æ ¹æ®æ ‡ç­¾é¡µç±»åž‹è®¾ç½®å¼¹çª—标题并打开弹窗
 */
const handleAddEdit = (currentTabName) => {
  const actionText =
    addOrEdit.value === "add"
      ? "新增"
      : addOrEdit.value === "edit"
      ? "编辑"
      : "查看";
  const tabTitleMap = {
    supplier: "供应商信息",
    customer: "客户信息",
  };
  title.value = `${actionText}${tabTitleMap[currentTabName] || ""}`;
  openDialog();
};
/**
 * æ‰“开弹窗
 * @description æ ¹æ®ç¼–辑状态决定是否复制表单数据
 */
const openDialog = () => {
  if (addOrEdit.value === "edit" || addOrEdit.value === "viewRow") {
    copyForm.value = JSON.parse(JSON.stringify(form.value));
  } else {
    form.value = {};
  }
  dialogFormVisible.value = true;
};
/**
 * åˆ†é¡µå¤„理
 * @param {Object} val - åˆ†é¡µå‚数对象
 */
const handPagination = (val) => {
  pageNum.value = val.page;
  pageSizes.value = val.limit;
  getList();
};
/**
 * è¡¨å•提交处理
 * @param {Object} val - æäº¤ç»“果对象
 */
const handleSubmit = async (val) => {
  if (val.result.code !== 200) {
    ElMessage.error("操作失败:" + val.result.msg);
    return;
  }
  ElMessage.success(val.title + val.result.msg);
  dialogFormVisible.value = false;
  getList();
};
/**
 * å¼¹çª—显示状态处理
 * @param {boolean} value - æ˜¾ç¤ºçŠ¶æ€
 */
const handleDialogFormVisible = (value) => {
  dialogFormVisible.value = value;
};
/**
 * è¡¨æ ¼è¡Œé€‰æ‹©å¤„理
 * @param {Array} selection - é€‰ä¸­çš„行数据
 */
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
/**
 * ç¼–辑按钮点击处理
 * @param {Object} row - è¡Œæ•°æ®
 * @description å¤„理编辑操作,构建地址数组并打开编辑弹窗
 */
const handleEdit = (row) => {
  form.value = JSON.parse(JSON.stringify(row));
  // æž„建供应商业务地址数组
  if (form.value.bprovinceId && form.value.bdistrictId && form.value.bcityId) {
    form.value.bids = [row.bprovinceId, row.bcityId, row.bdistrictId];
  }
  // æž„建供应商联系地址数组
  if (form.value.cprovinceId && form.value.cdistrictId && form.value.ccityId) {
    form.value.cids = [row.cprovinceId, row.ccityId, row.cdistrictId];
  }
  // æž„建客户业务地址数组
  if (
    form.value.businessCityId &&
    form.value.businessDistrictId &&
    form.value.businessProvinceId
  ) {
    form.value.bids = [
      row.businessProvinceId,
      row.businessCityId,
      row.businessDistrictId,
    ];
  }
  // æž„建客户联系地址数组
  if (form.value.cityId && form.value.districtId && form.value.provinceId) {
    form.value.cids = [row.provinceId, row.cityId, row.districtId];
  }
  addOrEdit.value = "edit";
  handleAddEdit(tabName.value);
};
/**
 * æ‰¹é‡åˆ é™¤å¤„理
 * @description æ‰¹é‡åˆ é™¤é€‰ä¸­çš„记录
 */
  const deleteApiMap = {
      management: delSupply,
    };
const {handleDeleteBatch :handleDelete} = useDelete({
  deleteApi: () => deleteApiMap[tabName.value],
  selectedRows: selectedRows,
  getList: () => getList,
  tableData: tableData,
  total: total,
  confirmText: "确认删除选中的数据吗?",
  successText: "删除成功",
})
/**
 * å…³é—­å¼¹çª—处理
 */
const handleBeforeClose = () => {
  dialogFormVisible.value = false;
  form.value = {};
};
/**
 * å¯¼å‡ºæ•°æ®
 * @param {string} api - å¯¼å‡ºæŽ¥å£è·¯å¾„
 * @param {string} name - å¯¼å‡ºæ–‡ä»¶åå‰ç¼€
 */
const exportData = (api, name) => {
  proxy.download(
    api,
    { ...queryParams },
    `${name}${new Date().getTime()}.xlsx`
  );
  ElMessage.success("导出数据:" + name);
};
// ===== æ•°æ®èŽ·å–å‡½æ•° =====
/**
 * æ ¹æ®å½“前标签页选择对应的API接口
 * @returns {Promise} API调用Promise
 * @description ç»Ÿä¸€çš„æŽ¥å£é€‰æ‹©å‡½æ•°ï¼Œæ ¹æ®æ ‡ç­¾é¡µç±»åž‹è°ƒç”¨å¯¹åº”çš„API
 */
const selectInterface = () => {
  const apiParams = {
    current: pageNum.value,
    pageSize: pageSizes.value,
    searchAll: queryParams.searchAll,
  };
  const apiMap = {
    management: () => getSupply(apiParams),
    customer: () => getCustomerList(apiParams),
    coal: () => getCoalInfo(apiParams),
    coalQualityMaintenance: () => getCoalPlanList(apiParams),
    coalMeiZhiZiDuanWeiHu: () => coalField(apiParams),
  };
  const apiFunction = apiMap[tabName.value];
  return apiFunction
    ? apiFunction()
    : Promise.reject(new Error("未找到对应的API接口"));
};
/**
 * èŽ·å–åˆ—è¡¨æ•°æ®
 * @description ç»Ÿä¸€çš„æ•°æ®èŽ·å–å‡½æ•°ï¼Œå¤„ç†åŠ è½½çŠ¶æ€å’Œé”™è¯¯å¤„ç†
 */
const getList = async () => {
  try {
    loading.value = true;
    const { data, code } = await selectInterface();
    if (code !== 200) {
      ElMessage.error("获取数据失败:" + (data?.msg || "未知错误"));
      return;
    }
    tableData.value = data.records || [];
    total.value = data.total || 0;
  } catch (error) {
    console.error("获取列表数据失败:", error);
    ElMessage.error("获取数据失败,请稍后再试");
  } finally {
    loading.value = false;
  }
};
const handleView = (row) => {
  form.value = JSON.parse(JSON.stringify(row));
  // æž„建供应商业务地址数组
  if (form.value.bprovinceId && form.value.bdistrictId && form.value.bcityId) {
    form.value.bids = [row.bprovinceId, row.bcityId, row.bdistrictId];
  }
  // æž„建供应商联系地址数组
  if (form.value.cprovinceId && form.value.cdistrictId && form.value.ccityId) {
    form.value.cids = [row.cprovinceId, row.ccityId, row.cdistrictId];
  }
  // æž„建客户业务地址数组
  if (
    form.value.businessCityId &&
    form.value.businessDistrictId &&
    form.value.businessProvinceId
  ) {
    form.value.bids = [
      row.businessProvinceId,
      row.businessCityId,
      row.businessDistrictId,
    ];
  }
  // æž„建客户联系地址数组
  if (form.value.cityId && form.value.districtId && form.value.provinceId) {
    form.value.cids = [row.provinceId, row.cityId, row.districtId];
  }
  addOrEdit.value = "viewRow";
  handleAddEdit(tabName.value);
};
// ===== ç”Ÿå‘½å‘¨æœŸé’©å­ =====
/**
 * ç»„件挂载后的初始化操作
 */
onMounted(async () => {
  try {
    // å¹¶è¡Œæ‰§è¡Œåˆå§‹åŒ–操作
    await Promise.all([
      handleTabClick({ props: { name: "management" } }),
      getUserList(),
    ]);
  } catch (error) {
    console.error("组件初始化失败:", error);
    ElMessage.error("页面初始化失败,请刷新重试");
  }
});
</script>
<style scoped>
/* å“åº”式布局 */
@media screen and (min-width: 768px) {
  .search-form :deep(.el-form-item) {
    width: 50%;
  }
}
@media screen and (min-width: 1200px) {
  .search-form :deep(.el-form-item) {
    width: 16%;
  }
}
.table-toolbar {
  margin-bottom: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
/* å“åº”式表格 */
@media screen and (max-width: 768px) {
  .table-toolbar {
    flex-direction: column;
  }
  .table-toolbar .el-button {
    width: 100%;
  }
}
/* è¡¨æ ¼å·¥å…·æ  */
.table-toolbar,
.table-toolbar > * {
  margin: 0 0 0 0 !important;
}
.table-toolbar {
  margin-bottom: 20px !important;
}
.el-form--inline .el-form-item {
  margin-right: 25px;
}
.main-container {
  background: red !important;
}
</style>
src/views/equipment/management/mould/managementDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,290 @@
<template>
  <div>
    <el-dialog
      v-model="dialogVisible"
      :title="title"
      width="600"
      :close-on-click-modal="false"
      :before-close="handleClose"
    >
      <el-form
        ref="formRef"
        style="max-width: 400px; margin: 0 auto"
        :model="formData"
        :rules="rules"
        label-width="auto"
      >        <el-form-item
          label="供应商名称"
          prop="supplierName"
        >
          <el-input
            v-model="formData.supplierName"
            placeholder="请输入供货商名称"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="纳税人识别号"
          prop="taxpayerId"
        >
          <el-input
            v-model="formData.taxpayerId"
            placeholder="请输入纳税人识别号"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="经营地址"
          prop="bids"
        >
          <el-cascader
            placeholder="请选择经营地址"
            size="default"
            :options="addressSelectOptions"
            v-model="formData.bids"
            :props="cascaderProps"
            @change="handleChange"
            :disabled="isViewMode"
          >
          </el-cascader>
        </el-form-item>
        <el-form-item
          label="详细地址"
          prop="businessAddress"
        >
          <el-input
            v-model="formData.businessAddress"
            placeholder="请输入客户详细地址"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="开户行"
          prop="bankAccount"
        >
          <el-input
            v-model="formData.bankAccount"
            placeholder="请输入开户行"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="银行账户"
          prop="bankName"
        >
          <el-input
            v-model="formData.bankName"
            placeholder="请输入银行账户"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="联系人"
          prop="contactPerson"
        >
          <el-input
            v-model="formData.contactPerson"
            placeholder="请输入联系人"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="联系人电话"
          prop="contactPhone"
        >
          <el-input
            v-model="formData.contactPhone"
            placeholder="请输入联系人电话"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item
          label="联系人地址"
          prop="cids"
        >
          <el-cascader
            placeholder="请选择联系人地址"
            size="default"
            :options="addressSelectOptions"
            v-model="formData.cids"
            :props="cascaderProps"
            @change="handleChange"
            :disabled="isViewMode"
          >
          </el-cascader>
        </el-form-item>
        <el-form-item
          label="联系人详细地址"
          prop="contactAddress"
        >
          <el-input
            v-model="formData.contactAddress"
            placeholder="请输入联系人地址"
            :disabled="isViewMode"
          />
        </el-form-item>
        <el-form-item class="dialog-footer">
          <el-button v-if="addOrEdit === 'edit'" @click="resetForm"
            >重置</el-button
          >
          <el-button v-if="addOrEdit !== 'edit'||addOrEdit.includes('viewRow')" @click="cancelForm"
            >取消</el-button
          >
          <el-button type="primary" @click="submitForm" v-if="!isViewMode"> ç¡®å®š</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, watch, defineProps, onMounted, computed, reactive } from "vue";
import { addOrEditSupply } from "@/api/basicInformation/supplier";
import { getAreaOptions } from "@/api/system/area.js";
const props = defineProps({
  beforeClose: {
    type: Function,
    default: () => {},
  },
  form: {
    type: Object,
    default: () => ({}),
  },
  addOrEdit: {
    type: String,
    default: "add",
  },
  title: {
    type: String,
    default: "",
  },
});
// è®¡ç®—属性:统一控制是否禁用
const isViewMode = computed(() => props.addOrEdit === 'viewRow');
const emit = defineEmits(["submit", "handleBeforeClose"]);
const copyForm = defineModel("copyForm", {
  required: true,
  type: Object,
});
onMounted(() => {
  fetchAreaOptions();
});
// ä¿®æ”¹æ ‘形选择的映射
const cascaderProps = ref({
  value: "id", // æŒ‡å®švalue字段为id
  label: "label", // æŒ‡å®šlabel字段
  children: "children", // æŒ‡å®šå­èŠ‚ç‚¹å­—æ®µ
});
// åœ°å€é€‰æ‹©æ•°æ®
const addressSelectOptions = ref([]);
const fetchAreaOptions = async () => {
  addressSelectOptions.value = [];
  const res = await getAreaOptions();
  if (res.code === 200) {
    addressSelectOptions.value = res.data;
  }
};
// å¤„理地址数据转换
function mapAddress(list) {
  return list.map((item) => ({
    value: item.id,
    label: item.name,
    children: item.children ? mapAddress(item.children) : undefined,
  }));
}
// è¡¨å•引用
const formRef = ref(null);
// è¡¨å•数据
const formData = ref({ ...props.form });
// å¼¹çª—可见性
const dialogVisible = defineModel("supplierDialogFormVisible", {
  required: true,
  type: Boolean,
});
// ç›‘听外部传入的表单数据变化
watch(
  () => props.form,
  (newVal) => {
    formData.value = { ...newVal };
  },
  { deep: true }
);
// ç›‘听内部弹窗状态变化
watch(
  () => dialogVisible.value,
  (newVal) => {
    emit("update:supplierDialogFormVisible", newVal);
  }
);
// å¤„理地址选择变化
const handleChange = (value) => {
  console.log(value);
};
// æäº¤è¡¨å•
const submitForm = async () => {
  if (!formRef.value) return;
  await formRef.value.validate(async (valid, fields) => {
    if (valid) {
      const obj = ref({});
      if (props.title.includes("新增")) {
        let result = await addOrEditSupply({
          ...formData.value,
        });
        obj.value = {
          title: "新增",
          ...formData.value,
          result,
        };
      } else {
        let result = await addOrEditSupply({
          ...formData.value,
        });
        obj.value = {
          title: "编辑",
          ...formData.value,
          result,
        };
      }
      emit("submit", obj.value);
    }
  });
};
// å–消表单
const cancelForm = () => {
  emit("update:supplierDialogFormVisible", false);
  formData.value = {};
};
// é‡ç½®è¡¨å•
const resetForm = () => {
  if (!formRef.value) return;
  formData.value = JSON.parse(JSON.stringify(copyForm.value));
};
// å…³é—­å¼¹çª—
const handleClose = () => {
  // è§¦å‘父组件的关闭函数
  emit("handleBeforeClose");
  emit("update:supplierDialogFormVisible", false);
};
const rules = reactive({
  supplierName: [
    { required: true, message: "请输入供货商名称", trigger: "blur" },
  ],
});
</script>
<style lang="scss" scoped>
.dialog-footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 20px;
  flex-direction: column;
}
</style>