4088aee5374b2bcebf28e590d612ca72ad44197c..75a462f8ee30491f05d29ccac1b65d31e835957b
3 天以前 spring
档案管理调整
75a462 对比 | 目录
3 天以前 spring
档案管理
d9ed7c 对比 | 目录
3 天以前 maven
yys 修改劳保统计表格
9b36bf 对比 | 目录
3 天以前 gaoluyang
劳保台账修改
00228d 对比 | 目录
3 天以前 gaoluyang
劳保台账修改
c4b339 对比 | 目录
已修改4个文件
已添加14个文件
5640 ■■■■■ 文件已修改
src/api/fileManagement/bookshelf.js 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/borrow.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/document.js 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/return.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/lavorissce/ledger.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DynamicTable/index.vue 402 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/example/DynamicTableExample.vue 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/example/SimpleExample.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/bookshelf/detail.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/bookshelf/index.vue 688 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/borrow/index.vue 584 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/document/attachmentManager.vue 426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/document/index.vue 1262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/return/index.vue 595 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/index.vue 309 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/ledger/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/lavorissue/statistics/index.vue 362 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fileManagement/bookshelf.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
import request from "@/utils/request";
/**
 * ä¹¦æž¶ç®¡ç†ç›¸å…³API接口
 * åŒ…含仓库管理、货架管理、图书管理等功能的接口
 */
/**
 * èŽ·å–ä»“åº“åˆ—è¡¨
 * @description èŽ·å–æ‰€æœ‰ä»“åº“çš„åŸºæœ¬ä¿¡æ¯åˆ—è¡¨
 * @returns {Promise} è¿”回仓库列表数据
 */
export function getWarehouseList() {
  return request({
    url: "/warehouse/tree",
    method: "get",
  });
}
/**
 * æ–°å¢žä»“库
 * @description åˆ›å»ºæ–°çš„仓库记录
 * @param {Object} data ä»“库信息对象,包含仓库名称等字段
 * @returns {Promise} è¿”回新增结果
 */
export function addWarehouse(data) {
  return request({
    url: "/warehouse/add",
    method: "post",
    data,
  });
}
/**
 * æ›´æ–°ä»“库信息
 * @description ä¿®æ”¹çŽ°æœ‰ä»“åº“çš„åŸºæœ¬ä¿¡æ¯
 * @param {Object} data ä»“库信息对象,必须包含仓库ID
 * @returns {Promise} è¿”回更新结果
 */
export function updateWarehouse(data) {
  return request({
    url: "/warehouse/update",
    method: "put",
    data,
  });
}
/**
 * åˆ é™¤ä»“库
 * @description æ ¹æ®ä»“库ID删除指定的仓库记录
 * @param {string|number} id ä»“库ID
 * @returns {Promise} è¿”回删除结果
 */
export function deleteWarehouse(data) {
  return request({
    url: `/warehouse/delete/`,
    method: "delete",
    data,
  });
}
/**
 * èŽ·å–è´§æž¶åˆ—è¡¨
 * @description æ ¹æ®ä»“库ID获取该仓库下的所有货架信息
 * @param {string|number} warehouseId ä»“库ID
 * @returns {Promise} è¿”回货架列表数据
 */
export function getShelfList(warehouseId) {
  return request({
    url: `/shelf/list/${warehouseId}`,
    method: "get",
  });
}
/**
 * æ–°å¢žè´§æž¶
 * @description åœ¨æŒ‡å®šä»“库下创建新的货架记录
 * @param {Object} data è´§æž¶ä¿¡æ¯å¯¹è±¡ï¼ŒåŒ…含货架名称、层数、列数等字段
 * @returns {Promise} è¿”回新增结果
 */
export function addShelf(data) {
  return request({
    url: "/warehouse/goodsShelves/add",
    method: "post",
    data,
  });
}
/**
 * æ›´æ–°è´§æž¶ä¿¡æ¯
 * @description ä¿®æ”¹çŽ°æœ‰è´§æž¶çš„åŸºæœ¬ä¿¡æ¯
 * @param {Object} data è´§æž¶ä¿¡æ¯å¯¹è±¡ï¼Œå¿…须包含货架ID
 * @returns {Promise} è¿”回更新结果
 */
export function updateShelf(data) {
  return request({
    url: "/warehouse/goodsShelves/update",
    method: "put",
    data,
  });
}
/**
 * åˆ é™¤è´§æž¶
 * @description æ ¹æ®è´§æž¶ID删除指定的货架记录
 * @param {string|number} id è´§æž¶ID
 * @returns {Promise} è¿”回删除结果
 */
export function deleteShelf(id) {
  return request({
    url: `/warehouse/goodsShelves/delete/${id}`,
    method: "delete",
  });
}
/**
 * èŽ·å–ä»“åº“ç»“æž„
 * @description èŽ·å–æŒ‡å®šä»“åº“çš„å®Œæ•´ç»“æž„ä¿¡æ¯ï¼ŒåŒ…æ‹¬è´§æž¶ã€å±‚æ•°ã€åˆ—æ•°ç­‰
 * @param {string|number} warehouseId ä»“库ID
 * @returns {Promise} è¿”回仓库的完整结构数据
 */
