spring
4 天以前 d9ed7cc8ec13a59b69bd643a9c3e2ccd907fabd1
档案管理
已修改1个文件
已添加10个文件
4068 ■■■■■ 文件已修改
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/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 599 ●●●●● 补丁 | 查看 | 原始文档 | 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/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 = !(val.children && val.children.length > 0);
  // åªæœ‰å¶å­èŠ‚ç‚¹æ‰æ‰§è¡Œä»¥ä¸‹é€»è¾‘
  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,599 @@
<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 v-if="returnList.length === 0" class="empty-data">
        æš‚无数据
      </div>
    </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>
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: {