Browse Source

- 文档新增功能完成

Huanyi 1 week ago
parent
commit
e6df6fa731

+ 27 - 0
src/api/document/document/index.ts

@@ -0,0 +1,27 @@
+import request from '@/utils/request';
+import { DocumentForm, DocumentQuery, DocumentVO } from './types';
+import { AxiosPromise } from 'axios';
+
+/**
+ * 查询文档列表
+ * @param query
+ */
+export const listDocument = (query: DocumentQuery): AxiosPromise<any> => {
+  return request({
+    url: '/document/document/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 新增文档
+ * @param data
+ */
+export const addDocument = (data: DocumentForm) => {
+  return request({
+    url: '/document/document',
+    method: 'post',
+    data: data
+  });
+};

+ 150 - 0
src/api/document/document/types.ts

@@ -0,0 +1,150 @@
+export interface DocumentVO {
+  /**
+   * 序号
+   */
+  id: string | number;
+
+  /**
+   * 所属文件夹
+   */
+  folderId: string | number;
+
+  /**
+   * 文档标识
+   */
+  specification?: string;
+
+  /**
+   * 计划文件名称/文件名称
+   */
+  name: string;
+
+  /**
+   * 文件名
+   */
+  fileName?: string;
+
+  /**
+   * 计划文件类型
+   */
+  planDocumentType?: string;
+
+  /**
+   * 状态
+   */
+  status?: number;
+
+  /**
+   * 递交截止时间
+   */
+  submitDeadline?: string;
+
+  /**
+   * 文档本体URL
+   */
+  url?: string;
+
+  /**
+   * 文件本体OSS ID
+   */
+  ossId?: string | number;
+
+  /**
+   * 递交时间
+   */
+  submitTime?: string;
+
+  /**
+   * 备注
+   */
+  note?: string;
+
+  /**
+   * 创建时间
+   */
+  createTime?: string;
+
+  /**
+   * 更新时间
+   */
+  updateTime?: string;
+
+  /**
+   * 计划递交人
+   */
+  submitter?: string;
+
+  /**
+   * 递交人ID
+   */
+  submitterId?: string | number;
+
+  /**
+   * 文档类型:0-非计划文档,1-计划文档
+   */
+  type?: number;
+}
+
+export interface DocumentForm extends BaseEntity {
+  /**
+   * 序号
+   */
+  id?: string | number;
+
+  /**
+   * 名称/计划名称
+   */
+  name?: string;
+
+  /**
+   * 文档类型:0-非计划文档,1-计划文档
+   */
+  type?: number;
+
+  /**
+   * 递交人ID
+   */
+  submitterId?: string | number;
+
+  /**
+   * 所属文件夹ID
+   */
+  folderId?: string | number;
+
+  /**
+   * 递交截止日期
+   */
+  submitDeadline?: string;
+
+  /**
+   * 计划文件类型
+   */
+  planType?: string;
+
+  /**
+   * 文件本体OSS ID
+   */
+  ossId?: string | number;
+
+  /**
+   * 递交时间
+   */
+  submitTime?: string;
+
+  /**
+   * 备注
+   */
+  note?: string;
+}
+
+export interface DocumentQuery extends PageQuery {
+  /**
+   * 文件名(模糊检索)
+   */
+  name?: string;
+
+  /**
+   * 文件夹ID
+   */
+  folderId?: string | number;
+}

+ 57 - 5
src/lang/modules/document/document/en_US.ts

@@ -9,7 +9,10 @@ export default {
   button: {
     newFolder: 'New Folder',
     submit: 'Submit',
-    cancel: 'Cancel'
+    cancel: 'Cancel',
+    search: 'Search',
+    reset: 'Reset',
+    audit: 'Audit'
   },
   // Menu
   menu: {
@@ -18,7 +21,8 @@ export default {
     delete: 'Delete',
     country: 'Country',
     center: 'Center',
-    folder: 'Folder'
+    folder: 'Folder',
+    document: 'Document'
   },
   // Dialog Titles
   dialog: {
@@ -27,7 +31,8 @@ export default {
     editFolder: 'Edit Folder',
     addCountry: 'Add Country',
     addCenter: 'Add Center',
-    confirmEdit: 'Confirm Edit'
+    confirmEdit: 'Confirm Edit',
+    addDocument: 'Add Document'
   },
   // Form
   form: {
@@ -70,15 +75,62 @@ export default {
     deleteConfirm: 'Confirm to delete "{name}"?',
     deleteTitle: 'Confirm',
     confirmButton: 'Confirm',
-    cancelButton: 'Cancel'
+    cancelButton: 'Cancel',
+    addDocumentSuccess: 'Document added successfully',
+    addDocumentFailed: 'Failed to add document',
+    searchSubmitterFailed: 'Failed to search submitter',
+    getDocumentListFailed: 'Failed to get document list'
   },
   // Validation Rules
   rule: {
     nameRequired: 'Please enter name',
     typeRequired: 'Please select type'
   },
+  // Document Form
+  documentForm: {
+    name: 'Name',
+    planName: 'Plan Name',
+    namePlaceholder: 'Please enter name',
+    type: 'Document Type',
+    normalDocument: 'Normal Document',
+    planDocument: 'Plan Document',
+    submitter: 'Submitter',
+    submitterPlaceholder: 'Search member by nickname',
+    submitDeadline: 'Submit Deadline',
+    submitDeadlinePlaceholder: 'Please select submit deadline',
+    planType: 'Plan Document Type',
+    planTypePlaceholder: 'Please select plan document type',
+    file: 'File',
+    submitTime: 'Submit Time',
+    note: 'Note',
+    notePlaceholder: 'Please enter note'
+  },
+  // Document Validation Rules
+  documentRule: {
+    nameRequired: 'Please enter name',
+    submitterRequired: 'Please select submitter',
+    fileRequired: 'Please upload file'
+  },
   // Empty State
   empty: {
-    description: 'Document content display area'
+    description: 'Please select a folder to view documents'
+  },
+  // Document List
+  documentList: {
+    fileName: 'File Name',
+    fileNamePlaceholder: 'Please enter file name',
+    index: 'No.',
+    name: 'Plan/File Name',
+    specification: 'Specification',
+    planDocumentType: 'Plan Document Type',
+    submitter: 'Submitter',
+    submitDeadline: 'Submit Deadline',
+    submitTime: 'Submit Time',
+    url: 'Actual Document',
+    download: 'Download',
+    note: 'Note',
+    createTime: 'Create Time',
+    updateTime: 'Update Time',
+    action: 'Action'
   }
 };

+ 57 - 5
src/lang/modules/document/document/zh_CN.ts

@@ -9,7 +9,10 @@ export default {
   button: {
     newFolder: '新建文件夹',
     submit: '确 定',
-    cancel: '取 消'
+    cancel: '取 消',
+    search: '搜 索',
+    reset: '重 置',
+    audit: '审核'
   },
   // 菜单
   menu: {
@@ -18,7 +21,8 @@ export default {
     delete: '删除',
     country: '国家',
     center: '中心',
-    folder: '文件夹'
+    folder: '文件夹',
+    document: '文档'
   },
   // 对话框标题
   dialog: {
@@ -27,7 +31,8 @@ export default {
     editFolder: '修改文件夹',
     addCountry: '新增国家',
     addCenter: '新增中心',
-    confirmEdit: '确认修改信息'
+    confirmEdit: '确认修改信息',
+    addDocument: '添加文档'
   },
   // 表单
   form: {
@@ -70,15 +75,62 @@ export default {
     deleteConfirm: '确认删除 "{name}" 吗?',
     deleteTitle: '提示',
     confirmButton: '确定',
-    cancelButton: '取消'
+    cancelButton: '取消',
+    addDocumentSuccess: '添加文档成功',
+    addDocumentFailed: '添加文档失败',
+    searchSubmitterFailed: '搜索递交人失败',
+    getDocumentListFailed: '获取文档列表失败'
   },
   // 验证规则
   rule: {
     nameRequired: '请输入名称',
     typeRequired: '请选择类型'
   },
+  // 文档表单
+  documentForm: {
+    name: '名称',
+    planName: '计划名称',
+    namePlaceholder: '请输入名称',
+    type: '文档类型',
+    normalDocument: '非计划文档',
+    planDocument: '计划文档',
+    submitter: '递交人',
+    submitterPlaceholder: '请输入成员昵称搜索',
+    submitDeadline: '递交截止日期',
+    submitDeadlinePlaceholder: '请选择递交截止日期',
+    planType: '计划文件类型',
+    planTypePlaceholder: '请选择计划文件类型',
+    file: '文件本体',
+    submitTime: '递交时间',
+    note: '备注',
+    notePlaceholder: '请输入备注'
+  },
+  // 文档验证规则
+  documentRule: {
+    nameRequired: '请输入名称',
+    submitterRequired: '请选择递交人',
+    fileRequired: '请上传文件'
+  },
   // 空状态
   empty: {
-    description: '文档内容展示区域'
+    description: '请选择文件夹查看文档列表'
+  },
+  // 文档列表
+  documentList: {
+    fileName: '文件名',
+    fileNamePlaceholder: '请输入文件名',
+    index: '序号',
+    name: '计划文件名称/文件名称',
+    specification: '文档标识',
+    planDocumentType: '计划文件类型',
+    submitter: '计划递交人',
+    submitDeadline: '递交截止日期',
+    submitTime: '递交时间',
+    url: '实际文档',
+    download: '下载',
+    note: '备注',
+    createTime: '创建时间',
+    updateTime: '更新时间',
+    action: '操作'
   }
 };

+ 530 - 7
src/views/document/folder/document.vue

@@ -30,7 +30,7 @@
                     <OfficeBuilding v-else-if="data.type === 2" />
                     <Document v-else />
                   </el-icon>
-                  <span class="node-label">{{ node.label }}</span>
+                  <span class="node-label" @click="handleFolderClick(data)">{{ node.label }}</span>
                   <span class="node-actions">
                     <span class="menu-trigger" @click="toggleMenu($event, data)">
                       <el-icon><MoreFilled /></el-icon>
@@ -83,16 +83,119 @@
               <span>{{ t('document.document.menu.folder') }}</span>
             </li>
           </template>
-          <!-- 文件夹:只显示文件夹 -->
+          <!-- 文件夹:只显示文件夹和文档 -->
           <template v-else-if="currentMenuData && currentMenuData.type === 0">
             <li class="menu-item" @click="handleMenuItemClick('add:0', currentMenuData)">
               <span>{{ t('document.document.menu.folder') }}</span>
             </li>
+            <li class="menu-item" v-hasPermi="['document:document:add']" @click="handleMenuItemClick('add:document', currentMenuData)">
+              <span>{{ t('document.document.menu.document') }}</span>
+            </li>
           </template>
         </ul>
         
         <div class="content-container">
-          <el-empty :description="t('document.document.empty.description')">
+          <!-- 文档列表展示区域 -->
+          <div v-if="selectedFolder" class="document-list-container">
+            <!-- 搜索栏 -->
+            <el-form :model="documentQueryParams" :inline="true" class="search-form">
+              <el-form-item :label="t('document.document.documentList.fileName')">
+                <el-input
+                  v-model="documentQueryParams.name"
+                  :placeholder="t('document.document.documentList.fileNamePlaceholder')"
+                  clearable
+                  style="width: 240px"
+                  @keyup.enter="handleDocumentQuery"
+                />
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleDocumentQuery">{{ t('document.document.button.search') }}</el-button>
+                <el-button icon="Refresh" @click="handleDocumentReset">{{ t('document.document.button.reset') }}</el-button>
+              </el-form-item>
+            </el-form>
+
+            <!-- 文档列表 -->
+            <el-table v-loading="documentLoading" :data="documentList" border style="margin-top: 10px">
+              <el-table-column type="index" width="55" align="center" :label="t('document.document.documentList.index')" />
+              <el-table-column prop="name" :label="t('document.document.documentList.name')" min-width="150" show-overflow-tooltip />
+              <el-table-column prop="specification" :label="t('document.document.documentList.specification')" min-width="120" show-overflow-tooltip />
+              <el-table-column prop="planDocumentType" :label="t('document.document.documentList.planDocumentType')" width="120" align="center">
+                <template #default="scope">
+                  <dict-tag v-if="scope.row.planDocumentType" :options="plan_document_type" :value="scope.row.planDocumentType" />
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="submitter" :label="t('document.document.documentList.submitter')" width="120" align="center" />
+              <el-table-column prop="submitDeadline" :label="t('document.document.documentList.submitDeadline')" width="110" align="center">
+                <template #default="scope">
+                  <span v-if="scope.row.submitDeadline">{{ parseTime(scope.row.submitDeadline, '{y}-{m}-{d}') }}</span>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="submitTime" :label="t('document.document.documentList.submitTime')" width="160" align="center">
+                <template #default="scope">
+                  <span v-if="scope.row.submitTime">{{ parseTime(scope.row.submitTime) }}</span>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="url" :label="t('document.document.documentList.url')" min-width="200">
+                <template #default="scope">
+                  <div v-if="scope.row.fileName" class="file-name-cell">
+                    <el-icon :size="18" class="file-icon">
+                      <Document v-if="isWordFile(scope.row.fileName)" style="color: #2b579a;" />
+                      <Grid v-else-if="isExcelFile(scope.row.fileName)" style="color: #217346;" />
+                      <Monitor v-else-if="isPPTFile(scope.row.fileName)" style="color: #d24726;" />
+                      <Reading v-else-if="isPDFFile(scope.row.fileName)" style="color: #e74c3c;" />
+                      <Document v-else style="color: #606266;" />
+                    </el-icon>
+                    <span class="file-name-text">{{ scope.row.fileName }}</span>
+                    <!-- 下载按钮已注释 -->
+                    <!-- <el-link v-if="scope.row.url" type="primary" :href="scope.row.url" :download="scope.row.fileName" target="_blank" :underline="false" class="download-btn">
+                      <el-icon><Download /></el-icon>
+                    </el-link> -->
+                  </div>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="note" :label="t('document.document.documentList.note')" min-width="150" show-overflow-tooltip />
+              <el-table-column prop="createTime" :label="t('document.document.documentList.createTime')" width="160" align="center">
+                <template #default="scope">
+                  <span v-if="scope.row.createTime">{{ parseTime(scope.row.createTime) }}</span>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="updateTime" :label="t('document.document.documentList.updateTime')" width="160" align="center">
+                <template #default="scope">
+                  <span v-if="scope.row.updateTime">{{ parseTime(scope.row.updateTime) }}</span>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column :label="t('document.document.documentList.action')" width="100" align="center" fixed="right">
+                <template #default="scope">
+                  <el-button
+                    v-hasPermi="['document:document:audit']"
+                    type="primary"
+                    link
+                    :icon="Select"
+                    @click="handleAudit(scope.row)"
+                    :title="t('document.document.button.audit')"
+                  />
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <!-- 分页 -->
+            <pagination
+              v-show="documentTotal > 0"
+              v-model:page="documentQueryParams.pageNum"
+              v-model:limit="documentQueryParams.pageSize"
+              :total="documentTotal"
+              @pagination="getDocumentList"
+            />
+          </div>
+          
+          <!-- 空状态 -->
+          <el-empty v-else :description="t('document.document.empty.description')">
           </el-empty>
         </div>
       </div>
@@ -129,18 +232,100 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 添加文档对话框 -->
+    <el-dialog v-model="documentDialog.visible" :title="documentDialog.title" width="700px" append-to-body>
+      <el-form ref="documentFormRef" :model="documentForm" :rules="documentRules" label-width="140px">
+        <el-form-item :label="documentForm.type === 1 ? t('document.document.documentForm.planName') : t('document.document.documentForm.name')" prop="name">
+          <el-input v-model="documentForm.name" :placeholder="t('document.document.documentForm.namePlaceholder')" clearable />
+        </el-form-item>
+        
+        <el-form-item :label="t('document.document.documentForm.type')" prop="type" v-if="hasAddPlanPermission">
+          <el-radio-group v-model="documentForm.type" @change="handleDocumentTypeChange">
+            <el-radio :label="0">{{ t('document.document.documentForm.normalDocument') }}</el-radio>
+            <el-radio :label="1">{{ t('document.document.documentForm.planDocument') }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        
+        <el-form-item :label="t('document.document.documentForm.submitter')" prop="submitterId">
+          <template v-if="documentForm.type === 0">
+            <el-input v-model="currentUserName" disabled />
+          </template>
+          <template v-else>
+            <el-select
+              v-model="documentForm.submitterId"
+              filterable
+              remote
+              reserve-keyword
+              :placeholder="t('document.document.documentForm.submitterPlaceholder')"
+              :remote-method="searchSubmitters"
+              :loading="submitterSearchLoading"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="submitter in submitterOptions"
+                :key="submitter.id"
+                :label="`${submitter.name} / ${submitter.dept} --- ${submitter.phoneNumber}`"
+                :value="submitter.id"
+              />
+            </el-select>
+          </template>
+        </el-form-item>
+        
+        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.submitDeadline')" prop="submitDeadline">
+          <el-date-picker
+            v-model="documentForm.submitDeadline"
+            type="date"
+            value-format="YYYY-MM-DD"
+            :placeholder="t('document.document.documentForm.submitDeadlinePlaceholder')"
+            style="width: 100%"
+          />
+        </el-form-item>
+        
+        <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.planType')" prop="planType">
+          <el-select v-model="documentForm.planType" :placeholder="t('document.document.documentForm.planTypePlaceholder')" clearable style="width: 100%">
+            <el-option v-for="dict in plan_document_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item :label="t('document.document.documentForm.file')" :prop="documentForm.type === 0 ? 'ossId' : ''">
+          <fileUpload v-model="uploadedFileId" :limit="1" />
+        </el-form-item>
+        
+        <el-form-item v-if="documentForm.submitTime" :label="t('document.document.documentForm.submitTime')">
+          <el-input v-model="documentForm.submitTime" disabled />
+        </el-form-item>
+        
+        <el-form-item :label="t('document.document.documentForm.note')" prop="note">
+          <el-input v-model="documentForm.note" type="textarea" :rows="4" :placeholder="t('document.document.documentForm.notePlaceholder')" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="documentButtonLoading" type="primary" @click="submitDocumentForm">{{ t('document.document.button.submit') }}</el-button>
+          <el-button @click="cancelDocument">{{ t('document.document.button.cancel') }}</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onUnmounted, nextTick, getCurrentInstance, watch } from 'vue';
+import { ref, reactive, onMounted, onUnmounted, nextTick, getCurrentInstance, watch, computed } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { listFolder, addFolder, delFolder, getFolder, updateFolder } from '@/api/document/folder';
 import { FolderListVO, FolderForm } from '@/api/document/folder/types';
-import { Folder, Document, Edit, Delete, Plus, MoreFilled, Location, OfficeBuilding, ArrowRight } from '@element-plus/icons-vue';
+import { addDocument, listDocument } from '@/api/document/document';
+import { DocumentForm, DocumentQuery, DocumentVO } from '@/api/document/document/types';
+import { queryMemberNotInCenter } from '@/api/project/management';
+import { MemberNotInCenterVO, MemberNotInCenterQuery } from '@/api/project/management/types';
+import { Folder, Document, Edit, Delete, Plus, MoreFilled, Location, OfficeBuilding, ArrowRight, Download, Select, Grid, Monitor, Reading } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import type { FormInstance } from 'element-plus';
 import type { ComponentInternalInstance } from 'vue';
+import { useUserStore } from '@/store/modules/user';
+import { checkPermi } from '@/utils/permission';
+import fileUpload from '@/components/FileUpload/index.vue';
 
 interface Props {
   projectId?: number | string;
@@ -154,12 +339,22 @@ const emit = defineEmits<{
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { t } = useI18n();
+const userStore = useUserStore();
+const { plan_document_type } = toRefs<any>(proxy?.useDict('plan_document_type'));
 
 // 数据定义
 const loading = ref(false);
 const buttonLoading = ref(false);
 const treeData = ref<FolderListVO[]>([]);
 const folderFormRef = ref<FormInstance>();
+const documentFormRef = ref<FormInstance>();
+const documentButtonLoading = ref(false);
+
+// 检查是否有计划文档添加权限
+const hasAddPlanPermission = computed(() => checkPermi(['document:document:addPlan']));
+
+// 当前用户信息
+const currentUserName = ref(userStore.nickname || '');
 
 // 对话框
 const dialog = reactive({
@@ -168,6 +363,12 @@ const dialog = reactive({
   isEdit: false
 });
 
+// 文档对话框
+const documentDialog = reactive({
+  visible: false,
+  title: ''
+});
+
 // 当前操作的节点
 const currentNode = ref<FolderListVO | null>(null);
 
@@ -190,6 +391,48 @@ const initFormData: FolderForm = {
 // 表单数据
 const form = ref<FolderForm>({ ...initFormData });
 
+// 文档表单初始数据
+const initDocumentFormData: DocumentForm = {
+  id: undefined,
+  name: '',
+  type: 0,
+  submitterId: undefined,
+  folderId: undefined,
+  submitDeadline: undefined,
+  planType: undefined,
+  ossId: undefined,
+  submitTime: undefined,
+  note: ''
+};
+
+// 文件上传的ossId(字符串格式)
+const uploadedFileId = ref<string>('');
+
+// 文档表单数据
+const documentForm = ref<DocumentForm>({ ...initDocumentFormData });
+
+// 递交人搜索相关
+const submitterSearchLoading = ref(false);
+const submitterOptions = ref<MemberNotInCenterVO[]>([]);
+let submitterSearchTimer: NodeJS.Timeout | null = null;
+
+// ========== 文档列表相关 ==========
+// 文档列表数据
+const documentList = ref<DocumentVO[]>([]);
+const documentLoading = ref(false);
+const documentTotal = ref(0);
+
+// 当前选中的文件夹
+const selectedFolder = ref<FolderListVO | null>(null);
+
+// 文档查询参数
+const documentQueryParams = reactive<DocumentQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  name: '',
+  folderId: undefined
+});
+
 // 表单验证规则
 const rules = {
   name: [
@@ -200,6 +443,19 @@ const rules = {
   ]
 };
 
+// 文档表单验证规则
+const documentRules = {
+  name: [
+    { required: true, message: t('document.document.documentRule.nameRequired'), trigger: 'blur' }
+  ],
+  submitterId: [
+    { required: true, message: t('document.document.documentRule.submitterRequired'), trigger: 'change' }
+  ],
+  ossId: [
+    { required: true, message: t('document.document.documentRule.fileRequired'), trigger: 'change' }
+  ]
+};
+
 // 树形组件配置
 const treeProps = {
   children: 'children',
@@ -314,8 +570,13 @@ const getAvailableTypes = () => {
 // 下拉菜单命令处理
 const handleCommand = (command: string, data: FolderListVO) => {
   if (command.startsWith('add:')) {
-    const type = parseInt(command.split(':')[1]);
-    handleAddChildWithType(data, type);
+    const cmdPart = command.split(':')[1];
+    if (cmdPart === 'document') {
+      handleAddDocument(data);
+    } else {
+      const type = parseInt(cmdPart);
+      handleAddChildWithType(data, type);
+    }
   } else if (command === 'edit') {
     handleEdit(data);
   } else if (command === 'delete') {
@@ -517,6 +778,222 @@ const handleMenuItemClick = (command: string, data: FolderListVO | null) => {
   closeAllMenus();
 };
 
+// ========== 文档相关函数 ==========
+
+// 重置文档表单
+const resetDocumentForm = () => {
+  documentForm.value = { ...initDocumentFormData };
+  uploadedFileId.value = '';
+  // 如果没有计划文档权限,默认为非计划文档
+  if (!hasAddPlanPermission.value) {
+    documentForm.value.type = 0;
+  }
+  documentForm.value.submitterId = userStore.userId;
+  currentUserName.value = userStore.nickname || '';
+  submitterOptions.value = [];
+  documentFormRef.value?.resetFields();
+};
+
+// 添加文档
+const handleAddDocument = (data: FolderListVO) => {
+  resetDocumentForm();
+  documentForm.value.folderId = data.id;
+  documentDialog.visible = true;
+  documentDialog.title = t('document.document.dialog.addDocument');
+};
+
+// 处理文档类型变化
+const handleDocumentTypeChange = (value: number) => {
+  if (value === 0) {
+    // 非计划文档,递交人为当前用户
+    documentForm.value.submitterId = userStore.userId;
+    documentForm.value.submitDeadline = undefined;
+    documentForm.value.planType = undefined;
+  } else {
+    // 计划文档,清空递交人
+    documentForm.value.submitterId = undefined;
+  }
+};
+
+// 搜索递交人
+const searchSubmitters = async (query: string) => {
+  if (!query || query.trim() === '') {
+    submitterOptions.value = [];
+    return;
+  }
+  
+  // 清除之前的定时器
+  if (submitterSearchTimer) {
+    clearTimeout(submitterSearchTimer);
+  }
+  
+  // 设置防抖
+  submitterSearchTimer = setTimeout(async () => {
+    submitterSearchLoading.value = true;
+    try {
+      const queryParams: MemberNotInCenterQuery = {
+        pageNum: 1,
+        pageSize: 10,
+        projectId: props.projectId || 0,
+        folderId: 0,
+        name: query
+      };
+      const res = await queryMemberNotInCenter(queryParams);
+      submitterOptions.value = res.rows || [];
+    } catch (error) {
+      console.error('Failed to search submitters:', error);
+      ElMessage.error(t('document.document.message.searchSubmitterFailed'));
+    } finally {
+      submitterSearchLoading.value = false;
+    }
+  }, 300);
+};
+
+// 监听文件上传变化
+watch(uploadedFileId, (newVal) => {
+  if (newVal) {
+    // 解析文件ID(可能是逗号分隔的字符串)
+    const ids = newVal.split(',').filter(id => id.trim());
+    if (ids.length > 0) {
+      documentForm.value.ossId = parseInt(ids[0]);
+      // 自动设置递交时间为当前时间
+      const now = new Date();
+      documentForm.value.submitTime = proxy?.parseTime(now, '{y}-{m}-{d} {h}:{i}:{s}');
+    }
+  } else {
+    documentForm.value.ossId = undefined;
+    documentForm.value.submitTime = undefined;
+  }
+});
+
+// 取消文档对话框
+const cancelDocument = () => {
+  resetDocumentForm();
+  documentDialog.visible = false;
+};
+
+// 提交文档表单
+const submitDocumentForm = () => {
+  documentFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      documentButtonLoading.value = true;
+      try {
+        // 构建完整的请求数据,确保所有字段都存在(参考文件夹的实现)
+        const submitData: DocumentForm = {
+          id: documentForm.value.id || 0,
+          name: documentForm.value.name || '',
+          type: documentForm.value.type !== undefined ? documentForm.value.type : 0,
+          submitterId: documentForm.value.submitterId || 0,
+          folderId: documentForm.value.folderId || 0,
+          submitDeadline: documentForm.value.submitDeadline || '',
+          planType: documentForm.value.planType || '',
+          ossId: documentForm.value.ossId || null,
+          submitTime: documentForm.value.submitTime || '',
+          note: documentForm.value.note || ''
+        };
+        
+        await addDocument(submitData);
+        proxy?.$modal.msgSuccess(t('document.document.message.addDocumentSuccess'));
+        documentDialog.visible = false;
+        await getList();
+        // 刷新文档列表
+        await getDocumentList();
+      } catch (error) {
+        console.error(t('document.document.message.addDocumentFailed'), error);
+      } finally {
+        documentButtonLoading.value = false;
+      }
+    }
+  });
+};
+
+// ========== 文档列表相关函数 ==========
+
+// 点击文件夹节点
+const handleFolderClick = (data: FolderListVO) => {
+  // 只有文件夹类型(type=0)才显示文档列表
+  if (data.type === 0) {
+    selectedFolder.value = data;
+    // 重置查询参数
+    documentQueryParams.name = '';
+    documentQueryParams.pageNum = 1;
+    // 加载文档列表
+    getDocumentList();
+  } else {
+    selectedFolder.value = null;
+    documentList.value = [];
+  }
+};
+
+// 查询文档列表
+const getDocumentList = async () => {
+  if (!selectedFolder.value) return;
+  
+  documentLoading.value = true;
+  documentQueryParams.folderId = selectedFolder.value.id;
+  
+  try {
+    const res = await listDocument(documentQueryParams);
+    documentList.value = res.rows || [];
+    documentTotal.value = res.total || 0;
+  } catch (error) {
+    console.error('Failed to get document list:', error);
+    ElMessage.error(t('document.document.message.getDocumentListFailed'));
+  } finally {
+    documentLoading.value = false;
+  }
+};
+
+// 搜索按钮
+const handleDocumentQuery = () => {
+  documentQueryParams.pageNum = 1;
+  getDocumentList();
+};
+
+// 重置按钮
+const handleDocumentReset = () => {
+  documentQueryParams.name = '';
+  documentQueryParams.pageNum = 1;
+  getDocumentList();
+};
+
+// 审核文档
+const handleAudit = (row: DocumentVO) => {
+  console.log('审核文档:', row);
+  ElMessage.info('审核功能待实现');
+};
+
+// ========== 文件类型判断函数 ==========
+
+// 判断是否为Word文档
+const isWordFile = (fileName: string): boolean => {
+  if (!fileName) return false;
+  const lowerFileName = fileName.toLowerCase();
+  return lowerFileName.endsWith('.doc') || lowerFileName.endsWith('.docx');
+};
+
+// 判断是否为Excel文档
+const isExcelFile = (fileName: string): boolean => {
+  if (!fileName) return false;
+  const lowerFileName = fileName.toLowerCase();
+  return lowerFileName.endsWith('.xls') || 
+         lowerFileName.endsWith('.xlsx') || 
+         lowerFileName.endsWith('.csv');
+};
+
+// 判断是否为PPT文档
+const isPPTFile = (fileName: string): boolean => {
+  if (!fileName) return false;
+  const lowerFileName = fileName.toLowerCase();
+  return lowerFileName.endsWith('.ppt') || lowerFileName.endsWith('.pptx');
+};
+
+// 判断是否为PDF文档
+const isPDFFile = (fileName: string): boolean => {
+  if (!fileName) return false;
+  return fileName.toLowerCase().endsWith('.pdf');
+};
+
 // 关闭所有菜单
 const closeAllMenus = () => {
   showSecondaryMenu.value = false;
@@ -570,6 +1047,11 @@ onUnmounted(() => {
   document.removeEventListener('scroll', handleScroll, true);
   // 清理菜单状态
   closeAllMenus();
+  // 清理搜索定时器
+  if (submitterSearchTimer) {
+    clearTimeout(submitterSearchTimer);
+    submitterSearchTimer = null;
+  }
 });
 </script>
 
@@ -638,6 +1120,11 @@ onUnmounted(() => {
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
+    cursor: pointer;
+    
+    &:hover {
+      color: var(--el-color-primary);
+    }
   }
   
   .node-actions {
@@ -672,6 +1159,42 @@ onUnmounted(() => {
   overflow: auto;
 }
 
+.document-list-container {
+  width: 100%;
+  
+  .search-form {
+    margin-bottom: 16px;
+  }
+  
+  .file-name-cell {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 0 8px;
+    
+    .file-icon {
+      flex-shrink: 0;
+    }
+    
+    .file-name-text {
+      flex: 1;
+      text-align: left;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    
+    .download-btn {
+      flex-shrink: 0;
+      font-size: 16px;
+      
+      &:hover {
+        transform: scale(1.1);
+      }
+    }
+  }
+}
+
 .detail-content {
   max-width: 800px;
 }