export function getWarehouseStructure(data) {
  return request({
    url: `/warehouse/goodsShelvesRowcol/list`,
    method: "get",
    params: data,
  });
}
src/api/fileManagement/borrow.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
import request from "@/utils/request";
// æ–‡æ¡£å€Ÿé˜…管理相关接口
// èŽ·å–æ–‡æ¡£åˆ—è¡¨ï¼ˆç”¨äºŽå€Ÿé˜…ä¹¦ç±é€‰æ‹©ï¼‰
export function getDocumentList() {
  return request({
    url: "/documentation/list",
    method: "get",
  });
}
// å€Ÿé˜…分页查询
export function getBorrowList(params) {
  return request({
    url: "/documentationBorrowManagement/listPage",
    method: "get",
    params: params,
  });
}
// æ–°å¢žå€Ÿé˜…
export function addBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/add",
    method: "post",
    data: data,
  });
}
// æ›´æ–°å€Ÿé˜…
export function updateBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å€Ÿé˜…
export function deleteBorrow(ids) {
  return request({
    url: "/documentationBorrowManagement/delete",
    method: "delete",
    data: ids,
  });
}
src/api/fileManagement/document.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,164 @@
import request from "@/utils/request";
// èŽ·å–åˆ†ç±»æ ‘
export function getCategoryTree() {
  return request({
    url: "/warehouse/documentClassification/getList",
    method: "get",
  });
}
// æ–°å¢žåˆ†ç±»
export function addCategory(data) {
  return request({
    url: "/warehouse/documentClassification/add",
    method: "post",
    data: {
      category: data.category,
      parentId: data.parentId,
    },
  });
}
// ä¿®æ”¹åˆ†ç±»
export function updateCategory(data) {
  return request({
    url: "/warehouse/documentClassification/update",
    method: "put",
    data: {
      id: data.id,
      category: data.category,
    },
  });
}
// åˆ é™¤åˆ†ç±»
export function deleteCategory(ids) {
  return request({
    url: "/warehouse/documentClassification/delete",
    method: "delete",
    data: ids,
  });
}
// èŽ·å–æ–‡æ¡£åˆ—è¡¨ï¼ˆåˆ†é¡µï¼‰
export function getDocumentList(query) {
  return request({
    url: "/documentation/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žæ–‡æ¡£
export function addDocument(data) {
  return request({
    url: "/documentation/add",
    method: "post",
    data: data,
  });
}
// ä¿®æ”¹æ–‡æ¡£
export function updateDocument(data) {
  return request({
    url: "/documentation/update",
    method: "put",
    data: data,
  });
}
// åˆ é™¤æ–‡æ¡£
export function deleteDocument(ids) {
  return request({
    url: "/documentation/delete",
    method: "delete",
    data: ids,
  });
}
// èŽ·å–æ–‡æ¡£è¯¦æƒ…
export function getDocumentDetail(id) {
  return request({
    url: "/document/" + id,
    method: "get",
  });
}
// æœç´¢æ–‡æ¡£
export function searchDocument(query) {
  return request({
    url: "/document/search",
    method: "get",
    params: query,
  });
}
// èŽ·å–ä»“åº“ç»“æž„
export function getWarehouseStructure() {
  return request({
    url: "/document/warehouse/structure",
    method: "get",
  });
}
// é™„件管理相关接口
// æ·»åР附件
export function addDocumentationFile(data) {
  return request({
    url: "/documentation/documentationFile/add",
    method: "post",
    data: data,
  });
}
// èŽ·å–é™„ä»¶åˆ—è¡¨
export function getDocumentationFileList(params) {
  return request({
    url: "/documentation/documentationFile/listPage",
    method: "get",
    params: params,
  });
}
// åˆ é™¤é™„ä»¶
export function deleteDocumentationFile(ids) {
  return request({
    url: "/documentation/documentationFile/del",
    method: "delete",
    data: ids,
  });
}
// æ–‡æ¡£å€Ÿé˜…管理相关接口
export function getBorrowList(params) {
  return request({
    url: "/documentationBorrowManagement/listPage",
    method: "get",
    params: params,
  });
}
export function addBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/add",
    method: "post",
    data: data,
  });
}
export function updateBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/update",
    method: "put",
    data: data,
  });
}
export function deleteBorrow(ids) {
  return request({
    url: "/documentationBorrowManagement/delete",
    method: "delete",
    data: ids,
  });
}
src/api/fileManagement/return.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢å½’还记录
export function getReturnListPage(query) {
  return request({
    url: "/documentationBorrowManagement/listPageReturn",
    method: "get",
    params: query,
  });
}
// å½’还操作
export function returnDocument(data) {
  return request({
    url: "/documentationBorrowManagement/revent",
    method: "put",
    data: data,
  });
}
// åˆ é™¤å½’还记录
export function deleteReturn(ids) {
  return request({
    url: "/documentationBorrowManagement/reventDelete",
    method: "delete",
    data: ids,
  });
}
// æ›´æ–°å€Ÿé˜…记录
export function updateBorrow(data) {
  return request({
    url: "/documentationBorrowManagement/update",
    method: "put",
    data: data,
  });
}
// å½’还更新
export function reventUpdate(data) {
  return request({
    url: "/documentationBorrowManagement/reventUpdate",
    method: "put",
    data: data,
  });
}
// èŽ·å–æ–‡æ¡£åˆ—è¡¨
export function getDocumentList() {
  return request({
    url: "/documentationBorrowManagement/list",
    method: "get",
  });
}
src/api/lavorissce/ledger.js
@@ -1,11 +1,11 @@
import request from '@/utils/request'
// åˆ†é¡µæŸ¥è¯¢
export function listPage(params) {
export function listPage(query) {
    return request({
        url: '/lavorIssue/listPage',
        method: 'get',
        params
        params: query
    })
}
@@ -18,6 +18,14 @@
    })
}
export function statisticsList(params) {
    return request({
        url: '/lavorIssue/statisticsList',
        method: 'get',
        params
    })
}
// æ·»åŠ 
export function add(data) {
    return request({
src/components/DynamicTable/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,402 @@
<template>
  <div class="dynamic-table-container">
    <el-table
      ref="tableRef"
      v-loading="loading"
      :data="tableData"
      :border="border"
      :height="height"
      :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
      style="width: 100%"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
    >
      <!-- é€‰æ‹©åˆ— -->
      <el-table-column
        v-if="showSelection"
        align="center"
        type="selection"
        width="55"
      />
      <!-- åºå·åˆ— -->
      <el-table-column
        v-if="showIndex"
        align="center"
        label="序号"
        type="index"
        width="60"
      />
      <!-- å›ºå®šåˆ—:部门 -->
      <el-table-column
        label="部门"
        prop="department"
        width="120"
        show-overflow-tooltip
        align="center"
      />
      <!-- å›ºå®šåˆ—:姓名 -->
      <el-table-column
        label="姓名"
        prop="name"
        width="100"
        show-overflow-tooltip
        align="center"
      />
      <!-- å›ºå®šåˆ—:工号 -->
      <el-table-column
        label="工号"
        prop="employeeId"
        width="100"
        show-overflow-tooltip
        align="center"
      />
      <!-- åŠ¨æ€åˆ—ï¼šæ ¹æ®å­—å…¸æ¸²æŸ“ -->
      <el-table-column
        v-for="(dictItem, index) in dynamicColumns"
        :key="dictItem.value"
        :label="dictItem.label"
        :prop="dictItem.value"
        :width="dictItem.width || 120"
        show-overflow-tooltip
        align="center"
      >
        <template #default="scope">
          <!-- æ ¹æ®å­—典类型渲染不同的显示方式 -->
          <template v-if="dictItem.renderType === 'tag'">
            <el-tag
              :type="getTagType(scope.row[dictItem.value])"
              size="small"
            >
              {{ getDictValueLabel(dictItem.dictType, scope.row[dictItem.value]) }}
            </el-tag>
          </template>
          <template v-else-if="dictItem.renderType === 'select'">
            <el-select
              v-model="scope.row[dictItem.value]"
              placeholder="请选择"
              size="small"
              @change="handleSelectChange(scope.row, dictItem.value, $event)"
            >
              <el-option
                v-for="option in dictItem.options"
                :key="option.value"
                :label="option.label"
                :value="option.value"
              />
            </el-select>
          </template>
          <template v-else-if="dictItem.renderType === 'input'">
            <el-input
              v-model="scope.row[dictItem.value]"
              size="small"
              placeholder="请输入"
              @blur="handleInputChange(scope.row, dictItem.value, $event)"
            />
          </template>
          <template v-else>
            <span>{{ getDictValueLabel(dictItem.dictType, scope.row[dictItem.value]) }}</span>
          </template>
        </template>
      </el-table-column>
      <!-- æ“ä½œåˆ— -->
      <el-table-column
        v-if="showActions"
        label="操作"
        width="150"
        align="center"
        fixed="right"
      >
        <template #default="scope">
          <el-button
            type="primary"
            link
            size="small"
            @click="handleEdit(scope.row, scope.$index)"
          >
            ç¼–辑
          </el-button>
          <el-button
            type="danger"
            link
            size="small"
            @click="handleDelete(scope.row, scope.$index)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µç»„ä»¶ -->
    <div v-if="showPagination" class="pagination-container">
      <el-pagination
        v-model:current-page="pagination.current"
        v-model:page-size="pagination.size"
        :page-sizes="[10, 20, 50, 100]"
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useDict } from '@/utils/dict'
// å®šä¹‰ç»„件属性
const props = defineProps({
  // è¡¨æ ¼æ•°æ®
  data: {
    type: Array,
    default: () => []
  },
  // å­—典类型数组,用于动态生成列
  dictTypes: {
    type: Array,
    default: () => []
  },
  // æ˜¯å¦æ˜¾ç¤ºé€‰æ‹©åˆ—
  showSelection: {
    type: Boolean,
    default: false
  },
  // æ˜¯å¦æ˜¾ç¤ºåºå·åˆ—
  showIndex: {
    type: Boolean,
    default: true
  },
  // æ˜¯å¦æ˜¾ç¤ºæ“ä½œåˆ—
  showActions: {
    type: Boolean,
    default: false
  },
  // æ˜¯å¦æ˜¾ç¤ºåˆ†é¡µ
  showPagination: {
    type: Boolean,
    default: false
  },
  // è¡¨æ ¼é«˜åº¦
  height: {
    type: [String, Number],
    default: 'auto'
  },
  // æ˜¯å¦æ˜¾ç¤ºè¾¹æ¡†
  border: {
    type: Boolean,
    default: true
  },
  // åŠ è½½çŠ¶æ€
  loading: {
    type: Boolean,
    default: false
  },
  // åˆ†é¡µé…ç½®
  pagination: {
    type: Object,
    default: () => ({
      current: 1,
      size: 10,
      total: 0
    })
  }
})
// å®šä¹‰äº‹ä»¶
const emit = defineEmits([
  'selection-change',
  'row-click',
  'edit',
  'delete',
  'select-change',
  'input-change',
  'size-change',
  'current-change'
])
// å“åº”式数据
const tableRef = ref(null)
const tableData = ref([])
// èŽ·å–å­—å…¸æ•°æ®
const dictData = ref({})
// åŠ¨æ€åˆ—é…ç½®
const dynamicColumns = computed(() => {
  const columns = []
  props.dictTypes.forEach(dictType => {
    const dictItems = dictData.value[dictType] || []
    // ä¸ºæ¯ä¸ªå­—典类型创建一个列,而不是为每个字典项创建列
    if (dictItems.length > 0) {
      columns.push({
        label: getDictLabel(dictType), // èŽ·å–å­—å…¸ç±»åž‹çš„æ˜¾ç¤ºåç§°
        value: dictType, // ä½¿ç”¨å­—典类型作为字段名
        width: 120,
        renderType: 'tag', // é»˜è®¤ä½¿ç”¨æ ‡ç­¾æ˜¾ç¤º
        options: dictItems, // æä¾›é€‰é¡¹
        dictType: dictType
      })
    }
  })
  return columns
})
// èŽ·å–å­—å…¸ç±»åž‹çš„æ˜¾ç¤ºåç§°
const getDictLabel = (dictType) => {
  const labelMap = {
    'sys_normal_disable': '状态',
    'sys_user_level': '级别',
    'sys_user_position': '职位',
    'sys_yes_no': '是否',
    'sys_user_sex': '性别',
    'sys_lavor_issue': '劳务问题'  // æ·»åŠ åŠ³åŠ¡é—®é¢˜å­—å…¸
  }
  return labelMap[dictType] || dictType
}
// èŽ·å–å­—å…¸æ•°æ®
const loadDictData = async () => {
  try {
    const dictPromises = props.dictTypes.map(async (dictType) => {
      const { getDicts } = await import('@/api/system/dict/data')
      const response = await getDicts(dictType)
      return {
        type: dictType,
        data: response.data.map(item => ({
          label: item.dictLabel,
          value: item.dictValue,
          elTagType: item.listClass,
          elTagClass: item.cssClass
        }))
      }
    })
    const results = await Promise.all(dictPromises)
    results.forEach(result => {
      dictData.value[result.type] = result.data
    })
  } catch (error) {
    console.error('加载字典数据失败:', error)
    // å¦‚果字典加载失败,使用默认数据
    props.dictTypes.forEach(dictType => {
      if (!dictData.value[dictType]) {
        dictData.value[dictType] = []
      }
    })
  }
}
// èŽ·å–æ ‡ç­¾ç±»åž‹
const getTagType = (value) => {
  // æ ¹æ®å€¼è¿”回不同的标签类型
  if (value === '1' || value === 'true' || value === '是') return 'success'
  if (value === '0' || value === 'false' || value === '否') return 'danger'
  if (value === '2' || value === 'warning') return 'warning'
  return 'info'
}
// èŽ·å–å­—å…¸å€¼çš„æ ‡ç­¾
const getDictValueLabel = (dictType, value) => {
  if (!value) return '-'
  const dictItems = dictData.value[dictType] || []
  const item = dictItems.find(item => item.value === value)
  return item ? item.label : value
}
// äº‹ä»¶å¤„理函数
const handleSelectionChange = (selection) => {
  emit('selection-change', selection)
}
const handleRowClick = (row, column, event) => {
  emit('row-click', row, column, event)
}
const handleEdit = (row, index) => {
  emit('edit', row, index)
}
const handleDelete = (row, index) => {
  emit('delete', row, index)
}
const handleSelectChange = (row, prop, value) => {
  emit('select-change', row, prop, value)
}
const handleInputChange = (row, prop, event) => {
  emit('input-change', row, prop, event.target.value)
}
const handleSizeChange = (size) => {
  emit('size-change', size)
}
const handleCurrentChange = (current) => {
  emit('current-change', current)
}
// ç›‘听数据变化
watch(() => props.data, (newData) => {
  tableData.value = newData
}, { immediate: true })
// ç›‘听字典类型变化
watch(() => props.dictTypes, () => {
  loadDictData()
}, { immediate: true })
// ç»„件挂载时加载字典数据
onMounted(() => {
  loadDictData()
})
// æš´éœ²æ–¹æ³•给父组件
defineExpose({
  tableRef,
  getSelection: () => tableRef.value?.getSelectionRows() || [],
  clearSelection: () => tableRef.value?.clearSelection(),
  toggleRowSelection: (row, selected) => tableRef.value?.toggleRowSelection(row, selected),
  setCurrentRow: (row) => tableRef.value?.setCurrentRow(row)
})
</script>
<style scoped>
.dynamic-table-container {
  width: 100%;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 8px 0;
}
:deep(.el-select) {
  width: 100%;
}
:deep(.el-input) {
  width: 100%;
}
</style>
src/views/example/DynamicTableExample.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,354 @@
<template>
  <div class="app-container">
    <div class="search-form">
      <el-form :inline="true" :model="searchForm">
        <el-form-item label="部门">
          <el-input
            v-model="searchForm.department"
            placeholder="请输入部门名称"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="姓名">
          <el-input
            v-model="searchForm.name"
            placeholder="请输入姓名"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="handleReset">重置</el-button>
          <el-button type="success" @click="handleAdd">新增</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table-container">
      <DynamicTable
        ref="dynamicTableRef"
        :data="tableData"
        :dict-types="dictTypes"
        :loading="loading"
        :show-selection="true"
        :show-actions="true"
        :show-pagination="true"
        :pagination="pagination"
        height="calc(100vh - 280px)"
        @selection-change="handleSelectionChange"
        @edit="handleEdit"
        @delete="handleDelete"
        @select-change="handleSelectChange"
        @input-change="handleInputChange"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
    <!-- æ–°å¢ž/编辑对话框 -->
    <el-dialog
      v-model="dialogVisible"
      :title="dialogTitle"
      width="600px"
      append-to-body
    >
      <el-form
        ref="formRef"
        :model="form"
        :rules="rules"
        label-width="100px"
      >
        <el-form-item label="部门" prop="department">
          <el-input v-model="form.department" placeholder="请输入部门" />
        </el-form-item>
        <el-form-item label="姓名" prop="name">
          <el-input v-model="form.name" placeholder="请输入姓名" />
        </el-form-item>
        <el-form-item label="工号" prop="employeeId">
          <el-input v-model="form.employeeId" placeholder="请输入工号" />
        </el-form-item>
        <!-- åŠ¨æ€è¡¨å•é¡¹ï¼šæ ¹æ®å­—å…¸ç”Ÿæˆ -->
        <el-form-item
          v-for="dictItem in dynamicFormItems"
          :key="dictItem.value"
          :label="dictItem.label"
          :prop="dictItem.value"
        >
          <el-select
            v-model="form[dictItem.value]"
            placeholder="请选择"
            style="width: 100%"
          >
            <el-option
              v-for="option in dictItem.options"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSubmit">确定</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import DynamicTable from '@/components/DynamicTable/index.vue'
// å“åº”式数据
const loading = ref(false)
const dialogVisible = ref(false)
const dialogTitle = ref('')
const editIndex = ref(-1)
const selectedRows = ref([])
// æœç´¢è¡¨å•
const searchForm = reactive({
  department: '',
  name: ''
})
// è¡¨æ ¼æ•°æ®
const tableData = ref([
  {
    id: 1,
    department: '技术部',
    name: '张三',
    employeeId: 'EMP001',
    status: '1',
    level: '2',
    position: '1'
  },
  {
    id: 2,
    department: '人事部',
    name: '李四',
    employeeId: 'EMP002',
    status: '0',
    level: '1',
    position: '2'
  },
  {
    id: 3,
    department: '财务部',
    name: '王五',
    employeeId: 'EMP003',
    status: '1',
    level: '3',
    position: '1'
  }
])
// å­—典类型配置
const dictTypes = ref([
  'sys_normal_disable', // çŠ¶æ€å­—å…¸
  'sys_user_level',     // çº§åˆ«å­—å…¸
  'sys_user_position'   // èŒä½å­—å…¸
])
// åˆ†é¡µé…ç½®
const pagination = reactive({
  current: 1,
  size: 10,
  total: 0
})
// è¡¨å•数据
const form = reactive({
  department: '',
  name: '',
  employeeId: '',
  status: '',
  level: '',
  position: ''
})
// è¡¨å•验证规则
const rules = {
  department: [
    { required: true, message: '请输入部门', trigger: 'blur' }
  ],
  name: [
    { required: true, message: '请输入姓名', trigger: 'blur' }
  ],
  employeeId: [
    { required: true, message: '请输入工号', trigger: 'blur' }
  ]
}
// åŠ¨æ€è¡¨å•é¡¹
const dynamicFormItems = computed(() => {
  // è¿™é‡Œå¯ä»¥æ ¹æ®å­—典数据动态生成表单项
  return [
    {
      label: '状态',
      value: 'status',
      options: [
        { label: '启用', value: '1' },
        { label: '禁用', value: '0' }
      ]
    },
    {
      label: '级别',
      value: 'level',
      options: [
        { label: '初级', value: '1' },
        { label: '中级', value: '2' },
        { label: '高级', value: '3' }
      ]
    },
    {
      label: '职位',
      value: 'position',
      options: [
        { label: '员工', value: '1' },
        { label: '主管', value: '2' },
        { label: '经理', value: '3' }
      ]
    }
  ]
})
// ç»„件引用
const dynamicTableRef = ref(null)
const formRef = ref(null)
// äº‹ä»¶å¤„理函数
const handleSearch = () => {
  // å®žçŽ°æœç´¢é€»è¾‘
  console.log('搜索条件:', searchForm)
  ElMessage.success('搜索功能待实现')
}
const handleReset = () => {
  searchForm.department = ''
  searchForm.name = ''
}
const handleAdd = () => {
  dialogTitle.value = '新增员工'
  editIndex.value = -1
  resetForm()
  dialogVisible.value = true
}
const handleEdit = (row, index) => {
  dialogTitle.value = '编辑员工'
  editIndex.value = index
  Object.assign(form, row)
  dialogVisible.value = true
}
const handleDelete = async (row, index) => {
  try {
    await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
      type: 'warning'
    })
    tableData.value.splice(index, 1)
    ElMessage.success('删除成功')
  } catch (error) {
    // ç”¨æˆ·å–消删除
  }
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
const handleSelectChange = (row, prop, value) => {
  console.log('选择变化:', row, prop, value)
  // å¯ä»¥åœ¨è¿™é‡Œå¤„理数据更新逻辑
}
const handleInputChange = (row, prop, value) => {
  console.log('输入变化:', row, prop, value)
  // å¯ä»¥åœ¨è¿™é‡Œå¤„理数据更新逻辑
}
const handleSizeChange = (size) => {
  pagination.size = size
  // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (current) => {
  pagination.current = current
  // é‡æ–°åŠ è½½æ•°æ®
}
const handleSubmit = async () => {
  try {
    await formRef.value.validate()
    if (editIndex.value === -1) {
      // æ–°å¢ž
      const newRow = {
        id: Date.now(),
        ...form
      }
      tableData.value.push(newRow)
      ElMessage.success('新增成功')
    } else {
      // ç¼–辑
      Object.assign(tableData.value[editIndex.value], form)
      ElMessage.success('编辑成功')
    }
    dialogVisible.value = false
  } catch (error) {
    console.error('表单验证失败:', error)
  }
}
const resetForm = () => {
  Object.assign(form, {
    department: '',
    name: '',
    employeeId: '',
    status: '',
    level: '',
    position: ''
  })
  formRef.value?.resetFields()
}
// ç»„件挂载时初始化数据
onMounted(() => {
  pagination.total = tableData.value.length
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.search-form {
  margin-bottom: 20px;
  padding: 20px;
  background-color: #f5f5f5;
  border-radius: 4px;
}
.table-container {
  background-color: #fff;
  border-radius: 4px;
  padding: 20px;
}
.dialog-footer {
  text-align: right;
}
</style>
src/views/example/SimpleExample.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,135 @@
<template>
  <div class="app-container">
    <!-- ç®€å•的搜索区域 -->
    <el-card class="search-card">
      <el-form :inline="true">
        <el-form-item label="部门">
          <el-input v-model="searchForm.department" placeholder="请输入部门" clearable />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- åŠ¨æ€è¡¨æ ¼ -->
    <el-card class="table-card">
      <template #header>
        <div class="card-header">
          <span>员工信息表</span>
          <el-button type="primary" size="small" @click="handleAdd">新增员工</el-button>
        </div>
      </template>
      <DynamicTable
        :data="tableData"
        :dict-types="dictTypes"
        :loading="loading"
        :show-selection="true"
        :show-actions="true"
        height="400px"
        @selection-change="handleSelectionChange"
        @edit="handleEdit"
        @delete="handleDelete"
      />
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import DynamicTable from '@/components/DynamicTable/index.vue'
// æœç´¢è¡¨å•
const searchForm = reactive({
  department: ''
})
// è¡¨æ ¼æ•°æ®
const tableData = ref([
  {
    id: 1,
    department: '技术部',
    name: '张三',
    employeeId: 'EMP001',
    sys_normal_disable: '1',  // çŠ¶æ€
    sys_user_level: '2',      // çº§åˆ«
    sys_user_position: '1'    // èŒä½
  },
  {
    id: 2,
    department: '人事部',
    name: '李四',
    employeeId: 'EMP002',
    sys_normal_disable: '0',  // çŠ¶æ€
    sys_user_level: '1',      // çº§åˆ«
    sys_user_position: '2'    // èŒä½
  }
])
// å­—典类型
const dictTypes = ref([
  'sys_normal_disable', // çŠ¶æ€ï¼šå¯ç”¨/禁用
  'sys_user_level',     // çº§åˆ«ï¼šåˆçº§/中级/高级
  'sys_user_position'   // èŒä½ï¼šå‘˜å·¥/主管/经理
])
// åŠ è½½çŠ¶æ€
const loading = ref(false)
// äº‹ä»¶å¤„理
const handleSearch = () => {
  loading.value = true
  // æ¨¡æ‹Ÿæœç´¢
  setTimeout(() => {
    loading.value = false
    ElMessage.success('搜索完成')
  }, 1000)
}
const handleReset = () => {
  searchForm.department = ''
}
const handleAdd = () => {
  ElMessage.info('新增功能待实现')
}
const handleSelectionChange = (selection) => {
  console.log('选中的行:', selection)
}
const handleEdit = (row, index) => {
  ElMessage.info(`编辑第${index + 1}行数据`)
}
const handleDelete = (row, index) => {
  ElMessage.warning(`删除第${index + 1}行数据`)
}
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.search-card {
  margin-bottom: 20px;
}
.table-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
:deep(.el-form-item) {
  margin-bottom: 0;
}
</style>
src/views/fileManagement/bookshelf/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,110 @@
<template>
  <div class="detail-container">
    <div class="header">
      <el-button @click="handleBack" type="primary" size="small">返回</el-button>
      <h2>图书详情</h2>
    </div>
    <div class="content" v-loading="loading">
      <el-card v-if="current">
        <template #header>
          <div class="card-header">
            <span>基本信息</span>
          </div>
        </template>
        <el-descriptions :column="2" border>
          <el-descriptions-item label="图书编号">{{ current.docNumber }}</el-descriptions-item>
          <el-descriptions-item label="图书名称">{{ current.docName }}</el-descriptions-item>
          <el-descriptions-item label="入库时间">{{ current.createTime }}</el-descriptions-item>
          <!-- <el-descriptions-item label="当前位置">{{ current.currentLocation }}</el-descriptions-item> -->
          <el-descriptions-item label="状态">{{ current.docStatus }}</el-descriptions-item>
        </el-descriptions>
        <!-- <div class="additional-info" v-if="current.description">
          <h4>图书简介</h4>
          <p>{{ current.description }}</p>
        </div> -->
      </el-card>
      <el-empty v-else description="暂无数据" />
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'
// å®šä¹‰props
const props = defineProps({
  current: {
    type: Object,
    required: true
  }
})
// å®šä¹‰emits
const emit = defineEmits(['hanldeBack'])
// å“åº”式数据
const loading = ref(false)
// const bookInfo = ref(null)
// æ–¹æ³•
const handleBack = () => {
  emit('hanldeBack')
}
</script>
<style scoped>
.detail-container {
  padding: 20px;
  height: 100%;
  background-color: #f5f5f5;
}
.header {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
  background-color: #fff;
  padding: 15px 20px;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header h2 {
  margin: 0 0 0 20px;
  color: #333;
}
.content {
  background-color: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-header {
  font-weight: bold;
  color: #333;
}
.additional-info {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #ebeef5;
}
.additional-info h4 {
  margin: 0 0 10px 0;
  color: #333;
  font-size: 16px;
}
.additional-info p {
  margin: 0;
  color: #666;
  line-height: 1.6;
}
</style>
src/views/fileManagement/bookshelf/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,688 @@
<template>
  <div class="sample">
    <div class="main-content" v-if="!isDetail">
      <div class="search">
                 <div class="search_thing">
           <div class="search_label">仓库名称:</div>
           <div class="search_input">
             <el-select v-model="entity.warehouseId" placeholder="选择仓库" size="small" @change="warehouseChange">
               <el-option v-for="item in warehouse" :key="item.id" :label="item.label" :value="item.id">
               </el-option>
             </el-select>
           </div>
         </div>
        <div class="search_thing">
          <div class="search_label">货架:</div>
          <div class="search_input">
            <el-select v-model="entity.shelfId" placeholder="选择货架" size="small" @change="handleShelf">
              <el-option v-for="item in shelf" :key="item.id" :label="item.label" :value="item.id">
              </el-option>
            </el-select>
          </div>
        </div>
          <!-- <div class="search_thing">
           <el-button size="small" @click="handleShelf(entity.shelfId,'')">重置</el-button>
           <el-button size="small" type="primary" @click="handleShelf(entity.shelfId)">查询</el-button>
         </div> -->
        <div class="btns">
          <el-button size="small" style="color:#3A7BFA" @click="keepVisible=true">维护</el-button>
          <el-button size="small" style="color:#3A7BFA" @click="warehouseVisible=true,isEdit=false">添加仓库</el-button>
          <el-button size="small" style="color:#3A7BFA" @click="shelvesVisible=true,isEdit=false"
            :disabled="entity.warehouseId==null">添加货架</el-button>
        </div>
      </div>
      <div class="table" v-loading="tableLoading">
        <table class="tables" style="table-layout:fixed;" v-if="tableList.length>0">
          <tbody>
            <tr v-for="(item,index) in tableList" :key="index">
              <td v-for="(m,i) in item" :key="i" class="content">
                <h4 v-if="m.row!=undefined">{{ m.row }} - {{ m.col }}</h4>
                <ul>
                  <el-tooltip
                  effect="dark"
                  placement="top"
                  v-for="(n,j) in m.documentationDtoList"
                  :key="j">
                    <template #content><span>{{ n.docName }}</span>
                      <span>&nbsp;[{{ n.docNumber }}]</span></template>
                    <li class="green"
                      @click="handelDetail(n)">
                      <i></i>
                      <span>{{ n.docName }}</span>
                      <span>&nbsp;[{{ n.docNumber }}] <span :style="{ color: getStatusColor(n.docStatus) }">({{ n.docStatus }})</span></span>
                    </li>
                  </el-tooltip>
                </ul>
              </td>
            </tr>
            <tr>
              <td v-for="(item,index) in rowList" :key="index" style="background: ghostwhite;height: 20px;">{{ item }}
              </td>
            </tr>
          </tbody>
        </table>
        <span v-else style="color: rgb(144, 147, 153);display: inline-block;position: absolute;top: 60%;left: 50%;transform: translate(-50%,-50%);">暂无数据</span>
      </div>
    </div>
    <Detail v-else @hanldeBack="isDetail=false" :current="current" />
    <!-- åº“位维护对话框 -->
    <el-dialog v-model="keepVisible" title="库位维护" width="350px" :append-to-body="true">
                                                       <el-tree :data="warehouse" ref="tree" node-key="id"
           highlight-current v-if="keepVisible"
           empty-text="暂无数据">
        <template #default="{ node, data }">
          <div class="custom-tree-node" style="width: 100%;">
            <el-row style="width: 100%;display: flex;align-items: center;">
              <el-col :span="14">
                <span>
                  <el-icon v-if="node.level < 2" class="folder-icon">
                    <FolderOpened />
                  </el-icon>
                  <el-icon v-else class="file-icon">
                    <Document />
                  </el-icon>
                  {{ data.label }}
                </span>
              </el-col>
              <el-col :span="10" v-if="node.level<3">
                <el-button type="link" size="small" :icon="Edit" @click.stop="handleEdit(data,node.level)">
                </el-button>
                <el-button type="danger" size="small" :icon="Delete" @click.stop="handleDelete(data,node.level)">
                </el-button>
              </el-col>
            </el-row>
          </div>
        </template>
      </el-tree>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="keepVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="keepVisible = false" >ç¡® å®š</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- ä»“库新增/修改对话框 -->
    <el-dialog v-model="warehouseVisible" :title="isEdit?'仓库修改':'仓库新增'" width="350px">
      <el-row>
        <el-col class="search_thing" :span="24">
          <div class="search_label"><span class="required-span">* </span>仓库名称:</div>
          <div class="search_input">
            <el-input v-model="name" size="small" @keyup.enter="confirmWarehouse"></el-input>
          </div>
        </el-col>
      </el-row>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="warehouseVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="confirmWarehouse" :loading="upLoadWarehouse">ç¡® å®š</el-button>
        </span>
      </template>
    </el-dialog>
    <!-- è´§æž¶æ–°å¢ž/修改对话框 -->
    <el-dialog v-model="shelvesVisible" :title="isEdit?'货架修改':'货架新增'" width="350px">
      <el-row>
        <el-col class="search_thing" :span="24">
          <div class="search_label"><span class="required-span">* </span>货架名称:</div>
          <div class="search_input">
            <el-input v-model="shelves.name" size="small"></el-input>
          </div>
        </el-col>
      </el-row>
      <el-row>
        <el-col class="search_thing" :span="24">
          <div class="search_label"><span class="required-span">* </span>货架层数:</div>
          <div class="search_input">
            <el-input v-model="shelves.row" size="small"></el-input>
          </div>
        </el-col>
      </el-row>
      <el-row>
        <el-col class="search_thing" :span="24">
          <div class="search_label"><span class="required-span">* </span>货架列数:</div>
          <div class="search_input">
            <el-input v-model="shelves.col" size="small"></el-input>
          </div>
        </el-col>
      </el-row>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="shelvesVisible = false">取 æ¶ˆ</el-button>
          <el-button type="primary" @click="confirmShelves" :loading="upLoadShelves">ç¡® å®š</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Edit, Delete, FolderOpened, Document } from '@element-plus/icons-vue'
import { getWarehouseList, addWarehouse, updateWarehouse, deleteWarehouse, getWarehouseStructure, addShelf, updateShelf, deleteShelf } from '@/api/fileManagement/bookshelf'
import Detail from './detail.vue'
// å“åº”式数据
const entity = reactive({
  warehouseId: null,
  shelfId: null
})
const warehouse = ref([])
const shelf = ref([])
const keepVisible = ref(false)
const warehouseVisible = ref(false)
const shelvesVisible = ref(false)
const upLoadWarehouse = ref(false)
const upLoadShelves = ref(false)
const tableList = ref([])
const rowList = ref([])
const value = ref('')
const name = ref('')
const shelves = reactive({})
const isEdit = ref(false)
const isDetail = ref(false)
const currentEdit = ref(null)
const tableLoading = ref(false)
const current = ref({})
// æ¨¡æ¿å¼•用
const organization = ref(null)
// ç›‘听器
watch(isEdit, (newVal) => {
  if (!newVal) {
    Object.keys(shelves).forEach(key => delete shelves[key])
  }
})
// æ–¹æ³•
const selectList = async () => {
  // è¿™é‡Œéœ€è¦æ›¿æ¢ä¸ºå®žé™…çš„API调用
  const res = await getWarehouseList()
  warehouse.value = res.data
  if (warehouse.value.length == 0) {
    entity.warehouseId = ''
    entity.shelfId = ''
    tableList.value = []
  }
  if (!entity.warehouseId && warehouse.value.length > 0) {
    entity.warehouseId = warehouse.value[0].id
    warehouseChange(entity.warehouseId)
    if (shelf.value.length > 0) {
      entity.shelfId = shelf.value[0].id
      handleShelf(entity.shelfId)
    } else {
      tableList.value = []
    }
  } else if (warehouse.value.length > 0) {
    warehouseChange(entity.warehouseId)
    if (shelf.value.length > 0) {
      entity.shelfId = shelf.value[0].id
      handleShelf(entity.shelfId)
    } else {
      tableList.value = []
    }
  }
}
const confirmWarehouse = () => {
  if (!name.value) {
    ElMessage.error('请填写仓库名称')
    return
  }
  upLoadWarehouse.value = true
  if (currentEdit.value && currentEdit.value.id) {
    // ä¿®æ”¹ä»“库
    // è¿™é‡Œéœ€è¦æ›¿æ¢ä¸ºå®žé™…çš„API调用
    updateWarehouse({
      id: currentEdit.value.id,
      warehouseName: name.value
    }).then(res => {
      upLoadWarehouse.value = false
      warehouseVisible.value = false
      currentEdit.value = null
      ElMessage.success('修改成功')
      selectList()
      name.value = ''
      warehouseChange(entity.warehouseId)
    })
  } else {
    // æ–°å¢žä»“库
    // è¿™é‡Œéœ€è¦æ›¿æ¢ä¸ºå®žé™…çš„API调用
    addWarehouse({
      warehouseName: name.value
    }).then(res => {
      upLoadWarehouse.value = false
      warehouseVisible.value = false
      ElMessage.success('添加成功')
      selectList()
      name.value = ''
      warehouseChange(entity.warehouseId)
    })
  }
}
const confirmShelves = () => {
  if (!shelves.name) {
    ElMessage.error('请填写货架名称')
    return
  }
  if (!shelves.row) {
    ElMessage.error('请填写货架层数')
    return
  }
  if (!shelves.col) {
    ElMessage.error('请填写货架列数')
    return
  }
  upLoadShelves.value = true
  if (currentEdit.value && currentEdit.value.id) {
    // ä¿®æ”¹
    updateShelf({
      id: currentEdit.value.id,
      name: shelves.name,
      row: Number(shelves.row),
      col: Number(shelves.col),
      warehouseId: entity.warehouseId
    }).then(res => {
      upLoadShelves.value = false
      shelvesVisible.value = false
      ElMessage.success('修改成功')
      selectList()
      currentEdit.value = {}
    }).catch(err => {
      upLoadShelves.value = false
      shelvesVisible.value = false
      ElMessage.error('修改失败')
    })
  } else {
    // æ–°å¢ž
    // è¿™é‡Œéœ€è¦æ›¿æ¢ä¸ºå®žé™…çš„API调用
      addShelf({
      name: shelves.name,
      row: Number(shelves.row),
      col: Number(shelves.col),
      warehouseId: entity.warehouseId
    }).then(res => {
      upLoadShelves.value = false
      shelvesVisible.value = false
      ElMessage.success('添加成功')
      selectList()
      Object.keys(shelves).forEach(key => delete shelves[key])
    }).catch(err => {
      upLoadShelves.value = false
      shelvesVisible.value = false
      ElMessage.error('添加失败')
    })
  }
  warehouseChange(entity.warehouseId)
}
const handleDelete = (row, level) => {
  ElMessageBox.confirm('是否删除当前数据?', "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning"
  }).then(() => {
    if (level == 1) {
      // åˆ é™¤ä»“库
      deleteWarehouse([row.id]).then(res => {
        ElMessage.success('删除成功')
        selectList()
      })
    } else {
      // åˆ é™¤è´§æž¶
      deleteShelf({
        id: row.id
      }).then(res => {
        ElMessage.success('删除成功')
        selectList()
      })
    }
    warehouseChange(entity.warehouseId)
  }).catch(() => {})
}
const handleEdit = (data, level) => {
  isEdit.value = true
  if (level == 1) {
    warehouseVisible.value = true
    currentEdit.value = data
    name.value = data.label
  } else {
    shelvesVisible.value = true
    currentEdit.value = data
    Object.assign(shelves, {
      name: data.label,
      row: data.row,
      col: data.col,
      warehouseId: data.warehouseId
    })
  }
}
const handelDetail = (row) => {
  current.value = row
  isDetail.value = true
}
// æ ¹æ®æ–‡æ¡£çŠ¶æ€è¿”å›žå¯¹åº”çš„é¢œè‰²
const getStatusColor = (status) => {
  if (status === '正常') {
    return '#34BD66' // ç»¿è‰²
  } else if (status === '借出') {
    return '#F56C6C' // çº¢è‰²
  }
  return '#606266' // é»˜è®¤é¢œè‰²
}
const warehouseChange = (val) => {
tableList.value = []
let map = warehouse.value.find(a => {
  return a && a.id === val ? a : null
})
if (map && map.children) {
  shelf.value = map.children
  entity.shelfId = ''
} else {
  shelf.value = []
}
currentEdit.value = null
}
const handleShelf = async(e) => {
  if (e) {
    tableLoading.value = true
    let data = []
    const res = await getWarehouseStructure({warehouseGoodsShelvesId:e})
    if(res.code == 200){
      data = res.data.map(m=>{
        m.books = m.documentationDtoList|[]
        return m
      })
    }else{
      ElMessage.error(res.message)
    }
    setTimeout(() => {
      tableLoading.value = false
      let set = new Set()
      tableList.value = []
      let arr = []
      if (data && data.length > 0) {
        data.forEach(m => {
          if (m && m.row && m.col) {
            set.add(m.col)
            if (arr.length > 0) {
              if (arr.find(n => n.row == m.row)) {
                arr.push(m)
              } else {
                tableList.value.push(arr)
                arr = []
                arr.push(m)
              }
            } else {
              arr.push(m)
            }
          }
        })
        if (arr.length > 0) {
          tableList.value.push(arr)
        }
      }
      rowList.value = []
      for (let i = 0; i < set.size; i++) {
        rowList.value.push(`${i + 1} åˆ—`)
      }
      console.log(6666, tableList.value,rowList.value,data)
    }, 1000)
  }
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  selectList()
})
</script>
<style scoped>
  .main-content {
    width: 100%;
    height: 100%;
    padding: 20px;
    box-sizing: border-box;
  }
  .title {
    height: 20px;
    line-height: 20px;
    margin-bottom: 20px;
  }
  .search {
    background-color: #fff;
    height: 80px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 20px;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    margin-bottom: 20px;
  }
  .search_thing {
    display: flex;
    align-items: center;
    height: 50px;
    margin-right: 20px;
  }
  .search_label {
    width: 90px;
    font-size: 14px;
    text-align: right;
    color: #606266;
    font-weight: 500;
    margin-right: 10px;
  }
  .search_input {
    width: 200px;
  }
  .table {
    background-color: #fff;
    width: 100%;
    height: calc(100% - 100px);
    padding: 20px;
    overflow-y: auto;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }
  .el-form-item {
    margin-bottom: 16px;
  }
  .btns {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .tables {
    width: 100%;
    height: 100%;
    border-collapse: collapse;
    border: 1px solid #e4e7ed;
  }
  .tables th {
    font-size: 14px;
    border: 1px solid #e4e7ed;
    background-color: #fafafa;
    padding: 8px;
    font-weight: 500;
  }
  .tables td {
    font-size: 12px;
    text-align: center;
    vertical-align: top;
    border: 1px solid #e4e7ed;
    padding: 8px;
    box-sizing: border-box;
    height: 120px;
    background-color: #fff;
  }
  .tables ul {
    list-style-type: none;
  }
  .tables ul li {
    border-radius: 3px;
    padding: 4px 10px;
    box-sizing: border-box;
    margin-bottom: 5px;
    font-size: 12px;
    display: flex;
    align-items: center;
    justify-content: start;
    color: #333333;
    cursor: pointer;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  .tables h4 {
    color: #999999;
    font-size: 14px;
    font-weight: 400;
    padding: 6px 0;
  }
  .tables i {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    margin-right: 6px;
  }
  li:hover {
    background: rgba(58, 123, 250, 0.18);
  }
  li:hover i {
    background: #3A7BFA;
  }
  li:hover .num {
    color: #3A7BFA;
  }
  .green {
    background: #E0F6EA;
  }
  .green i {
    background: #34BD66;
  }
  .green .num {
    color: #34BD66;
  }
  .el-dialog {
    position: relative;
  }
  .shaoma {
    display: flex;
    align-items: center;
    font-size: 14px;
    color: #3A7BFA;
    position: absolute;
    top: 23px;
    right: 54px;
    cursor: pointer;
  }
  .folder-icon {
    color: #409eff;
    font-size: 16px;
    margin-right: 6px;
  }
  .file-icon {
    color: #67c23a;
    font-size: 16px;
    margin-right: 6px;
  }
  .node_i {
    color: orange;
    font-size: 18px;
  }
  .custom-tree-node .el-button {
    opacity: 0;
  }
  .custom-tree-node:hover .el-button {
    opacity: 1;
  }
  :deep(.el-loading-mask) {
    z-index: 10;
  }
  .required-span {
    color: #f56c6c;
  }
  .table-row {
    border-bottom: 1px solid #e4e7ed;
  }
  .table-row:last-child {
    border-bottom: none;
  }
  .column-header {
    background-color: #fafafa !important;
    font-weight: 500;
    color: #606266;
  }
  .content {
    transition: background-color 0.2s ease;
  }
  .content:hover {
    background-color: #f5f7fa;
  }
</style>
src/views/fileManagement/borrow/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,584 @@
<template>
  <div class="app-container borrow-view">
    <!-- æŸ¥è¯¢åŒºåŸŸ -->
    <div class="search-container">
      <el-form :model="searchForm" :inline="true" class="search-form">
                 <el-form-item label="借阅状态:">
           <el-select v-model="searchForm.borrowStatus" placeholder="请选择借阅状态" clearable style="width: 150px">
             <el-option label="借阅" value="借阅" />
             <el-option label="归还" value="归还" />
           </el-select>
         </el-form-item>
        <el-form-item label="借阅人:">
          <el-input
            v-model="searchForm.borrower"
            placeholder="请输入借阅人"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="借阅日期范围:">
          <el-date-picker
            v-model="searchForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            style="width: 300px"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">
            <el-icon><Search /></el-icon>
            æŸ¥è¯¢
          </el-button>
          <el-button @click="handleReset">
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
        <el-form-item style="margin-left: auto;">
          <el-button type="primary" @click="openBorrowDia('add')">
            <el-icon><Plus /></el-icon>
            æ–°å¢žå€Ÿé˜…
          </el-button>
          <el-button
            type="danger"
            @click="handleBatchDelete"
            :disabled="selectedRows.length === 0"
          >
            <el-icon><Delete /></el-icon>
            æ‰¹é‡åˆ é™¤ ({{ selectedRows.length }})
          </el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- è¡¨æ ¼åŒºåŸŸ -->
    <div class="table-container">
      <PIMTable
        :table-data="borrowList"
        :column="tableColumns"
        :is-selection="true"
        :border="true"
        :table-loading="tableLoading"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
          layout: 'total, sizes, prev, pager, next, jumper'
        }"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
      />
    </div>
    <!-- å€Ÿé˜…新增/编辑对话框 -->
    <el-dialog
      v-model="borrowDia"
      :title="borrowOperationType === 'add' ? '新增借阅' : '编辑借阅'"
      width="800px"
      @close="closeBorrowDia"
      @keydown.enter.prevent
    >
      <el-form
        :model="borrowForm"
        label-width="140px"
        :rules="borrowRules"
        ref="borrowFormRef"
      >
        <el-row :gutter="20">
        </el-row>
                 <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="借阅人:" prop="borrower">
               <el-input v-model="borrowForm.borrower" placeholder="请输入借阅人" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="借阅书籍:" prop="documentationId">
               <el-select v-model="borrowForm.documentationId" placeholder="请选择借阅书籍" style="width: 100%">
                 <el-option
                   v-for="item in documentList"
                   :key="item.id"
                   :label="item.docName || item.name"
                   :value="item.id"
                 />
               </el-select>
             </el-form-item>
           </el-col>
         </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="借阅日期:" prop="borrowDate">
              <el-date-picker
                v-model="borrowForm.borrowDate"
                type="date"
                placeholder="选择借阅日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="应归还日期:" prop="dueReturnDate">
              <el-date-picker
                v-model="borrowForm.dueReturnDate"
                type="date"
                placeholder="选择应归还日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
        </el-row>
                 <el-row :gutter="20">
           <el-col :span="24">
             <el-form-item label="借阅目的:" prop="borrowPurpose">
               <el-input v-model="borrowForm.borrowPurpose" placeholder="请输入借阅目的" />
             </el-form-item>
           </el-col>
         </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:" prop="remark">
              <el-input
                v-model="borrowForm.remark"
                type="textarea"
                :rows="3"
                placeholder="请输入备注信息"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitBorrowForm">确认</el-button>
          <el-button @click="closeBorrowDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { Search, Refresh, Plus, Delete } from '@element-plus/icons-vue';
import PIMTable from '@/components/PIMTable/PIMTable.vue';
import { getBorrowList, addBorrow, updateBorrow, deleteBorrow, getDocumentList } from '@/api/fileManagement/borrow';
const { proxy } = getCurrentInstance();
// å“åº”式数据
const borrowDia = ref(false);
const borrowOperationType = ref("");
const tableLoading = ref(false);
const borrowList = ref([]);
const selectedRows = ref([]);
const documentList = ref([]); // æ–‡æ¡£åˆ—表,用于借阅书籍选择
// åˆ†é¡µç›¸å…³
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
// æŸ¥è¯¢è¡¨å•
const searchForm = reactive({
  documentationId: "",
  borrowStatus: "",
  borrower: "",
  returnerId: "",
  dateRange: []
});
// å€Ÿé˜…表单
const borrowForm = reactive({
  id: "",
  documentationId: "",
  borrower: "",
  returnerId: "",
  borrowPurpose: "",
  borrowDate: "",
  dueReturnDate: "",
  returnDate: "",
  borrowStatus: "",
  remark: ""
});
// è¡¨å•验证规则
const borrowRules = reactive({
  documentationId: [{ required: true, message: "请选择借阅书籍", trigger: "change" }],
  borrower: [{ required: true, message: "请输入借阅人", trigger: "blur" }],
  borrowPurpose: [{ required: true, message: "请输入借阅目的", trigger: "blur" }],
  borrowDate: [{ required: true, message: "请选择借阅日期", trigger: "change" }],
  dueReturnDate: [{ required: true, message: "请选择应归还日期", trigger: "change" }],
  borrowStatus: [{ required: true, message: "请选择借阅状态", trigger: "change" }]
});
// è¡¨æ ¼åˆ—配置
const tableColumns = ref([
  {
    label: '文档名称',
    prop: 'documentationId',
    width: '200',
    formatData: (params) => {
      if (!params) return '-';
      const doc = documentList.value.find(item => item.id === params);
      return doc ? (doc.docName || doc.name) : params;
    }
  },
  { label: '借阅人', prop: 'borrower' },
  { label: '借阅目的', prop: 'borrowPurpose' },
  { label: '借阅日期', prop: 'borrowDate' },
  { label: '应归还日期', prop: 'dueReturnDate' },
  {
    label: '借阅状态',
    prop: 'borrowStatus',
    width: '100',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      return params;
    },
         formatType: (params) => {
       if (params === '归还') return 'success';
       if (params === '借阅') return 'warning';
       return 'info';
     }
  },
  { label: '备注', prop: 'remark', width: '150' },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: '150',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openBorrowDia('edit', row)
        },
      },
      {
        name: "删除",
        type: "text",
        clickFun: (row) => {
          handleDelete(row)
        },
      },
    ],
  }
]);
// åˆå§‹åŒ–数据
const initData = async () => {
  await Promise.all([
    loadDocumentList(),
    loadBorrowList()
  ]);
};
// åŠ è½½æ–‡æ¡£åˆ—è¡¨
const loadDocumentList = async () => {
  try {
    const res = await getDocumentList();
    if (res.code === 200) {
      documentList.value = res.data || [];
    } else {
      ElMessage.error(res.msg || "获取文档列表失败");
      documentList.value = [];
    }
  } catch (error) {
    ElMessage.error("获取文档列表失败,请重试");
    documentList.value = [];
  }
};
// åŠ è½½å€Ÿé˜…åˆ—è¡¨
const loadBorrowList = async () => {
  try {
    tableLoading.value = true;
    // æž„建查询参数
    const query = {
      page: pagination.currentPage,
      size: pagination.pageSize,
      documentationId: searchForm.documentationId || undefined,
      borrowStatus: searchForm.borrowStatus || undefined,
      borrower: searchForm.borrower || undefined,
      returnerId: searchForm.returnerId || undefined,
      entryDateStart: searchForm.dateRange && searchForm.dateRange.length > 0 ? searchForm.dateRange[0] : undefined,
      entryDateEnd: searchForm.dateRange && searchForm.dateRange.length > 1 ? searchForm.dateRange[1] : undefined
    };
    // ç§»é™¤undefined的参数
    Object.keys(query).forEach(key => {
      if (query[key] === undefined) {
        delete query[key];
      }
    });
    const res = await getBorrowList(query);
    if (res.code === 200) {
      borrowList.value = res.data.records || [];
      pagination.total = res.data.total || 0;
    } else {
      ElMessage.error(res.msg || "获取借阅列表失败");
      borrowList.value = [];
      pagination.total = 0;
    }
    // é‡ç½®é€‰æ‹©çŠ¶æ€
    selectedRows.value = [];
  } catch (error) {
    ElMessage.error("获取借阅列表失败,请重试");
    borrowList.value = [];
    pagination.total = 0;
  } finally {
    tableLoading.value = false;
  }
};
// æŸ¥è¯¢
const handleSearch = () => {
  pagination.currentPage = 1;
  loadBorrowList();
};
// é‡ç½®æŸ¥è¯¢
const handleReset = () => {
  searchForm.documentationId = "";
  searchForm.borrowStatus = "";
  searchForm.borrower = "";
  searchForm.returnerId = "";
  searchForm.dateRange = [];
  pagination.currentPage = 1;
  loadBorrowList();
  ElMessage.success("查询条件已重置");
};
// æ‰“开借阅弹框
const openBorrowDia = (type, data) => {
  borrowOperationType.value = type;
  borrowDia.value = true;
  if (type === "edit") {
    // ç¼–辑模式,加载现有数据
    Object.assign(borrowForm, data);
  } else {
    // æ–°å¢žæ¨¡å¼ï¼Œæ¸…空表单
    Object.keys(borrowForm).forEach(key => {
      borrowForm[key] = "";
    });
         // è®¾ç½®é»˜è®¤çŠ¶æ€
     borrowForm.borrowStatus = "借阅";
    // è®¾ç½®å½“前日期为借阅日期
    borrowForm.borrowDate = new Date().toISOString().split('T')[0];
  }
};
// å…³é—­å€Ÿé˜…弹框
const closeBorrowDia = () => {
  proxy.$refs.borrowFormRef.resetFields();
  borrowDia.value = false;
};
// æäº¤å€Ÿé˜…表单
const submitBorrowForm = () => {
  proxy.$refs.borrowFormRef.validate(async (valid) => {
    if (valid) {
      try {
        if (borrowOperationType.value === "edit") {
          // ç¼–辑模式,更新现有数据
          const res = await updateBorrow({
            borrower:borrowForm.borrower,
            id: borrowForm.id,
            borrowPurpose: borrowForm.borrowPurpose,
            borrowDate: borrowForm.borrowDate,
            dueReturnDate: borrowForm.dueReturnDate,
            returnDate: borrowForm.returnDate,
            remark: borrowForm.remark
          });
          if (res.code === 200) {
            ElMessage.success("编辑成功");
            await loadBorrowList();
            closeBorrowDia();
          } else {
            ElMessage.error(res.msg || "编辑失败");
          }
        } else {
          // æ–°å¢žæ¨¡å¼ï¼Œæ·»åŠ æ–°æ•°æ®
          const res = await addBorrow({
            documentationId: borrowForm.documentationId,
            borrower: borrowForm.borrower,
            returnerId: borrowForm.returnerId,
            borrowPurpose: borrowForm.borrowPurpose,
            borrowDate: borrowForm.borrowDate,
            dueReturnDate: borrowForm.dueReturnDate,
            returnDate: borrowForm.returnDate,
            borrowStatus: borrowForm.borrowStatus,
            remark: borrowForm.remark
          });
          if (res.code === 200) {
            ElMessage.success("新增成功");
            await loadBorrowList();
            closeBorrowDia();
          } else {
            ElMessage.error(res.msg || "新增失败");
          }
        }
      } catch (error) {
        ElMessage.error("操作失败,请重试");
      }
    }
  });
};
// åˆ é™¤å€Ÿé˜…记录
const handleDelete = (row) => {
  ElMessageBox.confirm(
    `确定要删除这条借阅记录吗?`,
    "删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  ).then(async () => {
    try {
      const res = await deleteBorrow([row.id]);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        await loadBorrowList();
      } else {
        ElMessage.error(res.msg || "删除失败");
      }
    } catch (error) {
      ElMessage.error("删除失败,请重试");
    }
  }).catch(() => {
    ElMessage.info("已取消删除");
  });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning("请选择要删除的记录");
    return;
  }
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.value.length} æ¡å€Ÿé˜…记录吗?`,
    "批量删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  ).then(async () => {
    try {
      const selectedIds = selectedRows.value.map(row => row.id);
      const res = await deleteBorrow(selectedIds);
      if (res.code === 200) {
        ElMessage.success("批量删除成功");
        await loadBorrowList();
      } else {
        ElMessage.error(res.msg || "批量删除失败");
      }
    } catch (error) {
      ElMessage.error("批量删除失败,请重试");
    }
  }).catch(() => {
    ElMessage.info("已取消删除");
  });
};
// é€‰æ‹©å˜åŒ–事件
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// å¤„理分页变化
const handlePagination = (current, size) => {
  pagination.currentPage = current;
  pagination.pageSize = size;
  loadBorrowList();
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  initData();
});
</script>
<style scoped>
.borrow-view {
  padding: 20px;
}
.search-container {
  background: #ffffff;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.search-form {
  margin: 0;
}
.table-container {
  background: #ffffff;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.empty-data {
  text-align: center;
  color: #909399;
  padding: 40px;
  font-size: 14px;
}
.dialog-footer {
  text-align: right;
}
:deep(.el-form-item__label) {
  font-weight: 500;
  color: #303133;
}
:deep(.el-input__wrapper) {
  box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__wrapper:hover) {
  box-shadow: 0 0 0 1px #c0c4cc inset;
}
:deep(.el-input__wrapper.is-focus) {
  box-shadow: 0 0 0 1px #409eff inset;
}
</style>
src/views/fileManagement/document/attachmentManager.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,426 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件管理" width="60%" :before-close="handleClose">
    <div class="attachment-manager">
      <!-- ä¸Šä¼ åŒºåŸŸ -->
      <div class="upload-section">
        <el-upload
          ref="uploadRef"
          :action="uploadUrl"
          :headers="uploadHeaders"
          :before-upload="handleBeforeUpload"
          :on-success="handleUploadSuccess"
          :on-error="handleUploadError"
          :on-remove="handleRemove"
          :file-list="fileList"
          multiple
          :limit="10"
          :show-file-list="false"
          :data="{documentId: currentDocumentId}"
          accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.xml,.jpg,.jpeg,.png,.gif,.bmp,.rar,.zip,.7z"
        >
          <el-button type="primary" :icon="Plus">上传附件</el-button>
          <template #tip>
            <div class="el-upload__tip">
              æ”¯æŒæ ¼å¼ï¼šdoc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
              <br>单个文件大小不超过50MB
            </div>
          </template>
        </el-upload>
      </div>
      <!-- é™„件列表 -->
      <div class="attachment-list">
        <el-table :data="fileList" border height="400px" v-loading="loading">
          <el-table-column label="序号" type="index" width="60" align="center" />
          <el-table-column label="附件名称" prop="name" min-width="200" show-overflow-tooltip />
          <el-table-column label="文件大小" prop="size" width="100" align="center">
            <template #default="scope">
              {{ formatFileSize(scope.row.size) }}
            </template>
          </el-table-column>
          <el-table-column label="上传时间" prop="uploadTime" width="160" align="center">
            <template #default="scope">
              {{ formatDate(scope.row.uploadTime) }}
            </template>
          </el-table-column>
          <el-table-column label="状态" prop="status" width="80" align="center">
            <template #default="scope">
              <el-tag :type="scope.row.status === 'success' ? 'success' : 'danger'" size="small">
                {{ scope.row.status === 'success' ? '成功' : '失败' }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column fixed="right" label="操作" width="200" align="center">
            <template #default="scope">
              <el-button link type="primary" size="small" @click="previewFile(scope.row)">
                é¢„览
              </el-button>
              <el-button link type="primary" size="small" @click="downloadFile(scope.row)">
                ä¸‹è½½
              </el-button>
              <el-button link type="danger" size="small" @click="removeFile(scope.row)">
                åˆ é™¤
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <!-- æ–‡ä»¶é¢„览组件 -->
    <filePreview ref="filePreviewRef" />
  </el-dialog>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { getToken } from "@/utils/auth"
import { addDocumentationFile, getDocumentationFileList, deleteDocumentationFile } from '@/api/fileManagement/document'
import filePreview from '@/components/filePreview/index.vue'
const props = defineProps({
  // documentId é€šè¿‡ open äº‹ä»¶ä¼ å…¥ï¼Œä¸éœ€è¦ä½œä¸º props
})
const emit = defineEmits(['update:attachments'])
const dialogVisible = ref(false)
const loading = ref(false)
const fileList = ref([])
const uploadRef = ref()
const filePreviewRef = ref()
const currentDocumentId = ref('') // å†…部管理当前文档ID
// ä¸Šä¼ é…ç½®
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload"
const uploadHeaders = computed(() => ({
  Authorization: "Bearer " + getToken()
}))
// æ‰“开弹框
const open = (attachments = [], documentId = '') => {
  dialogVisible.value = true
  currentDocumentId.value = documentId // è®¾ç½®å½“前文档ID
  // å¦‚果有文档ID,则加载附件列表
  if (documentId) {
    loadAttachmentList(documentId)
  } else {
    fileList.value = attachments || []
    // total.value = fileList.value.length // Removed total.value
  }
  // currentPage.value = 1 // Removed currentPage.value
}
// åŠ è½½é™„ä»¶åˆ—è¡¨
const loadAttachmentList = async (documentId) => {
  try {
    loading.value = true
    const params = {
      page: 1, // Always load from page 1
      size: 1000, // Load all for now
      documentationId: documentId
    }
    const res = await getDocumentationFileList(params)
    if (res.code === 200) {
      const records = res.data
      // è½¬æ¢æ•°æ®æ ¼å¼
      fileList.value = records.map(item => ({
        id: item.id,
        name: item.name,
        size: item.fileSize,
        url: item.url,
        uploadTime: item.createTime || item.uploadTime,
        status: 'success',
        uid: item.id
      }))
      // total.value = totalCount // Removed total.value
    } else {
      ElMessage.error(res.msg || '获取附件列表失败')
      fileList.value = []
      // total.value = 0 // Removed total.value
    }
  } catch (error) {
    console.error('获取附件列表失败:', error)
    ElMessage.error('获取附件列表失败')
    fileList.value = []
    // total.value = 0 // Removed total.value
  } finally {
    loading.value = false
  }
}
// å…³é—­å¼¹æ¡†
const handleClose = () => {
  dialogVisible.value = false
  emit('update:attachments', fileList.value)
}
// æ–‡ä»¶ä¸Šä¼ å‰æ ¡éªŒ
const handleBeforeUpload = (file) => {
  // æ£€æŸ¥æ–‡ä»¶å¤§å°ï¼ˆ50MB)
  const isLt50M = file.size / 1024 / 1024 < 50
  if (!isLt50M) {
    ElMessage.error('文件大小不能超过50MB!')
    return false
  }
  // æ£€æŸ¥æ–‡ä»¶ç±»åž‹
  const allowedTypes = [
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/vnd.ms-powerpoint',
    'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'application/pdf',
    'text/plain',
    'text/xml',
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/bmp',
    'application/x-rar-compressed',
    'application/zip',
    'application/x-7z-compressed'
  ]
  if (!allowedTypes.includes(file.type)) {
    ElMessage.error('不支持的文件类型!')
    return false
  }
  return true
}
// æ–‡ä»¶ä¸Šä¼ æˆåŠŸ
const handleUploadSuccess = (response, file, fileList) => {
  console.log('文件上传成功响应:', response);
  console.log('文件信息:', file);
  if (response.code === 200) {
    // æž„建附件数据 - ç¡®ä¿æ­£ç¡®èŽ·å–URL
    const attachmentData = {
      name: file.name,
      url: response.data.url || response.data.path || response.data.tempPath || file.url,
      fileSize: file.size,
      documentationId: currentDocumentId.value
    };
    console.log('构建的附件数据:', attachmentData);
    // è°ƒç”¨ä¿å­˜é™„件接口
    saveAttachment(attachmentData, file, fileList);
  } else {
    ElMessage.error(response.msg || '文件上传失败')
  }
}
// ä¿å­˜é™„件信息
const saveAttachment = async (attachmentData, file, fileList) => {
  try {
    console.log('开始保存附件,数据:', attachmentData);
    // ç¡®ä¿URL字段存在且有效
    if (!attachmentData.url) {
      console.error('附件URL为空,无法保存');
      ElMessage.error('文件URL获取失败,无法保存附件');
      return;
    }
    const res = await addDocumentationFile(attachmentData);
    console.log('保存附件接口响应:', res);
    if (res.code === 200) {
      const newFile = {
        id: res.data.id || Date.now(),
        name: attachmentData.name,
        size: attachmentData.fileSize,
        url: attachmentData.url,
        uploadTime: new Date().toISOString(),
        status: 'success',
        uid: file.uid
      }
      console.log('创建的新文件对象:', newFile);
      fileList.push(newFile)
      ElMessage.success('文件上传并保存成功')
      // ä¿å­˜æˆåŠŸåŽåˆ·æ–°é™„ä»¶åˆ—è¡¨
      if (currentDocumentId.value) {
        await loadAttachmentList(currentDocumentId.value);
      }
    } else {
      ElMessage.error(res.msg || '保存附件信息失败')
      // ä¿å­˜å¤±è´¥æ—¶ç§»é™¤æ–‡ä»¶
      const index = fileList.findIndex(item => item.uid === file.uid)
      if (index > -1) {
        fileList.splice(index, 1)
      }
    }
  } catch (error) {
    console.error('保存附件失败:', error)
    ElMessage.error('保存附件信息失败')
    // ä¿å­˜å¤±è´¥æ—¶ç§»é™¤æ–‡ä»¶
    const index = fileList.findIndex(item => item.uid === file.uid)
    if (index > -1) {
      fileList.splice(index, 1)
    }
  }
}
// æ–‡ä»¶ä¸Šä¼ å¤±è´¥
const handleUploadError = (error, file, fileList) => {
  console.error('文件上传失败:', error);
  console.error('失败的文件:', file);
  console.error('当前文件列表:', fileList);
  ElMessage.error('文件上传失败,请检查网络连接或文件格式')
}
// ç§»é™¤æ–‡ä»¶
const handleRemove = (file, fileList) => {
  const index = fileList.findIndex(item => item.uid === file.uid)
  if (index > -1) {
    fileList.splice(index, 1)
    // total.value = fileList.length // Removed total.value
  }
}
// åˆ é™¤æ–‡ä»¶
const removeFile = (file) => {
  ElMessageBox.confirm(`确定要删除文件 "${file.name}" å—?`, '删除确认', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async () => {
    try {
      // è°ƒç”¨åˆ é™¤æŽ¥å£
      const res = await deleteDocumentationFile([file.id]);
      if (res.code === 200) {
        // ä»Žæœ¬åœ°åˆ—表中移除
        const index = fileList.value.findIndex(item => item.id === file.id);
        if (index > -1) {
          fileList.value.splice(index, 1);
        }
        ElMessage.success('删除成功');
        // å¦‚果有文档ID,刷新附件列表
        if (currentDocumentId.value) {
          await loadAttachmentList(currentDocumentId.value);
        }
      } else {
        ElMessage.error(res.msg || '删除失败');
      }
    } catch (error) {
      console.error('删除附件失败:', error);
      ElMessage.error('删除附件失败');
    }
  }).catch(() => {
    // å–消删除
  })
}
// é¢„览文件
const previewFile = (file) => {
  if (file.url) {
    filePreviewRef.value.open(file.url)
  } else {
    ElMessage.warning('文件地址无效,无法预览')
  }
}
// ä¸‹è½½æ–‡ä»¶
const downloadFile = (file) => {
  if (file.url) {
    // åˆ›å»ºä¸‹è½½é“¾æŽ¥
    const link = document.createElement('a')
    link.href = file.url
    link.download = file.name
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    ElMessage.success('开始下载文件')
  } else {
    ElMessage.warning('文件地址无效,无法下载')
  }
}
// æ ¼å¼åŒ–文件大小
const formatFileSize = (bytes) => {
  if (bytes === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// æ ¼å¼åŒ–日期
const formatDate = (dateString) => {
  if (!dateString) return ''
  const date = new Date(dateString)
  return date.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit'
  })
}
// æµ‹è¯•文件上传
const testUpload = () => {
  console.log('当前文档ID:', currentDocumentId.value);
  console.log('上传URL:', uploadUrl);
  console.log('上传Headers:', uploadHeaders.value);
}
// æš´éœ²æ–¹æ³•
defineExpose({
  open,
  loadAttachmentList,
  testUpload
})
</script>
<style scoped>
.attachment-manager {
  padding: 20px;
}
.upload-section {
  margin-bottom: 20px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  border: 2px dashed #d9d9d9;
}
.upload-section:hover {
  border-color: #409eff;
}
.attachment-list {
  margin-bottom: 20px;
}
.el-upload__tip {
  margin-top: 10px;
  color: #666;
  font-size: 12px;
  line-height: 1.5;
}
:deep(.el-upload) {
  width: 100%;
}
:deep(.el-upload-dragger) {
  width: 100%;
  height: 120px;
}
</style>
src/views/fileManagement/document/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1262 @@
<template>
  <div class="app-container document-view">
    <div class="left">
      <div>
        <el-input
          v-model="search"
          style="width: 210px"
          placeholder="输入关键字进行搜索"
          @change="searchFilter"
          @clear="searchFilter"
          clearable
          prefix-icon="Search"
        />
        <el-button
          type="primary"
          @click="openCategoryDia('addOne')"
          style="margin-left: 10px"
          >新增分类</el-button
        >
      </div>
      <div ref="containerRef">
        <el-tree
          ref="tree"
          v-loading="treeLoad"
          :data="categoryList"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          default-expand-all
          :default-expanded-keys="expandedKeys"
          :draggable="true"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'category' }"
          highlight-current
          node-key="id"
          style="
            height: calc(100vh - 190px);
            overflow-y: scroll;
            scrollbar-width: none;
            margin-top: 10px;
          "
        >
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span class="tree-node-content">
                <el-icon class="orange-icon">
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                {{ data.category }}
              </span>
              <div>
                <el-button
                  type="primary"
                  link
                  @click="openCategoryDia('edit', data)"
                >
                  ç¼–辑
                </el-button>
                <el-button
                  type="primary"
                  link
                  @click="openCategoryDia('addSub', data)"
                  v-if="node.level < 2"
                >
                  æ·»åŠ å­åˆ†ç±»
                </el-button>
                <el-button
                  v-if="!node.childNodes.length"
                  style="margin-left: 4px"
                  type="danger"
                  link
                  @click="removeCategory(node, data)"
                >
                  åˆ é™¤
                </el-button>
              </div>
            </div>
          </template>
        </el-tree>
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px" v-if="isShowButton">
        <el-button type="primary" @click="openDocumentDia('add')">
          æ–°å¢žæ–‡æ¡£
        </el-button>
        <el-button
          type="danger"
          @click="handleDelete"
          style="margin-left: 10px"
          plain
          :disabled="selectedRows.length === 0"
        >
          åˆ é™¤ ({{ selectedRows.length }})
        </el-button>
      </div>
      <div class="table-container">
        <!-- PIMTable ç»„ä»¶ -->
        <PIMTable
          :table-data="documentList"
          :column="tableColumns"
          :is-selection="true"
          :border="true"
          :table-loading="tableLoading"
          :page="{
            current: pagination.currentPage,
            size: pagination.pageSize,
            total: pagination.total,
            layout: 'total, sizes, prev, pager, next, jumper'
          }"
          @selection-change="handleSelectionChange"
          @pagination="handlePagination"
        />
      </div>
    </div>
    <!-- åˆ†ç±»æ–°å¢ž/修改对话框 -->
    <el-dialog v-model="categoryDia" title="分类" width="400px" @keydown.enter.prevent>
      <el-form
        :model="categoryForm"
        label-width="140px"
        label-position="top"
        :rules="categoryRules"
        ref="categoryFormRef"
      >
        <el-row :gutter="30">
          <el-col :span="24" v-if="categoryOperationType === 'addSub'">
            <el-form-item label="父分类:" prop="parentName">
              <el-input
                v-model="categoryForm.parentName"
                placeholder="父分类名称"
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="分类名称:" prop="category">
              <el-input
                v-model="categoryForm.category"
                placeholder="请输入分类名称"
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitCategoryForm">确认</el-button>
          <el-button @click="closeCategoryDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- æ–‡æ¡£æ–°å¢ž/修改对话框 -->
    <el-dialog
      v-model="documentDia"
      :title="documentOperationType === 'add' ? '新增文档' : '编辑文档'"
      width="600px"
      @close="closeDocumentDia"
      @keydown.enter.prevent
    >
      <el-form
        :model="documentForm"
        label-width="140px"
        label-position="top"
        :rules="documentRules"
        ref="documentFormRef"
      >
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="文档名称:" prop="docName">
              <el-input v-model="documentForm.docName" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="年度:" prop="year">
              <el-date-picker
                v-model="documentForm.year"
                type="year"
                value-format="YYYY"
                format="YYYY"
                placeholder="选择年度"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="文档编号:" prop="docNumber">
              <el-input v-model="documentForm.docNumber" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="责任人:" prop="responsiblePerson">
              <el-input v-model="documentForm.responsiblePerson" placeholder="请输入" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="文档分类:" prop="documentClassificationId">
              <el-select v-model="documentForm.documentClassificationId" placeholder="请选择文档分类" style="width: 100%">
                <el-option
                  v-for="item in categoryList"
                  :key="item.id"
                  :label="item.category"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="文档放置位置:" prop="warehouseGoodsShelvesRowcolId">
              <el-tree-select
                v-model="documentForm.warehouseGoodsShelvesRowcolId"
                :data="locationTree"
                placeholder="请选择文件放置位置"
                clearable
                check-strictly
                :render-after-expand="false"
                :props="{ children: 'children', label: 'label', value: 'value' }"
                style="width: 100%"
                @change="handleLocationChange"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="文档日期:" prop="docData">
              <el-date-picker
                v-model="documentForm.docData"
                type="date"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                placeholder="选择日期"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="保管期限:" prop="retentionPeriod">
              <el-select v-model="documentForm.retentionPeriod" placeholder="请选择">
                <el-option
                  v-for="item in retention_period"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="保密级别:" prop="securityLevel">
              <el-select v-model="documentForm.securityLevel" placeholder="请选择">
                <el-option
                  v-for="item in confidentiality_level"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="分数:" prop="copyCount">
              <el-input v-model="documentForm.copyCount" placeholder="请输入" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="页数:" prop="pageCount">
              <el-input v-model="documentForm.pageCount" placeholder="请输入" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="文档类别:" prop="docCategory">
              <el-select v-model="documentForm.docCategory" placeholder="请选择">
                <el-option
                  v-for="item in document_type"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="文档种类:" prop="docType">
              <el-select v-model="documentForm.docType" placeholder="请选择">
                <el-option
                  v-for="item in document_categories"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="紧急程度:" prop="urgencyLevel">
              <el-select v-model="documentForm.urgencyLevel" placeholder="请选择">
                <el-option
                  v-for="item in document_urgency"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="文档状态:" prop="docStatus">
              <el-select v-model="documentForm.docStatus" placeholder="请选择">
                <el-option
                  v-for="item in document_status"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:" prop="remark">
              <el-input
                v-model="documentForm.remark"
                type="textarea"
                :rows="3"
                placeholder="请输入备注信息"
              />
            </el-form-item>
          </el-col>
        </el-row>
       </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitDocumentForm">确认</el-button>
          <el-button @click="closeDocumentDia">取消</el-button>
        </div>
             </template>
     </el-dialog>
             <AttachmentManager ref="attachmentManagerRef" />
     </div>
   </template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance, toRefs, watch } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { ArrowRight, Folder, FolderOpened, Tickets, Document } from '@element-plus/icons-vue';
import PIMTable from '@/components/PIMTable/PIMTable.vue';
import { getToken } from "@/utils/auth";
import { getCategoryTree, addCategory, updateCategory, deleteCategory, getDocumentList, addDocument, updateDocument, deleteDocument, getDocumentDetail, searchDocument, getWarehouseStructure } from '@/api/fileManagement/document'
import { getWarehouseList } from '@/api/fileManagement/bookshelf'
import AttachmentManager from './attachmentManager.vue'
import { useDict } from '@/utils/dict'
const { proxy } = getCurrentInstance();
const tree = ref(null);
const containerRef = ref(null);
// ä½¿ç”¨å­—典数据
const { confidentiality_level, document_urgency, document_status, document_type, document_categories, retention_period } = useDict('confidentiality_level', 'document_urgency', 'document_status', 'document_type', 'document_categories', 'retention_period')
// ç›‘听字典数据变化
watch([confidentiality_level, document_urgency, document_status, document_type, document_categories, retention_period], () => {
  // å­—典数据已更新
}, { immediate: true, deep: true });
const categoryDia = ref(false);
const documentDia = ref(false);
const categoryOperationType = ref("");
const documentOperationType = ref("");
const search = ref("");
const currentId = ref("");
const currentParentId = ref("");
const treeLoad = ref(false);
const categoryList = ref([]);
const expandedKeys = ref([]);
const documentList = ref([]);
const isShowButton = ref(false);
const selectedRows = ref([]);
const selectAll = ref(false);
const isIndeterminate = ref(false);
const tableLoading = ref(false);
const attachmentManagerRef = ref(null);
// æ–‡ä»¶ä¸Šä¼ é…ç½®
const upload = reactive({
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  headers: { Authorization: "Bearer " + getToken() },
});
// ä½ç½®æ ‘数据
const locationTree = ref([]);
// è¡¨æ ¼åˆ—配置
const tableColumns = ref([
  { label: '文档名称', prop: 'docName', width: '200' },
  { label: '文档编号', prop: 'docNumber', width: '120' },
  { label: '年度', prop: 'year', width: '80' },
  { label: '责任人', prop: 'responsiblePerson', width: '100' },
  {
    label: '文档放置位置',
    prop: 'warehouseGoodsShelvesRowcolId',
    width: '150',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      return getLocationName(params);
    }
  },
  { label: '文档日期', prop: 'docData', width: '120' },
  {
    label: '保管期限',
    prop: 'retentionPeriod',
    width: '100',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      if (!retention_period.value || retention_period.value.length === 0) {
        return params;
      }
      const item = retention_period.value.find(item => item.value == params);
      return item ? item.label : params;
    },
    formatType: (params) => {
      if (params === null || params === undefined || params === '') return 'info';
      if (!retention_period.value || retention_period.value.length === 0) {
        return 'info';
      }
      const item = retention_period.value.find(item => item.value == params);
      const validTypes = ['success', 'warning', 'danger', 'info'];
      return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
    }
  },
  {
    label: '保密级别',
    prop: 'securityLevel',
    width: '80',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      if (!confidentiality_level.value || confidentiality_level.value.length === 0) {
        return params;
      }
      const item = confidentiality_level.value.find(item => item.value == params);
      return item ? item.label : params;
    },
    formatType: (params) => {
      if (params === null || params === undefined || params === '') return 'info';
      if (!confidentiality_level.value || confidentiality_level.value.length === 0) {
        return 'info';
      }
      const item = confidentiality_level.value.find(item => item.value == params);
      const validTypes = ['success', 'warning', 'danger', 'info'];
      return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
    }
  },
  { label: '分数', prop: 'copyCount', width: '80' },
  { label: '页数', prop: 'pageCount', width: '80' },
  {
    label: '文档类别',
    prop: 'docCategory',
    width: '100',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      if (!document_type.value || document_type.value.length === 0) {
        return params;
      }
      const item = document_type.value.find(item => item.value == params);
      return item ? item.label : params;
    },
    formatType: (params) => {
      if (params === null || params === undefined || params === '') return 'info';
      if (!document_type.value || document_type.value.length === 0) {
        return 'info';
      }
      const item = document_type.value.find(item => item.value == params);
      const validTypes = ['success', 'warning', 'danger', 'info'];
      return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
    }
  },
  {
    label: '文档种类',
    prop: 'docType',
    width: '100',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      if (!document_categories.value || document_categories.value.length === 0) {
        return params;
      }
      const item = document_categories.value.find(item => item.value == params);
      return item ? item.label : params;
    },
    formatType: (params) => {
      if (params === null || params === undefined || params === '') return 'info';
      if (!document_categories.value || document_categories.value.length === 0) {
        return 'info';
      }
      const item = document_categories.value.find(item => item.value == params);
      const validTypes = ['success', 'warning', 'danger', 'info'];
      return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
    }
  },
  {
    label: '紧急程度',
    prop: 'urgencyLevel',
    width: '100',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      if (!document_urgency.value || document_urgency.value.length === 0) {
        return params;
      }
      const item = document_urgency.value.find(item => item.value == params);
      return item ? item.label : params;
    },
    formatType: (params) => {
      if (params === null || params === undefined || params === '') return 'info';
      if (!document_urgency.value || document_urgency.value.length === 0) {
        return 'info';
      }
      const item = document_urgency.value.find(item => item.value == params);
      const validTypes = ['success', 'warning', 'danger', 'info'];
      return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
    }
  },
  {
    label: '文档状态',
    prop: 'docStatus',
    width: '100',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      if (!document_status.value || document_status.value.length === 0) {
        return params;
      }
      const item = document_status.value.find(item => item.value == params);
      return item ? item.label : params;
    },
    formatType: (params) => {
      if (params === null || params === undefined || params === '') return 'info';
      if (!document_status.value || document_status.value.length === 0) {
        return 'info';
      }
      const item = document_status.value.find(item => item.value == params);
      const validTypes = ['success', 'warning', 'danger', 'info'];
      return item && validTypes.includes(item.elTagType) ? item.elTagType : 'info';
    }
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: '150',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openDocumentDia('edit', row)
        },
      },
      {
        name: "附件",
        type: "text",
        clickFun: (row) => {
          openAttachment(row)
        },
      },
    ],
  }
]);
// åˆ†ç±»è¡¨å•
const categoryForm = reactive({
  category: "",
  parentId: "",
  parentName: "",
});
const categoryRules = reactive({
  category: [{ required: true, message: "请输入分类名称", trigger: "blur" }],
});
// æ–‡æ¡£è¡¨å•
const documentForm = reactive({
  id: "",
  documentClassificationId: "",
  docName: "",
  docNumber: "",
  year: "",
  responsiblePerson: "",
  warehouseGoodsShelvesRowcolId: "",
  docData: "",
  retentionPeriod: "",
  securityLevel: "",
  copyCount: "",
  pageCount: "",
  docCategory: "",
  docType: "",
  urgencyLevel: "",
  docStatus: "",
  remark: "",
  attachments: [], // æ–°å¢žé™„件数组
});
const documentRules = reactive({
  docName: [{ required: true, message: "请输入文档名称", trigger: "blur" }],
  docNumber: [{ required: true, message: "请输入文档编号", trigger: "blur" }],
  year: [{ required: true, message: "请选择年度", trigger: "change" }],
  documentClassificationId: [{ required: true, message: "请选择文档分类", trigger: "change" }],
  warehouseGoodsShelvesRowcolId: [{ required: true, message: "请选择文档放置位置", trigger: "change" }],
});
// åˆ†é¡µç›¸å…³
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
// åˆå§‹åŒ–分类树数据
const initCategoryTree = async() => {
  try {
    treeLoad.value = true;
    const res = await getCategoryTree();
    if (res.code === 200) {
      categoryList.value = res.data || [];
      // è®¾ç½®å±•开的节点
      expandedKeys.value = [];
      categoryList.value.forEach((item) => {
        if (item.id) {
          expandedKeys.value.push(item.id);
        }
      });
    } else {
      ElMessage.error(res.msg || "获取分类树失败");
    }
  } catch (error) {
    ElMessage.error("获取分类树失败,请重试");
  } finally {
    treeLoad.value = false;
  }
};
// åˆå§‹åŒ–仓库位置数据
const initLocationTree = async() => {
  try {
    const res = await getWarehouseList();
    if (res.code === 200) {
      // è½¬æ¢æ•°æ®æ ¼å¼ï¼Œé€‚配el-tree-select组件
      locationTree.value = transformWarehouseData(res.data || []);
    } else {
      ElMessage.error(res.msg || "获取仓库位置失败");
    }
  } catch (error) {
    ElMessage.error("获取仓库位置失败,请重试");
  }
};
// è½¬æ¢ä»“库数据格式
const transformWarehouseData = (data) => {
  return data.map(item => ({
    id: item.id,
    label: item.name || item.warehouseName || item.label,
    value: item.id,
    children: item.children ? transformWarehouseData(item.children) : []
  }));
};
// æ ¹æ®ID获取位置名称
const getLocationName = (locationId) => {
  if (!locationId || !locationTree.value || locationTree.value.length === 0) {
    return locationId || '-';
  }
  const findLocation = (tree, id) => {
    for (let item of tree) {
      if (item.value === locationId || item.id === locationId) {
        return item.label;
      }
      if (item.children && item.children.length > 0) {
        const result = findLocation(item.children, id);
        if (result) return result;
      }
    }
    return null;
  };
  const locationName = findLocation(locationTree.value, locationId);
  return locationName || locationId;
};
// è¿‡æ»¤åˆ†ç±»æ ‘
const searchFilter = () => {
  if (proxy.$refs.tree) {
    proxy.$refs.tree.filter(search.value);
  }
};
// æ‰“开分类弹框
const openCategoryDia = (type, data) => {
  categoryOperationType.value = type;
  categoryDia.value = true;
  categoryForm.category = "";
  categoryForm.parentId ="";
  categoryForm.parentName = "";
  if (type === "edit") {
    categoryForm.category = data.category;
    // ä¿å­˜å½“前编辑的分类ID
    currentId.value = data.id;
  } else if (type === "addSub") {
    categoryForm.parentId = data.id;
    categoryForm.parentName = data.category;
  }
};
// æ‰“开文档弹框
const openDocumentDia = (type, data) => {
  documentOperationType.value = type;
  documentDia.value = true;
  if (type === "edit") {
    // ç¼–辑模式,加载现有数据
    Object.assign(documentForm, data);
    documentForm.retentionPeriod = String(documentForm.retentionPeriod)
    documentForm.securityLevel = String(documentForm.securityLevel)
    documentForm.docCategory = String(documentForm.docCategory)
    documentForm.docType = String(documentForm.docType)
    documentForm.urgencyLevel = String(documentForm.urgencyLevel)
    documentForm.docStatus = String(documentForm.docStatus)
    // åŠ è½½é™„ä»¶ä¿¡æ¯
    if (data.attachments) {
      documentForm.attachments = [...data.attachments];
    } else {
      documentForm.attachments = [];
    }
  } else {
    // æ–°å¢žæ¨¡å¼ï¼Œæ¸…空表单
    Object.keys(documentForm).forEach(key => {
      documentForm[key] = "";
    });
    documentForm.attachments = []; // æ–°å¢žæ¨¡å¼ä¸‹ä¹Ÿæ¸…空附件
    // è®¾ç½®é»˜è®¤å€¼ - ä½¿ç”¨å­—典数据的第一个选项作为默认值
    if (document_status.value && document_status.value.length > 0) {
      documentForm.docStatus = document_status.value[0].value;
    }
    if (document_urgency.value && document_urgency.value.length > 0) {
      documentForm.urgencyLevel = document_urgency.value[0].value;
    }
  }
};
// æäº¤åˆ†ç±»è¡¨å•
const submitCategoryForm = () => {
  proxy.$refs.categoryFormRef.validate(async (valid) => {
    if (valid) {
      try {
        if (categoryOperationType.value === "addSub") {
          // æ·»åŠ å­åˆ†ç±»
          const res = await addCategory({
            category: categoryForm.category,
            parentId: categoryForm.parentId
          });
          if (res.code === 200) {
            ElMessage.success("添加子分类成功");
            // é‡æ–°åŠ è½½åˆ†ç±»æ ‘
            await initCategoryTree();
          } else {
            ElMessage.error(res.msg || "添加子分类失败");
          }
        } else if (categoryOperationType.value === "edit") {
          // ç¼–辑分类
          const res = await updateCategory({
            id: currentId.value,
            category: categoryForm.category
          });
          if (res.code === 200) {
            ElMessage.success("编辑分类成功");
            // é‡æ–°åŠ è½½åˆ†ç±»æ ‘
            await initCategoryTree();
          } else {
            ElMessage.error(res.msg || "编辑分类失败");
          }
        } else {
          // æ–°å¢žé¡¶çº§åˆ†ç±»
          const res = await addCategory({
            category: categoryForm.category,
            parentId: null
          });
          if (res.code === 200) {
            ElMessage.success("新增分类成功");
            // é‡æ–°åŠ è½½åˆ†ç±»æ ‘
            await initCategoryTree();
          } else {
            ElMessage.error(res.msg || "新增分类失败");
          }
        }
        closeCategoryDia();
      } catch (error) {
        ElMessage.error("操作失败,请重试");
      }
    }
  });
};
// å…³é—­åˆ†ç±»å¼¹æ¡†
const closeCategoryDia = () => {
  proxy.$refs.categoryFormRef.resetFields();
  categoryForm.parentId = "";
  categoryForm.parentName = "";
  categoryDia.value = false;
};
// åˆ é™¤åˆ†ç±»
const removeCategory = (node, data) => {
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(async () => {
      try {
        const res = await deleteCategory([data.id]);
        if (res.code === 200) {
          ElMessage.success("删除成功");
          // é‡æ–°åŠ è½½åˆ†ç±»æ ‘
          await initCategoryTree();
        } else {
          ElMessage.error(res.msg || "删除失败");
        }
      } catch (error) {
        ElMessage.error("删除失败,请重试");
      }
    })
    .catch(() => {
      ElMessage("已取消");
    });
};
// é€‰æ‹©åˆ†ç±»
const handleNodeClick = (val, node, el) => {
  // åˆ¤æ–­æ˜¯å¦ä¸ºå¶å­èŠ‚ç‚¹
  isShowButton.value = true;
  // åªæœ‰å¶å­èŠ‚ç‚¹æ‰æ‰§è¡Œä»¥ä¸‹é€»è¾‘
  currentId.value = val.id;
  currentParentId.value = val.parentId;
  // æ¸…空选择状态
  selectedRows.value = [];
  selectAll.value = false;
  isIndeterminate.value = false;
  // é‡ç½®åˆ†é¡µ
  pagination.currentPage = 1;
  pagination.total = 0;
  // åŠ è½½æ–‡æ¡£åˆ—è¡¨
  if (isShowButton.value) {
    loadDocumentList();
  } else {
    // å¦‚果不是叶子节点,清空文档列表
    documentList.value = [];
  }
};
// æäº¤æ–‡æ¡£è¡¨å•
const submitDocumentForm = () => {
  proxy.$refs.documentFormRef.validate(async (valid) => {
    if (valid) {
      try {
        // æž„建提交数据
        const submitData = {
          ...documentForm,
          // è®¾ç½®å½“前选中的分类ID
          documentClassificationId: currentId.value || documentForm.documentClassificationId,
          // æ·»åŠ é™„ä»¶ä¿¡æ¯
          // attachments: documentForm.attachments
        };
        if (documentOperationType.value === "edit") {
          // ç¼–辑模式,更新现有数据
          const res = await updateDocument(submitData);
          if (res.code === 200) {
            ElMessage.success("编辑成功");
            // é‡æ–°åŠ è½½æ–‡æ¡£åˆ—è¡¨
            await loadDocumentList();
            // åˆ·æ–°é™„件列表
            if (attachmentManagerRef.value && documentForm.id) {
              attachmentManagerRef.value.loadAttachmentList(documentForm.id);
            }
          } else {
            ElMessage.error(res.msg || "编辑失败");
          }
        } else {
          // æ–°å¢žæ¨¡å¼ï¼Œæ·»åŠ æ–°æ•°æ®
          const res = await addDocument(submitData);
          if (res.code === 200) {
            ElMessage.success("新增成功");
            // é‡æ–°åŠ è½½æ–‡æ¡£åˆ—è¡¨
            await loadDocumentList();
            // åˆ·æ–°é™„件列表
            if (attachmentManagerRef.value && res.data && res.data.id) {
              attachmentManagerRef.value.loadAttachmentList(res.data.id);
            }
          } else {
            ElMessage.error(res.msg || "新增失败");
          }
        }
        closeDocumentDia();
      } catch (error) {
        ElMessage.error("操作失败,请重试");
      }
    }
  });
};
// å…³é—­æ–‡æ¡£å¼¹æ¡†
const closeDocumentDia = () => {
  proxy.$refs.documentFormRef.resetFields();
  documentDia.value = false;
  // æ¸…空表单数据
  Object.keys(documentForm).forEach(key => {
    documentForm[key] = "";
  });
  documentForm.attachments = []; // å…³é—­å¼¹æ¡†æ—¶ä¹Ÿæ¸…空附件
};
// å¤„理位置选择变化
const handleLocationChange = (value) => {
  if (value) {
    // æ£€æŸ¥é€‰æ‹©çš„æ˜¯å¦ä¸ºå¶å­èŠ‚ç‚¹
    const isLeafNode = checkIfLeafNode(locationTree.value, value);
    if (!isLeafNode) {
      ElMessage.warning("请选择最底层的位置(如:柜层)");
      documentForm.warehouseGoodsShelvesRowcolId = "";
      return;
    }
  }
};
// æ£€æŸ¥æ˜¯å¦ä¸ºå¶å­èŠ‚ç‚¹
const checkIfLeafNode = (tree, value) => {
  for (let item of tree) {
    if (item.value === value || item.id === value) {
      // å¦‚果没有子节点,则为叶子节点
      return !item.children || item.children.length === 0;
    }
    if (item.children && item.children.length > 0) {
      const result = checkIfLeafNode(item.children, value);
      if (result !== null) {
        return result;
      }
    }
  }
  return null;
};
// åˆ é™¤æ–‡æ¡£
const handleDelete = () => {
  if (selectedRows.value.length > 0) {
    ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} æ¡è®°å½•吗?`, "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(async () => {
        try {
          const selectedIds = selectedRows.value.map(row => row.id);
          const res = await deleteDocument(selectedIds);
          if (res.code === 200) {
            ElMessage.success("删除成功");
            // é‡æ–°åŠ è½½æ–‡æ¡£åˆ—è¡¨
            await loadDocumentList();
          } else {
            ElMessage.error(res.msg || "删除失败");
          }
        } catch (error) {
          ElMessage.error("删除失败,请重试");
        }
      })
      .catch(() => {
        ElMessage("已取消");
      });
  } else {
    ElMessage.warning("请选择要删除的数据");
  }
};
// PIMTable é€‰æ‹©å˜åŒ–事件
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
  // æ›´æ–°å…¨é€‰çŠ¶æ€
  const selectedCount = selection.length;
  const totalCount = documentList.value.length;
  if (selectedCount === 0) {
    selectAll.value = false;
    isIndeterminate.value = false;
  } else if (selectedCount === totalCount) {
    selectAll.value = true;
    isIndeterminate.value = false;
  } else {
    selectAll.value = false;
    isIndeterminate.value = true;
  }
};
// åŠ è½½æ–‡æ¡£åˆ—è¡¨
const loadDocumentList = async () => {
  try {
    tableLoading.value = true;
    // æž„建查询参数
    const query = {
      page: pagination.currentPage,
      size: pagination.pageSize,
      documentClassificationId:currentId.value
    };
    const res = await getDocumentList(query);
    if (res.code === 200) {
      documentList.value = res.data.records || [];
      pagination.total = res.data.total || 0;
    } else {
      ElMessage.error(res.msg || "获取文档列表失败");
      documentList.value = [];
      pagination.total = 0;
    }
    // é‡ç½®é€‰æ‹©çŠ¶æ€
    selectedRows.value = [];
    selectAll.value = false;
    isIndeterminate.value = false;
  } catch (error) {
    ElMessage.error("获取文档列表失败,请重试");
    documentList.value = [];
    pagination.total = 0;
  } finally {
    tableLoading.value = false;
  }
};
// å¤„理分页变化
const handlePagination = (current, size) => {
  pagination.currentPage = current;
  pagination.pageSize = size;
  loadDocumentList();
};
// è°ƒç”¨tree过滤方法
const filterNode = (value, data, node) => {
  if (!value) {
    return true;
  }
  let val = value.toLowerCase();
  return chooseNode(val, data, node);
};
// è¿‡æ»¤çˆ¶èŠ‚ç‚¹ / å­èŠ‚ç‚¹
const chooseNode = (value, data, node) => {
  if (data.category && data.category.toLowerCase().indexOf(value) !== -1) {
    return true;
  }
  const level = node.level;
  if (level === 1) {
    return false;
  }
  let parentData = node.parent;
  let index = 0;
  while (index < level - 1) {
    if (parentData.data.category && parentData.data.category.toLowerCase().indexOf(value) !== -1) {
      return true;
    }
    parentData = parentData.parent;
    index++;
  }
  return false;
};
// æ‰“开附件
const openAttachment = (row) => {
  attachmentManagerRef.value.open([], row.id);
};
onMounted(() => {
  initCategoryTree();
  initLocationTree();
  // ä¸åœ¨åˆå§‹åŒ–时加载文档列表,等待用户选择分类后再加载
});
</script>
<style scoped>
.document-view {
  display: flex;
  height: 100%;
}
.left {
  width: 380px;
  padding: 16px;
  background: #ffffff;
  border-right: 1px solid #e4e7ed;
}
.right {
  width: calc(100% - 380px);
  padding: 16px;
  background: #ffffff;
}
.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
.tree-node-content {
  display: flex;
  align-items: center;
  height: 100%;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px;
}
.table-container {
  background: #ffffff;
  border-radius: 8px;
  overflow: hidden;
  position: relative;
}
.add-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background-color: #f5f7fa;
  cursor: pointer;
  transition: background-color 0.2s ease;
  padding: 12px 16px;
  margin-bottom: 16px;
  border-radius: 6px;
  border: 1px dashed #d9d9d9;
}
.add-row:hover {
  background-color: #e4e7ed;
  border-color: #c0c4cc;
}
.add-icon {
  color: #909399;
  font-size: 16px;
}
.add-row span {
  color: #606266;
  font-size: 14px;
}
.empty-data {
  text-align: center;
  color: #909399;
  padding: 40px;
  font-size: 14px;
}
.dialog-footer {
  text-align: right;
}
.operation-column {
  position: absolute;
  right: 0;
  top: 0;
  width: 120px;
  background: #ffffff;
  border-left: 1px solid #e4e7ed;
  z-index: 1;
  box-shadow: -2px 0 4px rgba(0, 0, 0, 0.1);
}
.operation-header {
  height: 40px;
  line-height: 40px;
  text-align: center;
  background: #fafafa;
  border-bottom: 1px solid #e4e7ed;
  font-weight: 500;
  color: #606266;
}
.operation-cell {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid #e4e7ed;
}
.operation-cell:last-child {
  border-bottom: none;
}
.attachment-section {
  width: 100%;
}
.attachment-list {
  margin-bottom: 10px;
}
.attachment-item {
  display: flex;
  align-items: center;
  padding: 8px 12px;
  background-color: #f5f7fa;
  border-radius: 4px;
  margin-bottom: 8px;
}
.file-icon {
  margin-right: 8px;
  color: #409eff;
}
.file-name {
  flex: 1;
  color: #606266;
  font-size: 14px;
}
</style>
src/views/fileManagement/return/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,595 @@
<template>
  <div class="app-container return-view">
    <!-- æŸ¥è¯¢åŒºåŸŸ -->
    <div class="search-container">
      <el-form :model="searchForm" :inline="true" class="search-form">
        <!-- <el-form-item label="借阅状态:">
          <el-select v-model="searchForm.borrowStatus" placeholder="请选择借阅状态" clearable style="width: 150px">
            <el-option label="借阅" value="借阅" />
            <el-option label="归还" value="归还" />
          </el-select>
        </el-form-item> -->
        <el-form-item label="借阅人:">
          <el-input
            v-model="searchForm.borrower"
            placeholder="请输入借阅人"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="归还人:">
          <el-input
            v-model="searchForm.returner"
            placeholder="请输入归还人"
            clearable
            style="width: 200px"
          />
        </el-form-item>
        <el-form-item label="归还日期范围:">
          <el-date-picker
            v-model="searchForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            style="width: 300px"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">
            <el-icon><Search /></el-icon>
            æŸ¥è¯¢
          </el-button>
          <el-button @click="handleReset">
            <el-icon><Refresh /></el-icon>
            é‡ç½®
          </el-button>
        </el-form-item>
        <el-form-item style="margin-left: auto;">
          <el-button type="primary" @click="openReturnDia('add')">
            <el-icon><Plus /></el-icon>
            æ–°å¢žå½’还
          </el-button>
          <el-button
            type="danger"
            @click="handleBatchDelete"
            :disabled="selectedRows.length === 0"
          >
            <el-icon><Delete /></el-icon>
            æ‰¹é‡åˆ é™¤ ({{ selectedRows.length }})
          </el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- è¡¨æ ¼åŒºåŸŸ -->
    <div class="table-container">
      <PIMTable
        :table-data="returnList"
        :column="tableColumns"
        :is-selection="true"
        :border="true"
        :table-loading="tableLoading"
        :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
          layout: 'total, sizes, prev, pager, next, jumper'
        }"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
      />
    </div>
    <!-- å½’还新增/编辑对话框 -->
    <el-dialog
      v-model="returnDia"
      :title="returnOperationType === 'add' ? '新增归还' : '编辑归还'"
      width="800px"
      @close="closeReturnDia"
      @keydown.enter.prevent
    >
      <el-form
        :model="returnForm"
        label-width="140px"
        :rules="returnRules"
        ref="returnFormRef"
      >
                 <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="文档:" prop="borrowId">
               <el-select v-model="returnForm.borrowId" placeholder="请选择文档" style="width: 100%" @change="handleDocumentChange">
                 <el-option
                   v-for="item in documentList"
                   :key="item.id"
                   :label="item.docName || item.name"
                   :value="item.id"
                 />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="借阅人:" prop="borrower">
               <el-input v-model="returnForm.borrower" placeholder="借阅人将根据文档选择自动带出" disabled />
             </el-form-item>
           </el-col>
         </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="归还人:" prop="returner">
              <el-input v-model="returnForm.returner" placeholder="请输入归还人" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="归还日期:" prop="returnDate">
              <el-date-picker
                v-model="returnForm.returnDate"
                type="date"
                placeholder="选择归还日期"
                style="width: 100%"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
          </el-col>
        </el-row>
                 <el-row :gutter="20">
           <el-col :span="24">
             <el-form-item label="应归还日期:" prop="dueReturnDate">
               <el-date-picker
                 v-model="returnForm.dueReturnDate"
                 type="date"
                 placeholder="应归还日期将根据文档选择自动带出"
                 style="width: 100%"
                 format="YYYY-MM-DD"
                 value-format="YYYY-MM-DD"
                 disabled
               />
             </el-form-item>
           </el-col>
         </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注说明:" prop="remark">
              <el-input
                v-model="returnForm.remark"
                type="textarea"
                :rows="3"
                placeholder="请输入备注说明"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitReturnForm">确认</el-button>
          <el-button @click="closeReturnDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { Search, Refresh, Plus, Delete } from '@element-plus/icons-vue';
import PIMTable from '@/components/PIMTable/PIMTable.vue';
import { getReturnListPage, returnDocument, deleteReturn, getDocumentList, updateBorrow, reventUpdate } from '@/api/fileManagement/return';
const { proxy } = getCurrentInstance();
// å“åº”式数据
const returnDia = ref(false);
const returnOperationType = ref("");
const tableLoading = ref(false);
const returnList = ref([]);
const selectedRows = ref([]);
const documentList = ref([]); // æ–‡æ¡£åˆ—表
// åˆ†é¡µç›¸å…³
const pagination = reactive({
  currentPage: 1,
  pageSize: 10,
  total: 0,
});
// æŸ¥è¯¢è¡¨å•
const searchForm = reactive({
  borrowStatus: "",
  borrower: "",
  returner: "",
  dateRange: []
});
// å½’还表单
const returnForm = reactive({
  id: "",
  borrowId: "",
  borrower: "",
  returner: "",
  borrowStatus: "",
  returnDate: "",
  dueReturnDate: "",
  remark: ""
});
// è¡¨å•验证规则
const returnRules = reactive({
  borrowId: [{ required: true, message: "请选择文档", trigger: "change" }],
  returner: [{ required: true, message: "请输入归还人", trigger: "blur" }],
  returnDate: [{ required: true, message: "请选择归还日期", trigger: "change" }]
});
// è¡¨æ ¼åˆ—配置
const tableColumns = ref([
  {
    label: '文档名称',
    prop: 'docName',
    width: '200',
  },
  { label: '借阅人', prop: 'borrower' },
  { label: '归还人', prop: 'returner' },
  {
    label: '借阅状态',
    prop: 'borrowStatus',
    dataType: 'tag',
    formatData: (params) => {
      if (params === null || params === undefined || params === '') return '-';
      return params;
    },
    formatType: (params) => {
      if (params === '归还') return 'success';
      if (params === '借阅') return 'warning';
      return 'info';
    }
  },
  { label: '归还日期', prop: 'returnDate' },
  { label: '应归还日期', prop: 'dueReturnDate' },
  { label: '备注', prop: 'remark', width: '150' },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: '150',
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openReturnDia('edit', row)
        },
      },
      {
        name: "删除",
        type: "text",
        clickFun: (row) => {
          handleDelete(row)
        },
      },
    ],
  }
]);
// åˆå§‹åŒ–数据
const initData = async () => {
  await Promise.all([
    loadDocumentList(),
    loadReturnList()
  ]);
};
// åŠ è½½æ–‡æ¡£åˆ—è¡¨
const loadDocumentList = async () => {
  try {
    const res = await getDocumentList();
    if (res.code === 200) {
      documentList.value = res.data || [];
    } else {
      ElMessage.error(res.msg || "获取文档列表失败");
      documentList.value = [];
    }
  } catch (error) {
    ElMessage.error("获取文档列表失败,请重试");
    documentList.value = [];
  }
};
// åŠ è½½å½’è¿˜åˆ—è¡¨
const loadReturnList = async () => {
  try {
    tableLoading.value = true;
    // æž„建查询参数
    const query = {
      page: pagination.currentPage,
      size: pagination.pageSize,
      borrowStatus: searchForm.borrowStatus || undefined,
      borrower: searchForm.borrower || undefined,
      returner: searchForm.returner || undefined,
      entryDateStart: searchForm.dateRange && searchForm.dateRange.length > 0 ? searchForm.dateRange[0] : undefined,
      entryDateEnd: searchForm.dateRange && searchForm.dateRange.length > 1 ? searchForm.dateRange[1] : undefined
    };
    // ç§»é™¤undefined的参数
    Object.keys(query).forEach(key => {
      if (query[key] === undefined) {
        delete query[key];
      }
    });
    const res = await getReturnListPage(query);
    if (res.code === 200) {
      returnList.value = res.data.records || [];
      pagination.total = res.data.total || 0;
    } else {
      ElMessage.error(res.msg || "获取归还列表失败");
      returnList.value = [];
      pagination.total = 0;
    }
    // é‡ç½®é€‰æ‹©çŠ¶æ€
    selectedRows.value = [];
  } catch (error) {
    ElMessage.error("获取归还列表失败,请重试");
    returnList.value = [];
    pagination.total = 0;
  } finally {
    tableLoading.value = false;
  }
};
// æŸ¥è¯¢
const handleSearch = () => {
  pagination.currentPage = 1;
  loadReturnList();
};
// é‡ç½®æŸ¥è¯¢
const handleReset = () => {
  searchForm.borrowStatus = "";
  searchForm.borrower = "";
  searchForm.returner = "";
  searchForm.dateRange = [];
  pagination.currentPage = 1;
  loadReturnList();
  ElMessage.success("查询条件已重置");
};
// æ‰“开归还弹框
const openReturnDia = (type, data) => {
  returnOperationType.value = type;
  returnDia.value = true;
  if (type === "edit") {
    // ç¼–辑模式,加载现有数据
    Object.assign(returnForm, data);
    // ç¼–辑模式下,文档选择后自动填充借阅人和应归还日期
    if (returnForm.borrowId) {
      handleDocumentChange(returnForm.borrowId);
    }
  } else {
    // æ–°å¢žæ¨¡å¼ï¼Œæ¸…空表单
    Object.keys(returnForm).forEach(key => {
      returnForm[key] = "";
    });
    // è®¾ç½®é»˜è®¤çŠ¶æ€
    returnForm.borrowStatus = "归还";
    // è®¾ç½®å½“前日期为归还日期
    returnForm.returnDate = new Date().toISOString().split('T')[0];
  }
};
// å…³é—­å½’还弹框
const closeReturnDia = () => {
  proxy.$refs.returnFormRef.resetFields();
  returnDia.value = false;
};
// æäº¤å½’还表单
const submitReturnForm = () => {
  proxy.$refs.returnFormRef.validate(async (valid) => {
    if (valid) {
      try {
                 if (returnOperationType.value === "edit") {
           // ç¼–辑模式,调用归还更新接口
           const res = await reventUpdate({
             id: returnForm.id,
             documentationId: returnForm.documentationId,
             borrower: returnForm.borrower,
             returner: returnForm.returner,
             borrowStatus: returnForm.borrowStatus,
             returnDate: returnForm.returnDate,
             dueReturnDate: returnForm.dueReturnDate,
             remark: returnForm.remark
           });
          if (res.code === 200) {
            ElMessage.success("编辑成功");
            await loadReturnList();
            closeReturnDia();
          } else {
            ElMessage.error(res.msg || "编辑失败");
          }
        } else {
          // æ–°å¢žæ¨¡å¼ï¼Œè°ƒç”¨å½’还接口
          const res = await returnDocument({
            borrowId: returnForm.borrowId,
            borrower: returnForm.borrower,
            returner: returnForm.returner,
            borrowStatus: returnForm.borrowStatus,
            returnDate: returnForm.returnDate,
            dueReturnDate: returnForm.dueReturnDate,
            remark: returnForm.remark
          });
          if (res.code === 200) {
            ElMessage.success("新增成功");
            await loadReturnList();
            closeReturnDia();
          } else {
            ElMessage.error(res.msg || "新增失败");
          }
        }
      } catch (error) {
        ElMessage.error("操作失败,请重试");
      }
    }
  });
};
// åˆ é™¤å½’还记录
const handleDelete = (row) => {
  ElMessageBox.confirm(
    `确定要删除这条归还记录吗?`,
    "删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  ).then(async () => {
    try {
      const res = await deleteReturn([row.id]);
      if (res.code === 200) {
        ElMessage.success("删除成功");
        await loadReturnList();
      } else {
        ElMessage.error(res.msg || "删除失败");
      }
    } catch (error) {
      ElMessage.error("删除失败,请重试");
    }
  }).catch(() => {
    ElMessage.info("已取消删除");
  });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning("请选择要删除的记录");
    return;
  }
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.value.length} æ¡å½’还记录吗?`,
    "批量删除提示",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  ).then(async () => {
    try {
      const selectedIds = selectedRows.value.map(row => row.id);
      const res = await deleteReturn(selectedIds);
      if (res.code === 200) {
        ElMessage.success("批量删除成功");
        await loadReturnList();
      } else {
        ElMessage.error(res.msg || "批量删除失败");
      }
    } catch (error) {
      ElMessage.error("批量删除失败,请重试");
    }
  }).catch(() => {
    ElMessage.info("已取消删除");
  });
};
// é€‰æ‹©å˜åŒ–事件
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// å¤„理分页变化
const handlePagination = (current, size) => {
  pagination.currentPage = current;
  pagination.pageSize = size;
  loadReturnList();
};
// å¤„理文档选择变化
const handleDocumentChange = (documentId) => {
  if (documentId) {
    // æ ¹æ®é€‰æ‹©çš„æ–‡æ¡£ID,从文档列表中查找对应的文档信息
    const selectedDoc = documentList.value.find(doc => doc.id === documentId);
    if (selectedDoc) {
      // è‡ªåŠ¨å¡«å……å€Ÿé˜…äººå’Œåº”å½’è¿˜æ—¥æœŸ
      returnForm.borrower = selectedDoc.borrower || selectedDoc.borrowerName || '';
      returnForm.dueReturnDate = selectedDoc.dueReturnDate || selectedDoc.expectedReturnDate || '';
    }
  } else {
    // æ¸…空相关字段
    returnForm.borrower = '';
    returnForm.dueReturnDate = '';
  }
};
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  initData();
});
</script>
<style scoped>
.return-view {
  padding: 20px;
}
.search-container {
  background: #ffffff;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.search-form {
  margin: 0;
}
.table-container {
  background: #ffffff;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.empty-data {
  text-align: center;
  color: #909399;
  padding: 40px;
  font-size: 14px;
}
.dialog-footer {
  text-align: right;
}
:deep(.el-form-item__label) {
  font-weight: 500;
  color: #303133;
}
:deep(.el-input__wrapper) {
  box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__wrapper:hover) {
  box-shadow: 0 0 0 1px #c0c4cc inset;
}
:deep(.el-input__wrapper.is-focus) {
  box-shadow: 0 0 0 1px #409eff inset;
}
</style>
src/views/inventoryManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,309 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">发放季度:</span>
                <el-select
                    style="width: 200px;"
                    @change="handleQuery"
                    v-model="searchForm.season"
                    placeholder="请选择"
                    :clearable="false"
                >
                    <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="item.value" />
                </el-select>
                <span class="search_title ml10">员工名称:</span>
                <el-input
                    v-model="searchForm.staffName"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
                <el-button @click="handleOut" icon="download">导出</el-button>
                <el-button
                    type="danger"
                    icon="Delete"
                    :disabled="multipleList.length <= 0"
                    @click="deleteRow(multipleList.map((item) => item.id))"
                >
                    æ‰¹é‡åˆ é™¤
                </el-button>
            </div>
        </div>
        <div class="table_list">
            <el-table
                ref="tableRef"
                v-loading="tableLoading"
                :data="tableData"
                border
                height="calc(100vh - 21em)"
                :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
                style="width: 100%"
                @selection-change="handleSelectionChange"
            >
                <!-- é€‰æ‹©åˆ— -->
                <el-table-column
                    align="center"
                    type="selection"
                    width="55"
                    fixed="left"
                />
                <!-- åºå·åˆ— -->
                <el-table-column
                    align="center"
                    label="序号"
                    type="index"
                    width="60"
                    fixed="left"
                />
                <!-- å›ºå®šåˆ—:姓名 -->
                <el-table-column
                    label="姓名"
                    prop="staffName"
                    width="100"
                    show-overflow-tooltip
                    align="center"
                    fixed="left"
                />
                <!-- å›ºå®šåˆ—:工号 -->
                <el-table-column
                    label="工号"
                    prop="staffNo"
                    width="100"
                    show-overflow-tooltip
                    align="center"
                    fixed="left"
                />
                <!-- åŠ¨æ€åˆ—ï¼šæ ¹æ®å­—å…¸æ¸²æŸ“ -->
                <el-table-column
                    v-for="(dictItem, index) in sys_lavor_issue"
                    :key="dictItem.value"
                    :label="dictItem.label"
                    :prop="dictItem.value"
                    show-overflow-tooltip
                >
                </el-table-column>
                <!-- æ“ä½œåˆ— -->
                <el-table-column
                    label="操作"
                    width="150"
                    align="center"
                    fixed="right"
                >
                    <template #default="scope">
                        <el-button
                            type="primary"
                            link
                            size="small"
                            @click="edit(scope.row)"
                        >
                            ç¼–辑
                        </el-button>
                        <el-button
                            type="danger"
                            link
                            size="small"
                            :disabled="!!scope.row.adoptedDate"
                            @click="adopted(scope.row)"
                        >
                            é¢†ç”¨
                        </el-button>
                    </template>
                </el-table-column>
            </el-table>
            <pagination :total="total" layout="total, sizes, prev, pager, next, jumper"
                                    :page="page.current" :limit="page.size" @pagination="paginationChange" />
        </div>
        <Modal ref="modalRef" @success="handleQuery"></Modal>
        <files-dia ref="filesDia"></files-dia>
    </div>
</template>
<script setup>
import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue'
import dayjs from "dayjs";
import Modal from "./Modal.vue";
import FilesDia from "./filesDia.vue";
import Pagination from "@/components/Pagination/index.vue";
import {lavorIssueListPage, deleteLedger, update} from "@/api/lavorissce/ledger.js";
import {ElMessageBox, ElMessage} from "element-plus";
const { proxy } = getCurrentInstance();
import { getCurrentMonth } from "@/utils/util"
const page = ref({
    current: 1,
    size: 100,
})
const total = ref(0)
// å“åº”式数据
const tableRef = ref(null)
const tableData = ref([])
const tableLoading = ref(false)
const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue")
const data = reactive({
    searchForm: {
        season: "",
        staffName: "",
    },
});
const { searchForm } = toRefs(data);
const modalRef = ref();
const filesDia = ref();
const multipleList = ref([]);
const jidu = ref([
    {
        value: '1',
        label: '第一季度'
    },
    {
        value: '2',
        label: '第二季度'
    },
    {
        value: '3',
        label: '第三季度'
    },
    {
        value: '4',
        label: '第四季度'
    }
])
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.value.current = 1;
    getList();
};
// èŽ·å–å­—å…¸æ•°æ®
const getList = async () => {
    tableLoading.value = true;
    const params = { ...searchForm.value, ...page.value };
    lavorIssueListPage(params).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        total.value = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
}
const add = () => {
    modalRef.value.openModal();
};
const edit = (row) => {
    modalRef.value.loadForm(row);
};
const deleteRow = (id) => {
    ElMessageBox.confirm("此操作将永久删除该数据, æ˜¯å¦ç»§ç»­?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
    }).then(async () => {
        const { code } = await deleteLedger(id);
        if (code == 200) {
            ElMessage({
                type: "success",
                message: "删除成功",
            });
            await getList();
        }
    });
};
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download(`/lavorIssue/exportCopy`, {season: searchForm.value.season}, "劳保台账.xlsx");
        })
        .catch(() => {
            ElMessage.info("已取消");
        });
};
const adopted = (row) => {
    ElMessageBox.confirm("是否确认领用?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
    }).then(async () => {
        const params = {
            id: row.id,
            adoptedDate: dayjs().format("YYYY-MM-DD")
        }
        const { code } = await update(params);
        if (code == 200) {
            ElMessage({
                type: "success",
                message: "领用成功",
            });
            await getList();
        }
    })
}
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
    nextTick(() => {
        filesDia.value?.openDialog( row,'收入')
    })
};
// äº‹ä»¶å¤„理函数
const handleSelectionChange = (selection) => {
    multipleList.value = selection;
}
const paginationChange = (pagination) => {
    page.value.current = pagination.page;
    page.value.size = pagination.limit;
    getList();
}
// ç»„件挂载时加载字典数据
onMounted(() => {
    handleQuery()
})
</script>
<style scoped>
.dynamic-table-container {
    width: 100%;
}
.pagination-container {
    margin-top: 20px;
    display: flex;
    justify-content: flex-end;
}
:deep(.el-table .el-table__header-wrapper th) {
    background-color: #F0F1F5 !important;
    color: #333333;
    font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
    padding: 8px 0;
}
:deep(.el-select) {
    width: 100%;
}
:deep(.el-input) {
    width: 100%;
}
</style>
src/views/lavorissue/ledger/index.vue
@@ -32,7 +32,7 @@
        <div></div>
        <div>
          <el-button type="primary" @click="add" icon="Plus"> æ–°å¢ž </el-button>
          <el-button @click="handleOut" icon="download">导出</el-button>
<!--          <el-button @click="handleOut" icon="download">导出</el-button>-->
          <el-button
              type="danger"
              icon="Delete"
src/views/lavorissue/statistics/index.vue
@@ -1,73 +1,142 @@
<template>
  <div class="app-container">
    <el-form :model="filters" :inline="true">
      <el-form-item label="发放季度:" prop="season">
    <div class="search_form">
      <div>
        <span class="search_title">发放季度:</span>
        <el-select
            :disabled="filters.issueDate ? true : false"
            style="width: 200px;"
            @change="getList"
            v-model="filters.season"
            @change="handleQuery"
            v-model="searchForm.season"
            placeholder="请选择"
            @clear="clearSeason"
            clearable
            :disabled="searchForm.issueDate ? true : false"
        >
          <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="value" />
          <el-option :label="item.label" :value="item.value" v-for="(item,index) in jidu" :key="item.value" />
        </el-select>
      </el-form-item>
      <el-form-item label="发放月份:" prop="issueDate">
        <span class="search_title ml10">发放月份:</span>
        <el-date-picker
            :disabled="filters.season ? true : false"
            v-model="filters.issueDate"
            @change="getList"
            style="width: 200px;"
            :disabled="searchForm.season ? true : false"
            v-model="searchForm.issueDate"
            @change="handleQuery"
            @clear="clearIssueDaten"
            type="month"
            value-format="YYYY-MM-DD"
            format="YYYY-MM"
            placeholder="请选择月份"
            clearable
            style="width: 100%"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getList">搜索</el-button>
        <el-button @click="resetParams">重置</el-button>
      </el-form-item>
    </el-form>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
        <el-button type="primary" @click="resetHandleQuery" style="margin-left: 10px"
        >重置</el-button
        >
      </div>
      <div>
        <el-button @click="handleOut" icon="download">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <div class="actions">
        <div class="head" @click="getList(1)">已领取劳保数量:{{statisticsObj.ylqNum}}</div>
        <div class="head" @click="getList(2)">未领取劳保数量: {{ statisticsObj.wlqNum }}</div>
        <div class="head" @click="getList(3)">超时已领取劳保数量: {{statisticsObj.csylqNum}}</div>
        <div class="head" @click="getList(4)">超时未领取劳保数量: {{statisticsObj.cswlqNum}}</div>
        <div class="head" @click="handleQuery(1)">已领取劳保数量:{{statisticsObj.ylqNum}}</div>
        <div class="head" @click="handleQuery(2)">未领取劳保数量: {{ statisticsObj.wlqNum }}</div>
        <div class="head" @click="handleQuery(3)">超时已领取劳保数量: {{statisticsObj.csylqNum}}</div>
        <div class="head" @click="handleQuery(4)">超时未领取劳保数量: {{statisticsObj.cswlqNum}}</div>
      </div>
      <PIMTable
          rowKey="id"
          isSelection
          :column="columns"
          :tableData="dataList"
          :page="{
          current: pagination.currentPage,
          size: pagination.pageSize,
          total: pagination.total,
        }"
      <el-table
          ref="tableRef"
          v-loading="tableLoading"
          :data="tableData"
          border
          height="calc(100vh - 21em)"
          :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
          style="width: 100%"
          @selection-change="handleSelectionChange"
          @pagination="changePage"
      >
      </PIMTable>
        <!-- é€‰æ‹©åˆ— -->
        <el-table-column
            align="center"
            type="selection"
            width="55"
            fixed="left"
        />
        <!-- åºå·åˆ— -->
        <el-table-column
            align="center"
            label="序号"
            type="index"
            width="60"
            fixed="left"
        />
        <!-- å›ºå®šåˆ—:姓名 -->
        <el-table-column
            label="姓名"
            prop="staffName"
            width="100"
            show-overflow-tooltip
            align="center"
            fixed="left"
        />
        <!-- å›ºå®šåˆ—:工号 -->
        <el-table-column
            label="工号"
            prop="staffNo"
            width="100"
            show-overflow-tooltip
            align="center"
            fixed="left"
        />
        <!-- åŠ¨æ€åˆ—ï¼šæ ¹æ®å­—å…¸æ¸²æŸ“ -->
        <el-table-column
            v-for="(dictItem, index) in sys_lavor_issue"
            :key="dictItem.value"
            :label="dictItem.label"
            :prop="dictItem.value"
            show-overflow-tooltip
        >
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script setup>
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { listPage,statistics } from "@/api/lavorissce/ledger";
import { onMounted, getCurrentInstance } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { ref, onMounted, reactive, toRefs, nextTick, getCurrentInstance } from 'vue'
import dayjs from "dayjs";
// è¡¨æ ¼å¤šé€‰æ¡†é€‰ä¸­é¡¹
const multipleList = ref([]);
import {statisticsList, statistics} from "@/api/lavorissce/ledger.js";
import {ElMessageBox, ElMessage} from "element-plus";
const { proxy } = getCurrentInstance();
import { getCurrentMonth } from "@/utils/util"
const page = ref({
  current: 1,
  size: 100,
})
const total = ref(0)
// å“åº”式数据
const tableRef = ref(null)
const tableData = ref([])
const tableLoading = ref(false)
const { sys_lavor_issue } = proxy.useDict("sys_lavor_issue")
const data = reactive({
  searchForm: {
    season: getCurrentMonth(),
    issueDate: "",
    status: 0
  },
});
const { searchForm } = toRefs(data);
const modalRef = ref();
const filesDia = ref()
const filesDia = ref();
const multipleList = ref([]);
const jidu = ref([
  {
    value: '1',
@@ -86,148 +155,126 @@
    label: '第四季度'
  }
])
const clearSeason = () => {
  console.log("req")
  searchForm.value.season = ""
  searchForm.value.issueDate = dayjs().format("YYYY-MM-DD");
}
const {
  filters,
  columns,
  dataList,
  pagination,
  getTableData,
  resetFilters,
  onCurrentChange,
} = usePaginationApi(
    listPage,
    {
      season: '',
      issueDate: '',
      status: 0
    },
    [
      {
        label: "劳保单号",
        align: "center",
        prop: "orderNo",
      },
      {
        label: "员工名称",
        align: "center",
        prop: "staffName",
      },
      {
        label: "员工编号",
        align: "center",
        prop: "staffNo"
      },
      {
        label: "劳保类型",
        align: "center",
        prop: "dictTypeName",
      },
      {
        label: "劳保防具",
        align: "center",
        prop: "dictName",
      },
      {
        label: "发放数量",
        align: "center",
        prop: "num",
      },
      {
        label: "进厂日期",
        align: "center",
        prop: "factoryDate",
      },
      {
        label: "发放日期",
        align: "center",
        prop: "issueDate",
      },
      {
        label: "领用日期",
        align: "center",
        prop: "adoptedDate",
      }
    ]
);
const clearIssueDaten = () => {
  searchForm.value.issueDate = ""
  searchForm.value.season = getCurrentMonth()
}
const statisticsObj = ref({
  ylqNum: 0,  // å·²é¢†å–数量
  wlqNum: 0,  // æœªé¢†å–数量
  csylqNum: 0,  // è¶…时已领取数量
  cswlqNum: 0  // è¶…时未领取数量
})
const resetParams = () => {
  resetFilters();
  getStatistics();
};
// å¤šé€‰åŽåšä»€ä¹ˆ
const handleSelectionChange = (selectionList) => {
  multipleList.value = selectionList;
};
const getList = (status) => {
  switch (status){
    case 1:
      filters.status = 1
      break;
    case 2:
      filters.status = 2
      break;
    case 3:
      filters.status = 3
      break;
    case 4:
      filters.status = 4
      break;
    default:
      filters.status = 0
  }
  console.log(filters)
  getTableData();
  getStatistics();
const resetHandleQuery = () => {
  searchForm.value.issueDate = "";
  searchForm.value.season = getCurrentMonth();
  handleQuery(0)
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getTableData();
const handleQuery = (status) => {
  switch (status){
    case 1:
      searchForm.value.status = 1
      break;
    case 2:
      searchForm.value.status = 2
      break;
    case 3:
      searchForm.value.status = 3
      break;
    case 4:
      searchForm.value.status = 4
      break;
    default:
      searchForm.value.status = 0
  }
  getList();
  getStatistics();
};
const changePage = ({ page, limit }) => {
  pagination.currentPage = page;
  pagination.pageSize = limit;
  onCurrentChange(page);
};
const getStatistics = () => {
  statistics(filters).then(res => {
  statistics(searchForm.value).then(res => {
    statisticsObj.value.cswlqNum = res.data.cswlqNum
    statisticsObj.value.csylqNum = res.data.csylqNum
    statisticsObj.value.ylqNum = res.data.ylqNum
    statisticsObj.value.wlqNum = res.data.wlqNum
  })
}
// èŽ·å–å­—å…¸æ•°æ®
const getList = async () => {
  tableLoading.value = true;
  const params = { ...searchForm.value};
  statisticsList(params).then(res => {
    tableLoading.value = false;
    tableData.value = res.data;
  }).catch(err => {
    tableLoading.value = false;
  })
}
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download(`/lavorIssue/exportCopy`, {season: searchForm.value.season,issueDate: searchForm.value.issueDate}, "劳保台账.xlsx");
      })
      .catch(() => {
        ElMessage.info("已取消");
      });
};
// äº‹ä»¶å¤„理函数
const handleSelectionChange = (selection) => {
  multipleList.value = selection;
}
// ç»„件挂载时加载字典数据
onMounted(() => {
  getList()
});
  handleQuery()
})
</script>
<style lang="scss" scoped>
.table_list {
  margin-top: unset;
<style scoped>
.dynamic-table-container {
  width: 100%;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
:deep(.el-table .el-table__header-wrapper th) {
  background-color: #F0F1F5 !important;
  color: #333333;
  font-weight: 600;
}
:deep(.el-table .el-table__body-wrapper td) {
  padding: 8px 0;
}
:deep(.el-select) {
  width: 100%;
}
:deep(.el-input) {
  width: 100%;
}
.actions {
  display: flex;
  justify-content: space-around;
  align-items: center;
  //margin-top: 20px;
  margin-bottom: 30px;
}
.head{
@@ -236,4 +283,3 @@
  font-weight: 600;
}
</style>
vite.config.js
@@ -8,8 +8,8 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
    VITE_APP_ENV == "development"
      ? "http://114.132.189.42:8092" // å¼€å‘环境后端接口
      : "http://114.132.189.42:8092"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
      ? "http://192.168.1.138:7788" // å¼€å‘环境后端接口
      : "http://114.132.189.42:7003"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
  return {
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。
@@ -45,7 +45,7 @@
    },
    // vite ç›¸å…³é…ç½®
    server: {
      port: 80,
      port: 8001,
      host: true,
      open: true,
      proxy: {