Quellcode durchsuchen

归档已完成

Huanyi vor 3 Monaten
Ursprung
Commit
d9101fb4d4
44 geänderte Dateien mit 1164 neuen und 309 gelöschten Zeilen
  1. 3 2
      src/api/document/document/index.ts
  2. 5 0
      src/api/document/document/types.ts
  3. 30 0
      src/api/home/taskCenter/filing/index.ts
  4. 33 0
      src/api/home/taskCenter/filing/types.ts
  5. 12 0
      src/api/home/taskCenter/submission/index.ts
  6. 2 0
      src/api/home/taskCenter/submission/types.ts
  7. 23 0
      src/api/setting/agreement/index.ts
  8. 14 0
      src/api/setting/agreement/types.ts
  9. 0 63
      src/api/setting/applet/index.ts
  10. 0 52
      src/api/setting/applet/types.ts
  11. 393 0
      src/components/ArchiveConfirmDialog/index.vue
  12. 18 8
      src/components/DocumentAuditDialog/index.vue
  13. 1 1
      src/components/FileUpload/index.vue
  14. 2 1
      src/lang/modules/components/en_US.ts
  15. 2 1
      src/lang/modules/components/zh_CN.ts
  16. 13 3
      src/lang/modules/document/document/en_US.ts
  17. 13 3
      src/lang/modules/document/document/zh_CN.ts
  18. 1 1
      src/lang/modules/home/taskCenter/en_US.ts
  19. 1 1
      src/lang/modules/home/taskCenter/zh_CN.ts
  20. 1 1
      src/lang/modules/project/management/en_US.ts
  21. 1 1
      src/lang/modules/search/en_US.ts
  22. 1 1
      src/lang/modules/search/zh_CN.ts
  23. 36 0
      src/lang/modules/setting/agreement/en_US.ts
  24. 36 0
      src/lang/modules/setting/agreement/zh_CN.ts
  25. 3 1
      src/lang/modules/setting/index.ts
  26. 3 1
      src/lang/modules/setting/index_en.ts
  27. 1 1
      src/lang/modules/system/tenant/en_US.ts
  28. 1 1
      src/lang/modules/system/tenant/zh_CN.ts
  29. 1 1
      src/lang/modules/system/user/en_US.ts
  30. 1 3
      src/layout/components/AppMain.vue
  31. 1 1
      src/permission.ts
  32. 1 1
      src/router/index.ts
  33. 38 8
      src/views/document/folder/document/DocumentList.vue
  34. 2 1
      src/views/document/folder/document/FolderTree.vue
  35. 0 100
      src/views/document/folder/document/components/ArchiveConfirmDialog.vue
  36. 25 4
      src/views/document/folder/document/index.vue
  37. 0 1
      src/views/document/folder/待优化.md
  38. 354 4
      src/views/home/taskCenter/filing/index.vue
  39. 45 4
      src/views/home/taskCenter/submission/index.vue
  40. 1 1
      src/views/home/workbench/index.vue
  41. 9 0
      src/views/search/index.vue
  42. 35 35
      src/views/setting/agreement/index.vue
  43. 1 1
      src/views/system/client/index.vue
  44. 1 1
      src/views/workflow/task/myDocument.vue

+ 3 - 2
src/api/document/document/index.ts

@@ -95,12 +95,13 @@ export const listDocumentAuditLog = (query: any): AxiosPromise<any> => {
 /**
  * 归档文档
  * @param documentId 文档ID
+ * @param folderId 文件夹ID
  */
-export const filingDocument = (documentId: number) => {
+export const filingDocument = (documentId: number, folderId?: number | string) => {
   return request({
     url: '/document/document/filing',
     method: 'put',
-    data: { documentId }
+    data: { documentId, folderId }
   });
 };
 

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

@@ -145,6 +145,11 @@ export interface DocumentForm extends BaseEntity {
    * 备注
    */
   note?: string;
+
+  /**
+   * 是否需要寄送
+   */
+  sendFlag?: boolean;
 }
 
 export interface DocumentQuery extends PageQuery {

+ 30 - 0
src/api/home/taskCenter/filing/index.ts

@@ -0,0 +1,30 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { FilingTaskVO, FilingTaskQuery, FilingTaskFilingForm } from './types';
+
+/**
+ * 查询归档任务列表
+ * @param query 查询参数
+ */
+export const listFilingTasks = (query: FilingTaskQuery): AxiosPromise<{
+    total: number;
+    rows: FilingTaskVO[];
+}> => {
+    return request({
+        url: '/home/taskCenter/filing/list',
+        method: 'get',
+        params: query
+    });
+};
+
+/**
+ * 归档文档
+ * @param data 归档参数
+ */
+export const filingDocument = (data: FilingTaskFilingForm) => {
+    return request({
+        url: '/home/taskCenter/filing/filing',
+        method: 'put',
+        data: data
+    });
+};

+ 33 - 0
src/api/home/taskCenter/filing/types.ts

@@ -0,0 +1,33 @@
+export interface FilingTaskVO {
+    id: number;
+    name: string;
+    status: number;
+    type: number;
+    documentType: string;
+    submitter: string;
+    submitterId: number;
+    deadline: string;
+    submitTime: string;
+    createBy: string;
+    createTime: string;
+    ossId: number;
+    ossUrl: string;
+    folderId?: number | string;
+    folderName?: string;
+    projectId?: number | string;
+    projectCode?: string;
+    projectName?: string;
+}
+
+export interface FilingTaskQuery extends PageQuery {
+    name?: string;
+    projectCode?: string;
+    projectName?: string;
+    centerName?: string;
+}
+
+export interface FilingTaskFilingForm {
+    documentId: number;
+    folderId: number | string;
+    projectId: number | string;
+}

+ 12 - 0
src/api/home/taskCenter/submission/index.ts

@@ -39,4 +39,16 @@ export const listSubmissionAuditLog = (query: any): AxiosPromise<any> => {
     method: 'get',
     params: query
   });
+};
+
+/**
+ * 确认寄送
+ * @param id 文档ID
+ */
+export const sendDocument = (id: number) => {
+  return request({
+    url: '/home/taskCenter/submission/send',
+    method: 'put',
+    data: { id }
+  });
 };

+ 2 - 0
src/api/home/taskCenter/submission/types.ts

@@ -9,6 +9,8 @@ export interface SubmissionTaskVO {
   submitTime: string;
   createBy: string;
   createTime: string;
+  sendFlag: boolean;
+  sendStatus: boolean;
 }
 
 export interface SubmissionTaskQuery extends PageQuery {

+ 23 - 0
src/api/setting/agreement/index.ts

@@ -0,0 +1,23 @@
+import request from '@/utils/request';
+import { AgreementVO } from './types';
+
+/**
+ * 获取协议设置
+ */
+export function getAgreement(id: number) {
+  return request({
+    url: `/setting/agreement/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 更新协议设置
+ */
+export function updateAgreement(data: AgreementVO) {
+  return request({
+    url: '/setting/agreement',
+    method: 'put',
+    data: data
+  });
+}

+ 14 - 0
src/api/setting/agreement/types.ts

@@ -0,0 +1,14 @@
+/**
+ * 协议设置对象
+ */
+export interface AgreementVO {
+  id?: number;
+  userAgreement?: string;
+  privacyAgreement?: string;
+  createDept?: number;
+  createBy?: number;
+  createTime?: string;
+  updateBy?: number;
+  updateTime?: string;
+  params?: Record<string, any>;
+}

+ 0 - 63
src/api/setting/applet/index.ts

@@ -1,63 +0,0 @@
-import request from '@/utils/request';
-import { AxiosPromise } from 'axios';
-import { AppletVO, AppletForm, AppletQuery } from '@/api/setting/applet/types';
-
-/**
- * 查询小程序设置列表
- * @param query
- * @returns {*}
- */
-
-export const listApplet = (query?: AppletQuery): AxiosPromise<AppletVO[]> => {
-  return request({
-    url: '/setting/applet/list',
-    method: 'get',
-    params: query
-  });
-};
-
-/**
- * 查询小程序设置详细
- * @param id
- */
-export const getApplet = (id: string | number): AxiosPromise<AppletVO> => {
-  return request({
-    url: '/setting/applet/' + id,
-    method: 'get'
-  });
-};
-
-/**
- * 新增小程序设置
- * @param data
- */
-export const addApplet = (data: AppletForm) => {
-  return request({
-    url: '/setting/applet',
-    method: 'post',
-    data: data
-  });
-};
-
-/**
- * 修改小程序设置
- * @param data
- */
-export const updateApplet = (data: AppletForm) => {
-  return request({
-    url: '/setting/applet',
-    method: 'put',
-    data: data
-  });
-};
-
-/**
- * 删除小程序设置
- * @param id
- */
-export const delApplet = (id: string | number | Array<string | number>) => {
-  return request({
-    url: '/setting/applet/' + id,
-    method: 'delete'
-  });
-};

+ 0 - 52
src/api/setting/applet/types.ts

@@ -1,52 +0,0 @@
-export interface AppletVO extends BaseEntity {
-  /**
-   * 序号
-   */
-  id?: string | number;
-
-  /**
-   * 用户协议
-   */
-  userAgreement?: string;
-
-  /**
-   * 隐私协议
-   */
-  privacyAgreement?: string;
-}
-
-export interface AppletForm extends BaseEntity {
-  /**
-   * 序号
-   */
-  id?: string | number;
-
-  /**
-   * 用户协议
-   */
-  userAgreement?: string;
-
-  /**
-   * 隐私协议
-   */
-  privacyAgreement?: string;
-
-}
-
-export interface AppletQuery extends PageQuery {
-
-  /**
-   * 用户协议
-   */
-  userAgreement?: string;
-
-  /**
-   * 隐私协议
-   */
-  privacyAgreement?: string;
-
-  /**
-   * 日期范围参数
-   */
-  params?: any;
-}

+ 393 - 0
src/components/ArchiveConfirmDialog/index.vue

@@ -0,0 +1,393 @@
+<template>
+  <div>
+    <!-- 第一步:选择文件夹 -->
+    <el-dialog 
+      v-model="dialogVisible" 
+      :title="t('document.document.dialog.archiveDialog.title')" 
+      width="600px" 
+      append-to-body
+      @close="handleClose"
+    >
+      <div class="archive-content">
+        <!-- 文档信息 -->
+        <div class="document-info" v-if="document">
+          <el-descriptions :column="1" size="default" border>
+            <el-descriptions-item :label="t('document.document.dialog.archiveDialog.documentName')">
+              {{ document.name }}
+            </el-descriptions-item>
+            <el-descriptions-item :label="t('document.document.dialog.archiveDialog.currentFolder')">
+              {{ getCurrentFolderName(document.folderId) }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <!-- 文件夹选择 -->
+        <div class="folder-select-section">
+          <div class="section-title">{{ t('document.document.dialog.archiveDialog.selectFolder') }}</div>
+          <el-alert 
+            v-if="!treeData || treeData.length === 0" 
+            title="暂无可选文件夹" 
+            type="warning" 
+            :closable="false"
+            style="margin-bottom: 10px"
+          />
+          <el-tree
+            ref="treeRef"
+            :data="treeData"
+            :props="treeProps"
+            node-key="id"
+            :default-expanded-keys="defaultExpandedKeys"
+            :default-checked-keys="defaultCheckedKeys"
+            show-checkbox
+            check-strictly
+            :check-on-click-node="true"
+            @check="handleCheck"
+            class="folder-tree"
+          >
+            <template #default="{ node, data }">
+              <span class="custom-tree-node">
+                <el-icon v-if="data.type === 1"><OfficeBuilding /></el-icon>
+                <el-icon v-else-if="data.type === 2"><Location /></el-icon>
+                <el-icon v-else><Folder /></el-icon>
+                <span class="node-label">{{ node.label }}</span>
+              </span>
+            </template>
+          </el-tree>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleCancel">{{ t('document.document.button.cancel') }}</el-button>
+          <el-button type="primary" @click="handleNext" :disabled="!selectedFolderId">
+            {{ t('document.document.button.confirm') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 第二步:确认归档 -->
+    <el-dialog 
+      v-model="confirmDialogVisible" 
+      :title="t('document.document.dialog.archiveDialog.confirmTitle')" 
+      width="500px" 
+      append-to-body
+      @close="handleConfirmDialogClose"
+    >
+      <div class="confirm-content">
+        <el-alert 
+          :title="t('document.document.dialog.archiveDialog.confirmMessage')" 
+          type="warning" 
+          show-icon
+          :closable="false" 
+        />
+        <div class="confirm-info" v-if="document">
+          <el-descriptions :column="1" size="default" border>
+            <el-descriptions-item :label="t('document.document.dialog.archiveDialog.documentName')">
+              {{ document.name }}
+            </el-descriptions-item>
+            <el-descriptions-item :label="t('document.document.dialog.archiveDialog.oldFolder')">
+              {{ getCurrentFolderName(document.folderId) }}
+            </el-descriptions-item>
+            <el-descriptions-item :label="t('document.document.dialog.archiveDialog.newFolder')">
+              {{ getSelectedFolderName() }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleBackToSelect">{{ t('document.document.button.back') }}</el-button>
+          <el-button type="primary" @click="handleConfirm" :loading="loading">
+            {{ t('document.document.button.confirm') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { OfficeBuilding, Location, Folder } from '@element-plus/icons-vue';
+
+interface Document {
+  id: number | string;
+  name: string;
+  folderId?: number | string;
+}
+
+interface FolderNode {
+  id: number | string;
+  name: string;
+  type?: number;
+  children?: FolderNode[];
+}
+
+interface Props {
+  modelValue: boolean;
+  document?: Document | null;
+  treeData?: FolderNode[];
+  projectId?: number | string;
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: boolean): void;
+  (e: 'confirm', data: { document: Document; folderId: number | string; projectId: number | string }): void;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  treeData: () => []
+});
+const emit = defineEmits<Emits>();
+
+const { t } = useI18n();
+
+const dialogVisible = ref(false);
+const confirmDialogVisible = ref(false);
+const loading = ref(false);
+const selectedFolderId = ref<number | string | undefined>(undefined);
+const treeRef = ref();
+const isSettingChecked = ref(false); // 添加标志位防止循环触发
+
+// 树配置
+const treeProps = {
+  children: 'children',
+  label: 'name'
+};
+
+// 默认展开的节点
+const defaultExpandedKeys = computed(() => {
+  if (props.document?.folderId) {
+    return [props.document.folderId];
+  }
+  return [];
+});
+
+// 默认选中的节点
+const defaultCheckedKeys = computed(() => {
+  if (props.document?.folderId) {
+    return [props.document.folderId];
+  }
+  return [];
+});
+
+// 监听modelValue变化
+watch(
+  () => props.modelValue,
+  (val) => {
+    dialogVisible.value = val;
+    if (val && props.document) {
+      console.log('打开归档对话框,树数据:', props.treeData);
+      console.log('当前文档:', props.document);
+      console.log('项目ID:', props.projectId);
+      // 默认选中当前文件夹
+      selectedFolderId.value = props.document.folderId;
+      // 设置树的选中状态
+      setTimeout(() => {
+        if (treeRef.value && props.document?.folderId) {
+          treeRef.value.setCheckedKeys([props.document.folderId]);
+        }
+      }, 100);
+    }
+  }
+);
+
+// 监听dialogVisible变化
+watch(dialogVisible, (val) => {
+  if (!val && !confirmDialogVisible.value) {
+    // 只有在确认对话框也关闭的情况下才重置状态和通知父组件
+    emit('update:modelValue', val);
+    // 重置状态
+    selectedFolderId.value = undefined;
+  }
+});
+
+// 处理复选框选中(实现单选效果)
+const handleCheck = (data: FolderNode, checked: any) => {
+  // 如果是程序设置的,跳过处理
+  if (isSettingChecked.value) {
+    return;
+  }
+  
+  const clickedId = data.id;
+  const checkedKeys = checked.checkedKeys || [];
+  
+  console.log('handleCheck 被调用', { 
+    clickedId,
+    checkedKeys,
+    currentSelected: selectedFolderId.value
+  });
+  
+  // 设置标志位,防止循环触发
+  isSettingChecked.value = true;
+  
+  // 实现单选:只保留当前点击的节点
+  if (checkedKeys.includes(clickedId)) {
+    // 如果当前节点在选中列表中,说明是选中操作
+    selectedFolderId.value = clickedId;
+    console.log('选中文件夹:', clickedId);
+    // 清除所有选中,只保留当前节点
+    treeRef.value?.setCheckedKeys([clickedId]);
+  } else {
+    // 如果当前节点不在选中列表中,说明是取消选中操作
+    // 不允许取消选中,重新选中该节点
+    console.log('不允许取消选中,保持选中状态');
+    treeRef.value?.setCheckedKeys([selectedFolderId.value || clickedId]);
+  }
+  
+  // 延迟重置标志位
+  setTimeout(() => {
+    isSettingChecked.value = false;
+  }, 100);
+};
+
+// 获取当前文件夹名称
+const getCurrentFolderName = (folderId: number | string | undefined): string => {
+  if (!folderId || !props.treeData || props.treeData.length === 0) return '-';
+  const folder = findFolderById(props.treeData, folderId);
+  return folder ? folder.name : '-';
+};
+
+// 获取选中的文件夹名称
+const getSelectedFolderName = (): string => {
+  if (!selectedFolderId.value || !props.treeData || props.treeData.length === 0) return '-';
+  const folder = findFolderById(props.treeData, selectedFolderId.value);
+  return folder ? folder.name : '-';
+};
+
+// 递归查找文件夹
+const findFolderById = (folders: FolderNode[] | undefined, id: number | string): FolderNode | null => {
+  if (!folders || folders.length === 0) return null;
+  for (const folder of folders) {
+    if (folder.id === id) {
+      return folder;
+    }
+    if (folder.children && folder.children.length > 0) {
+      const found = findFolderById(folder.children, id);
+      if (found) return found;
+    }
+  }
+  return null;
+};
+
+// 关闭对话框
+const handleClose = () => {
+  confirmDialogVisible.value = false;
+  dialogVisible.value = false;
+  emit('update:modelValue', false);
+  selectedFolderId.value = undefined;
+};
+
+// 取消操作
+const handleCancel = () => {
+  confirmDialogVisible.value = false;
+  dialogVisible.value = false;
+  emit('update:modelValue', false);
+  selectedFolderId.value = undefined;
+};
+
+// 下一步(改为直接显示确认对话框,不关闭当前对话框)
+const handleNext = () => {
+  if (!selectedFolderId.value) return;
+  confirmDialogVisible.value = true;
+};
+
+// 返回选择(关闭确认对话框即可)
+const handleBackToSelect = () => {
+  confirmDialogVisible.value = false;
+};
+
+// 确认对话框关闭
+const handleConfirmDialogClose = () => {
+  confirmDialogVisible.value = false;
+};
+
+// 确认归档
+const handleConfirm = () => {
+  if (props.document && selectedFolderId.value && props.projectId) {
+    loading.value = true;
+    emit('confirm', {
+      document: props.document,
+      folderId: selectedFolderId.value,
+      projectId: props.projectId
+    });
+    // 注意:loading 和对话框关闭由父组件通过 closeLoading 和 closeDialog 方法控制
+  }
+};
+
+// 暴露方法供父组件调用(关闭loading)
+defineExpose({
+  closeLoading: () => {
+    loading.value = false;
+  },
+  closeDialog: () => {
+    confirmDialogVisible.value = false;
+    dialogVisible.value = false;
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.archive-content {
+  .document-info {
+    margin-bottom: 20px;
+  }
+
+  .folder-select-section {
+    margin-top: 20px;
+
+    .section-title {
+      font-size: 14px;
+      font-weight: 500;
+      margin-bottom: 12px;
+      color: #303133;
+    }
+
+    .folder-tree {
+      border: 1px solid #dcdfe6;
+      border-radius: 4px;
+      padding: 10px;
+      max-height: 400px;
+      overflow-y: auto;
+
+      :deep(.el-tree-node__content) {
+        height: 32px;
+      }
+
+      .custom-tree-node {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        flex: 1;
+
+        .el-icon {
+          font-size: 16px;
+        }
+
+        .node-label {
+          font-size: 14px;
+        }
+      }
+    }
+  }
+}
+
+.confirm-content {
+  .el-alert {
+    margin-bottom: 20px;
+  }
+
+  .confirm-info {
+    margin-top: 15px;
+  }
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+</style>

+ 18 - 8
src/views/document/folder/document/components/DocumentAuditDialog.vue → src/components/DocumentAuditDialog/index.vue

@@ -24,17 +24,27 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch, nextTick, computed } from 'vue';
+import { ref, reactive, watch, nextTick } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { auditDocument } from '@/api/document/document';
-import { DocumentAuditForm, DocumentVO } from '@/api/document/document/types';
 import type { FormInstance } from 'element-plus';
 import { ElMessage } from 'element-plus';
 
+interface Document {
+    id: number | string;
+    name?: string;
+}
+
+interface AuditData {
+    documentId: number | string;
+    result: number;
+    rejectReason?: string;
+}
+
 interface Props {
     modelValue: boolean;
-    document?: DocumentVO | null;
+    document?: Document | null;
     title?: string;
+    auditApi: (data: AuditData) => Promise<any>;
 }
 
 interface Emits {
@@ -53,7 +63,7 @@ const auditFormRef = ref<FormInstance>();
 
 // 审核表单数据
 const auditForm = ref({
-    id: 0,
+    id: 0 as number | string,
     result: '3', // 默认通过
     reason: ''
 });
@@ -118,12 +128,12 @@ const submitForm = () => {
         if (valid) {
             loading.value = true;
             try {
-                const auditData: DocumentAuditForm = {
+                const auditData: AuditData = {
                     documentId: auditForm.value.id,
                     result: parseInt(auditForm.value.result),
                     rejectReason: auditForm.value.reason
                 };
-                await auditDocument(auditData);
+                await props.auditApi(auditData);
                 ElMessage.success(t('document.document.message.auditSuccess'));
                 dialogVisible.value = false;
                 emit('success');
@@ -144,4 +154,4 @@ const submitForm = () => {
     justify-content: flex-end;
     gap: 10px;
 }
-</style>
+</style>

+ 1 - 1
src/components/FileUpload/index.vue

@@ -144,7 +144,7 @@ const handleBeforeUpload = (file: any) => {
       return false;
     }
   }
-  proxy?.$modal.loading(t('components.imageUpload.uploading'));
+  proxy?.$modal.loading(t('components.fileUpload.uploading'));
   number.value++;
   return true;
 };

+ 2 - 1
src/lang/modules/components/en_US.ts

@@ -18,7 +18,8 @@ export default {
     theFile: 'file',
     delete: 'Delete',
     sizeLimitExceed: 'Upload file size cannot exceed {size}MB!',
-    formatError: 'File format is incorrect, please upload {types} format file!'
+    formatError: 'File format is incorrect, please upload {types} format file!',
+    uploading: 'Uploading file, please wait...'
   },
   imageUpload: {
     uploadImage: 'Upload',

+ 2 - 1
src/lang/modules/components/zh_CN.ts

@@ -18,7 +18,8 @@ export default {
     theFile: '的文件',
     delete: '删除',
     sizeLimitExceed: '上传文件大小不能超过 {size}MB!',
-    formatError: '文件格式不正确, 请上传{types}格式文件!'
+    formatError: '文件格式不正确, 请上传{types}格式文件!',
+    uploading: '正在上传文件,请稍候...'
   },
   imageUpload: {
     uploadImage: '上传',

+ 13 - 3
src/lang/modules/document/document/en_US.ts

@@ -18,7 +18,9 @@ export default {
     confirmSubmit: 'Confirm Submit',
     viewAuditLog: 'View Audit Log',
     archive: 'Archive',
-    confirm: 'Confirm'
+    confirm: 'Confirm',
+    next: 'Next',
+    back: 'Back'
   },
   // Menu
   menu: {
@@ -45,7 +47,13 @@ export default {
     auditLog: 'Audit Log',
     archiveDialog: {
       title: 'Archive Confirmation',
-      confirmMessage: 'Confirm that the file has been successfully archived?'
+      confirmMessage: 'Confirm that the file has been successfully archived?',
+      confirmTitle: 'Confirm Archive',
+      documentName: 'Document Name',
+      currentFolder: 'Current Folder',
+      oldFolder: 'Old Folder',
+      newFolder: 'New Folder',
+      selectFolder: 'Select Target Folder'
     }
   },
   // Form
@@ -126,9 +134,11 @@ export default {
     planType: 'Plan Document Type',
     planTypePlaceholder: 'Please select plan document type',
     file: 'File',
+    actualFile: 'Actual File',
     submitTime: 'Submit Time',
     note: 'Note',
-    notePlaceholder: 'Please enter note'
+    notePlaceholder: 'Please enter note',
+    sendFlag: 'Need Send'
   },
   // Document Validation Rules
   documentRule: {

+ 13 - 3
src/lang/modules/document/document/zh_CN.ts

@@ -18,7 +18,9 @@ export default {
     confirmSubmit: '确认递交',
     viewAuditLog: '查看审核记录',
     archive: '归档',
-    confirm: '确认'
+    confirm: '确认',
+    next: '下一步',
+    back: '返回'
   },
   // 菜单
   menu: {
@@ -45,7 +47,13 @@ export default {
     auditLog: '审核记录',
     archiveDialog: {
       title: '归档确认',
-      confirmMessage: '确认该文件成功归档?'
+      confirmMessage: '确认该文件成功归档?',
+      confirmTitle: '确认归档',
+      documentName: '文件名称',
+      currentFolder: '当前文件夹',
+      oldFolder: '原文件夹',
+      newFolder: '新文件夹',
+      selectFolder: '选择目标文件夹'
     }
   },
   // 表单
@@ -126,9 +134,11 @@ export default {
     planType: '计划文件类型',
     planTypePlaceholder: '请选择计划文件类型',
     file: '文件本体',
+    actualFile: '实际文件',
     submitTime: '递交时间',
     note: '备注',
-    notePlaceholder: '请输入备注'
+    notePlaceholder: '请输入备注',
+    sendFlag: '是否需要寄送'
   },
   // 文档验证规则
   documentRule: {

+ 1 - 1
src/lang/modules/home/taskCenter/en_US.ts

@@ -96,7 +96,7 @@ export default {
             reset: 'Reset'
         },
         table: {
-            id: 'ID',
+            id: 'No.',
             name: 'Name',
             type: 'Type',
             normalDocument: 'Normal Document',

+ 1 - 1
src/lang/modules/home/taskCenter/zh_CN.ts

@@ -93,7 +93,7 @@ export default {
             reset: '重置'
         },
         table: {
-            id: 'ID',
+            id: '序号',
             name: '名称',
             type: '类型',
             normalDocument: '非计划文档',

+ 1 - 1
src/lang/modules/project/management/en_US.ts

@@ -49,7 +49,7 @@ export default {
   },
   // Table Columns
   table: {
-    id: 'ID',
+    id: 'No.',
     code: 'Code',
     name: 'Name',
     icon: 'Icon',

+ 1 - 1
src/lang/modules/search/en_US.ts

@@ -23,7 +23,7 @@ export default {
         download: 'Download'
     },
     table: {
-        id: 'ID',
+        id: 'No.',
         folderName: 'Folder',
         name: 'Document Name',
         type: 'Type',

+ 1 - 1
src/lang/modules/search/zh_CN.ts

@@ -23,7 +23,7 @@ export default {
         download: '下载'
     },
     table: {
-        id: 'ID',
+        id: '序号',
         folderName: '文件夹',
         name: '文档名称',
         type: '类型',

+ 36 - 0
src/lang/modules/setting/agreement/en_US.ts

@@ -0,0 +1,36 @@
+// Agreement Settings Module - English Translation
+export default {
+    // Page Title
+    title: 'Agreement Settings',
+
+    // Tabs
+    tab: {
+        userAgreement: 'User Agreement',
+        privacyAgreement: 'Privacy Agreement'
+    },
+
+    // Language Labels
+    lang: {
+        chinese: 'Chinese',
+        english: 'English'
+    },
+
+    // Buttons
+    button: {
+        save: 'Save'
+    },
+
+    // Messages
+    message: {
+        saveSuccess: 'Saved successfully',
+        saveFailed: 'Save failed',
+        fetchFailed: 'Failed to fetch data',
+        noData: 'No data available'
+    },
+
+    // Editor Placeholders
+    editor: {
+        userAgreementPlaceholder: 'Please enter user agreement content',
+        privacyAgreementPlaceholder: 'Please enter privacy agreement content'
+    }
+};

+ 36 - 0
src/lang/modules/setting/agreement/zh_CN.ts

@@ -0,0 +1,36 @@
+// 协议设置模块 - 中文翻译
+export default {
+    // 页面标题
+    title: '协议设置',
+
+    // 标签页
+    tab: {
+        userAgreement: '用户协议',
+        privacyAgreement: '隐私协议'
+    },
+
+    // 语言标签
+    lang: {
+        chinese: '中文',
+        english: '英文'
+    },
+
+    // 按钮
+    button: {
+        save: '保存'
+    },
+
+    // 消息
+    message: {
+        saveSuccess: '保存成功',
+        saveFailed: '保存失败',
+        fetchFailed: '获取数据失败',
+        noData: '暂无数据'
+    },
+
+    // 编辑器占位符
+    editor: {
+        userAgreementPlaceholder: '请输入用户协议内容',
+        privacyAgreementPlaceholder: '请输入隐私协议内容'
+    }
+};

+ 3 - 1
src/lang/modules/setting/index.ts

@@ -1,8 +1,10 @@
 // 设置管理模块 - 统一导出
 import applet from './applet/zh_CN';
 import ai from './ai/zh_CN';
+import agreement from './agreement/zh_CN';
 
 export default {
   applet,
-  ai
+  ai,
+  agreement
 };

+ 3 - 1
src/lang/modules/setting/index_en.ts

@@ -1,8 +1,10 @@
 // Settings Module - Unified Export (English)
 import applet from './applet/en_US';
 import ai from './ai/en_US';
+import agreement from './agreement/en_US';
 
 export default {
   applet,
-  ai
+  ai,
+  agreement
 };

+ 1 - 1
src/lang/modules/system/tenant/en_US.ts

@@ -26,7 +26,7 @@ export default {
   },
   // 表格列
   table: {
-    id: 'ID',
+    id: 'No.',
     tenantId: 'Company ID',
     contactUserName: 'Contact Person',
     contactPhone: 'Contact Phone',

+ 1 - 1
src/lang/modules/system/tenant/zh_CN.ts

@@ -26,7 +26,7 @@ export default {
   },
   // 表格列
   table: {
-    id: 'id',
+    id: '序号',
     tenantId: '企业编号',
     contactUserName: '联系人',
     contactPhone: '联系电话',

+ 1 - 1
src/lang/modules/system/user/en_US.ts

@@ -104,7 +104,7 @@ export default {
     statusChangeSuccess: '{text} successfully',
     appletStatusEnable: 'Enable',
     appletStatusDisable: 'Disable',
-    appletStatusChangeConfirm: 'Are you sure to {text} applet access for user "{userName}"?',
+    appletStatusChangeConfirm: 'Are you sure to {text} agreement access for user "{userName}"?',
     appletStatusChangeSuccess: '{text} successfully',
     resetPwdTitle: 'Prompt',
     resetPwdText: 'Please enter new password for "{userName}"',

+ 1 - 3
src/layout/components/AppMain.vue

@@ -2,9 +2,7 @@
   <section class="app-main">
     <router-view v-slot="{ Component, route }">
       <transition :enter-active-class="animate" mode="out-in">
-        <keep-alive :include="tagsViewStore.cachedViews">
-          <component :is="Component" v-if="!route.meta.link" :key="route.path" />
-        </keep-alive>
+        <component :is="Component" v-if="!route.meta.link" :key="route.path" />
       </transition>
     </router-view>
     <iframe-toggle />

+ 1 - 1
src/permission.ts

@@ -23,7 +23,7 @@ router.beforeEach(async (to, from, next) => {
     to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
     /* has token*/
     if (to.path === '/login') {
-      next({ path: '/' });
+      next({ path: '/home/workbench' });
       NProgress.done();
     } else if (isWhiteList(to.path)) {
       next();

+ 1 - 1
src/router/index.ts

@@ -68,7 +68,7 @@ export const constantRoutes: RouteRecordRaw[] = [
   {
     path: '',
     component: Layout,
-    redirect: '/index',
+    redirect: '/home/workbench',
     children: [
       {
         path: '/index',

+ 38 - 8
src/views/document/folder/document/DocumentList.vue

@@ -40,7 +40,13 @@
           <span v-else>-</span>
         </template>
       </el-table-column>
-      <el-table-column prop="planType" :label="t('document.document.documentList.planDocumentType')" width="120" align="center">
+      <el-table-column 
+        prop="planType" 
+        :label="t('document.document.documentList.planDocumentType')" 
+        min-width="120" 
+        align="center"
+        show-overflow-tooltip
+      >
         <template #default="scope">
           <dict-tag v-if="scope.row.planType" :options="plan_document_type" :value="scope.row.planType" />
           <span v-else>-</span>
@@ -208,13 +214,21 @@
     />
 
     <!-- 归档确认对话框 -->
-    <ArchiveConfirmDialog v-model="archiveDialog.visible" :document="archiveDialog.currentDocument" @confirm="handleArchiveConfirm" />
+    <ArchiveConfirmDialog 
+      ref="archiveConfirmDialogRef"
+      v-model="archiveDialog.visible" 
+      :document="archiveDialog.currentDocument" 
+      :tree-data="treeData"
+      :project-id="projectId"
+      @confirm="handleArchiveConfirm" 
+    />
 
     <!-- 审核文档对话框 -->
     <DocumentAuditDialog
       v-model="auditDialog.visible"
       :document="auditDialog.document"
       :title="t('document.document.dialog.auditDocument')"
+      :audit-api="auditDocument"
       @success="getDocumentList"
     />
   </div>
@@ -243,14 +257,15 @@ import { parseTime } from '@/utils/ruoyi';
 import { parseI18nName } from '@/utils/i18n';
 import fileUpload from '@/components/FileUpload/index.vue';
 import AuditLogDialog from '@/components/AuditLogDialog/index.vue';
-import { listDocumentAuditLog } from '@/api/document/document';
-import ArchiveConfirmDialog from './components/ArchiveConfirmDialog.vue';
-import DocumentAuditDialog from './components/DocumentAuditDialog.vue';
+import { listDocumentAuditLog, auditDocument } from '@/api/document/document';
+import ArchiveConfirmDialog from '@/components/ArchiveConfirmDialog/index.vue';
+import DocumentAuditDialog from '@/components/DocumentAuditDialog/index.vue';
 import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
 
 interface Props {
   selectedFolder: FolderListVO | null;
   treeData: FolderListVO[];
+  projectId?: number | string;
 }
 
 const props = defineProps<Props>();
@@ -340,6 +355,7 @@ const archiveDialog = reactive({
   visible: false,
   currentDocument: null as DocumentVO | null
 });
+const archiveConfirmDialogRef = ref<InstanceType<typeof ArchiveConfirmDialog>>();
 
 // 审核对话框状态
 const auditDialog = reactive({
@@ -487,17 +503,22 @@ const handleArchive = (row: DocumentVO) => {
 };
 
 // 处理归档确认
-const handleArchiveConfirm = async (row: DocumentVO) => {
+const handleArchiveConfirm = async (data: { document: DocumentVO; folderId: number | string; projectId: number | string }) => {
   try {
-    await filingDocument(row.id);
+    // 调用归档接口,传入新的 folderId
+    await filingDocument(data.document.id, data.folderId);
     ElMessage.success(t('document.document.message.archiveSuccess'));
+    // 关闭所有对话框
+    archiveConfirmDialogRef.value?.closeDialog();
     archiveDialog.visible = false;
     await getDocumentList();
   } catch (error) {
     console.error(t('document.document.message.archiveFailed'), error);
     ElMessage.error(t('document.document.message.archiveFailed'));
+    // 关闭loading
+    archiveConfirmDialogRef.value?.closeLoading();
   }
-};
+};;
 
 // 标识文档
 const handleMark = (row: DocumentVO) => {
@@ -638,6 +659,15 @@ defineExpose({
       }
     }
   }
+
+  // 确保表格单元格内容不换行
+  :deep(.el-table__cell) {
+    .cell {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
 }
 
 .show-overflow-tooltip {

+ 2 - 1
src/views/document/folder/document/FolderTree.vue

@@ -467,7 +467,8 @@ const handleScroll = () => {
 
 // 暴露方法给父组件
 defineExpose({
-  getList
+  getList,
+  treeData
 });
 
 // 初始化

+ 0 - 100
src/views/document/folder/document/components/ArchiveConfirmDialog.vue

@@ -1,100 +0,0 @@
-<template>
-  <el-dialog v-model="dialogVisible" :title="t('document.document.archiveDialog.title')" width="500px" append-to-body>
-    <div class="archive-confirm-content">
-      <el-alert :title="t('document.document.archiveDialog.confirmMessage')" type="warning" show-icon
-        :closable="false" />
-      <div class="document-info" v-if="document">
-        <el-descriptions :column="1" size="small" border>
-          <el-descriptions-item :label="t('document.document.documentList.name')">
-            {{ document.name }}
-          </el-descriptions-item>
-          <el-descriptions-item :label="t('document.document.documentList.specification')"
-            v-if="document.specification">
-            {{ document.specification }}
-          </el-descriptions-item>
-          <el-descriptions-item :label="t('document.document.documentList.fileName')" v-if="document.fileName">
-            {{ document.fileName }}
-          </el-descriptions-item>
-        </el-descriptions>
-      </div>
-    </div>
-
-    <template #footer>
-      <div class="dialog-footer">
-        <el-button @click="handleCancel">{{ t('document.document.button.cancel') }}</el-button>
-        <el-button type="primary" @click="handleConfirm" :loading="loading">
-          {{ t('document.document.button.confirm') }}
-        </el-button>
-      </div>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, watch } from 'vue';
-import { useI18n } from 'vue-i18n';
-import { DocumentVO } from '@/api/document/document/types';
-
-interface Props {
-  modelValue: boolean;
-  document?: DocumentVO | null;
-}
-
-interface Emits {
-  (e: 'update:modelValue', value: boolean): void;
-  (e: 'confirm', document: DocumentVO): void;
-}
-
-const props = defineProps<Props>();
-const emit = defineEmits<Emits>();
-
-const { t } = useI18n();
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-
-// 监听modelValue变化
-watch(
-  () => props.modelValue,
-  (val) => {
-    dialogVisible.value = val;
-  }
-);
-
-// 监听dialogVisible变化
-watch(dialogVisible, (val) => {
-  emit('update:modelValue', val);
-});
-
-// 取消操作
-const handleCancel = () => {
-  dialogVisible.value = false;
-};
-
-// 确认操作
-const handleConfirm = () => {
-  if (props.document) {
-    loading.value = true;
-    emit('confirm', props.document);
-    // 注意:这里不需要手动关闭弹窗,应该在父组件处理完归档逻辑后再关闭
-  }
-};
-</script>
-
-<style scoped lang="scss">
-.archive-confirm-content {
-  .el-alert {
-    margin-bottom: 20px;
-  }
-
-  .document-info {
-    margin-top: 15px;
-  }
-}
-
-.dialog-footer {
-  display: flex;
-  justify-content: flex-end;
-  gap: 10px;
-}
-</style>

+ 25 - 4
src/views/document/folder/document/index.vue

@@ -20,7 +20,7 @@
 
         <!-- 文档列表组件 -->
         <div class="content-container">
-          <DocumentList ref="documentListRef" :selected-folder="selectedFolder" :tree-data="treeData" />
+          <DocumentList ref="documentListRef" :selected-folder="selectedFolder" :tree-data="treeData" :project-id="projectId" />
         </div>
       </div>
     </el-card>
@@ -83,7 +83,7 @@
           </el-select>
         </el-form-item>
 
-        <el-form-item :label="t('document.document.documentForm.file')" :prop="documentForm.type === 0 ? 'ossId' : ''">
+        <el-form-item v-if="documentForm.type === 0" :label="t('document.document.documentForm.actualFile')" prop="ossId">
           <fileUpload v-model="uploadedFileId" :limit="1" />
         </el-form-item>
 
@@ -91,6 +91,14 @@
           <el-input v-model="documentForm.submitTime" disabled />
         </el-form-item>
 
+        <el-form-item :label="t('document.document.documentForm.sendFlag')" prop="sendFlag">
+          <el-switch
+            v-model="documentForm.sendFlag"
+            :active-value="true"
+            :inactive-value="false"
+          />
+        </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>
@@ -174,7 +182,8 @@ const initDocumentFormData: DocumentForm = {
   ossId: undefined,
   submitTime: undefined,
   projectId: props.projectId,
-  note: ''
+  note: '',
+  sendFlag: false
 };
 
 // 文件上传的ossId
@@ -208,6 +217,8 @@ const handleFolderClick = (folder: FolderListVO) => {
   } else {
     selectedFolder.value = null;
   }
+  // 更新树数据
+  handleTreeRefresh();
 };
 
 // 处理添加文档
@@ -329,7 +340,8 @@ const submitDocumentForm = () => {
           submitTime: documentForm.value.submitTime || (hasUploadedFile ? new Date().toISOString() : ''),
           projectId: documentForm.value.projectId || props.projectId,
           status: hasUploadedFile ? 1 : 0,
-          note: documentForm.value.note || ''
+          note: documentForm.value.note || '',
+          sendFlag: documentForm.value.sendFlag !== undefined ? documentForm.value.sendFlag : false
         };
 
         await addDocument(submitData);
@@ -345,6 +357,15 @@ const submitDocumentForm = () => {
     }
   });
 };
+
+// 初始化时更新树数据
+watch(
+  () => folderTreeRef.value,
+  () => {
+    handleTreeRefresh();
+  },
+  { immediate: true }
+);
 </script>
 
 <style scoped lang="scss">

+ 0 - 1
src/views/document/folder/待优化.md

@@ -1 +0,0 @@
-组件拆分

+ 354 - 4
src/views/home/taskCenter/filing/index.vue

@@ -1,7 +1,357 @@
-<script setup lang="ts"></script>
-
 <template>
-  <div>文件归档任务</div>
+  <div class="filing-task-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="flex justify-between items-center">
+          <span class="text-lg font-bold">文件归档任务</span>
+        </div>
+      </template>
+
+      <!-- 搜索栏 -->
+      <el-form :model="queryParams" :inline="true" class="search-form">
+        <el-form-item label="项目编号">
+          <el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable style="width: 240px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="项目名称">
+          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 240px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="中心名称">
+          <el-input v-model="queryParams.centerName" placeholder="请输入中心名称" clearable style="width: 240px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="名称">
+          <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 240px"
+            @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 任务列表 -->
+      <el-table v-loading="loading" :data="taskList" border style="margin-top: 10px">
+        <el-table-column prop="id" label="序号" width="80" align="center" />
+        <el-table-column prop="name" label="名称" min-width="150" show-overflow-tooltip>
+          <template #default="scope">
+            <el-badge v-if="scope.row.status === 3" is-dot class="status-badge" />
+            <span>{{ scope.row.name }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="type" label="类型" min-width="120" show-overflow-tooltip>
+          <template #default="scope">
+            <span v-if="scope.row.type === 0">非计划文档</span>
+            <span v-else-if="scope.row.type === 1">计划文档</span>
+            <span v-else>{{ scope.row.type }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="documentType" label="计划文档类型" width="120" align="center">
+          <template #default="scope">
+            <dict-tag v-if="scope.row.documentType" :options="plan_document_type" :value="scope.row.documentType" />
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="状态" width="120" align="center">
+          <template #default="scope">
+            <DocumentStatusTag :status="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column prop="folderName" label="所属文件夹" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="submitter" label="递交人" width="120" align="center" />
+        <el-table-column prop="deadline" label="截止时间" width="160" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.deadline">{{ parseTime(scope.row.deadline) }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="submitTime" label="递交时间" width="160" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.submitTime">{{ scope.row.submitTime }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" width="160" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.createTime">{{ scope.row.createTime }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <!-- 操作列 -->
+        <el-table-column label="操作" width="150" align="center" fixed="right">
+          <template #default="scope">
+            <el-button 
+              v-if="scope.row.status === 3" 
+              v-hasPermi="['taskCenter:filing:filing']"
+              type="primary" 
+              link 
+              @click="handleArchive(scope.row)" 
+              title="归档">
+              <el-icon>
+                <FolderAdd />
+              </el-icon>
+            </el-button>
+            <el-button v-if="scope.row.ossId" type="primary" link @click="handleDownload(scope.row)" title="下载">
+              <el-icon>
+                <Download />
+              </el-icon>
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+        :total="total" @pagination="getTaskList" />
+    </el-card>
+
+    <!-- 归档确认对话框 -->
+    <ArchiveConfirmDialog 
+      ref="archiveConfirmDialogRef"
+      v-model="archiveDialog.visible" 
+      :document="archiveDialog.currentDocument" 
+      :tree-data="treeData"
+      :project-id="archiveDialog.projectId"
+      @confirm="handleArchiveConfirm" 
+    />
+  </div>
 </template>
 
-<style scoped lang="scss"></style>
+<script setup lang="ts">
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
+import { ElMessage } from 'element-plus';
+import { listFilingTasks, filingDocument } from '@/api/home/taskCenter/filing';
+import { FilingTaskVO, FilingTaskFilingForm } from '@/api/home/taskCenter/filing/types';
+import { listFolderOnProject } from '@/api/document/folder';
+import { FolderListVO } from '@/api/document/folder/types';
+import { downloadDocumentFile } from '@/api/document/document';
+import { FolderAdd, Download } from '@element-plus/icons-vue';
+import DictTag from '@/components/DictTag/index.vue';
+import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
+import ArchiveConfirmDialog from '@/components/ArchiveConfirmDialog/index.vue';
+
+const { proxy } = getCurrentInstance() as any;
+const { plan_document_type } = proxy.useDict('plan_document_type');
+
+const loading = ref(false);
+
+// 查询参数
+const queryParams = reactive({
+  name: '',
+  projectCode: '',
+  projectName: '',
+  centerName: '',
+  pageNum: 1,
+  pageSize: 10
+});
+
+// 任务列表数据
+const taskList = ref<FilingTaskVO[]>([]);
+const total = ref(0);
+
+// 归档对话框
+const archiveDialog = reactive({
+  visible: false,
+  currentDocument: null as any,
+  projectId: undefined as number | string | undefined
+});
+const archiveConfirmDialogRef = ref<InstanceType<typeof ArchiveConfirmDialog>>();
+
+// 文件夹树数据
+const treeData = ref<FolderListVO[]>([]);
+
+/**
+ * 解析时间格式
+ * @param time 时间字符串
+ * @param pattern 格式模式
+ */
+const parseTime = (time: string, pattern = '{y}-{m}-{d}') => {
+  if (!time) return '';
+  const date = new Date(time);
+  const formatObj: any = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  };
+  const time_str = pattern.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key];
+    if (key === 'a') return ['日', '一', '二', '三', '四', '五', '六'][value];
+    if (result.length > 0 && value < 10) {
+      value = '0' + value;
+    }
+    return String(value);
+  });
+  return time_str;
+};
+
+/**
+ * 获取任务列表
+ */
+const getTaskList = async () => {
+  try {
+    loading.value = true;
+    const res = await listFilingTasks(queryParams);
+    if (res.code === 200) {
+      taskList.value = res.rows || [];
+      total.value = res.total || 0;
+    } else {
+      ElMessage.error(res.msg || '获取任务列表失败');
+    }
+  } catch (error) {
+    console.error('获取任务列表失败:', error);
+    ElMessage.error('获取任务列表失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+/**
+ * 处理搜索
+ */
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getTaskList();
+};
+
+/**
+ * 重置搜索
+ */
+const resetQuery = () => {
+  queryParams.name = '';
+  queryParams.projectCode = '';
+  queryParams.projectName = '';
+  queryParams.centerName = '';
+  handleQuery();
+};
+
+/**
+ * 获取文件夹树数据
+ */
+const getFolderTree = async (projectId: number | string) => {
+  try {
+    const res = await listFolderOnProject(projectId);
+    // 检查响应格式,可能是 res.data 或 res.rows
+    if (res.code === 200) {
+      treeData.value = res.data || res.rows || [];
+      console.log('获取到的树数据:', treeData.value);
+    } else {
+      ElMessage.error(res.msg || '获取文件夹列表失败');
+      treeData.value = [];
+    }
+  } catch (error) {
+    console.error('获取文件夹列表失败:', error);
+    ElMessage.error('获取文件夹列表失败');
+    treeData.value = [];
+  }
+};
+
+/**
+ * 处理归档
+ */
+const handleArchive = async (row: FilingTaskVO) => {
+  // 从任务数据中获取项目ID
+  if (!row.projectId) {
+    ElMessage.warning('无法获取项目信息,请刷新后重试');
+    return;
+  }
+  
+  // 获取文件夹树数据
+  await getFolderTree(row.projectId);
+  
+  // 设置归档对话框数据
+  archiveDialog.currentDocument = {
+    id: row.id,
+    name: row.name,
+    folderId: row.folderId
+  };
+  archiveDialog.projectId = row.projectId;
+  archiveDialog.visible = true;
+};
+
+/**
+ * 处理归档确认
+ */
+const handleArchiveConfirm = async (data: { document: any; folderId: number | string; projectId: number | string }) => {
+  try {
+    const filingData: FilingTaskFilingForm = {
+      documentId: data.document.id,
+      folderId: data.folderId,
+      projectId: data.projectId
+    };
+    await filingDocument(filingData);
+    ElMessage.success('归档成功');
+    // 关闭所有对话框
+    archiveConfirmDialogRef.value?.closeDialog();
+    archiveDialog.visible = false;
+    // 刷新任务列表
+    await getTaskList();
+  } catch (error) {
+    console.error('归档失败:', error);
+    ElMessage.error('归档失败');
+    // 关闭loading
+    archiveConfirmDialogRef.value?.closeLoading();
+  }
+};
+
+/**
+ * 下载文件
+ */
+const handleDownload = async (row: FilingTaskVO) => {
+  if (!row.ossId) {
+    ElMessage.warning('暂无文件可下载');
+    return;
+  }
+
+  await downloadDocumentFile(row.ossId, row.name);
+};
+
+onMounted(() => {
+  getTaskList();
+});
+</script>
+
+<style scoped lang="scss">
+.filing-task-container {
+  padding: 20px;
+
+  .flex {
+    display: flex;
+  }
+
+  .justify-between {
+    justify-content: space-between;
+  }
+
+  .items-center {
+    align-items: center;
+  }
+
+  .text-lg {
+    font-size: 1.125rem;
+  }
+
+  .font-bold {
+    font-weight: 700;
+  }
+
+  .search-form {
+    margin-bottom: 15px;
+  }
+
+  :deep(.el-table) {
+    .cell {
+      white-space: nowrap;
+    }
+  }
+
+  :deep(.status-badge) {
+    margin-right: 8px;
+    vertical-align: middle;
+  }
+}
+</style>

+ 45 - 4
src/views/home/taskCenter/submission/index.vue

@@ -40,7 +40,7 @@
 
       <!-- 任务列表 -->
       <el-table v-loading="loading" :data="taskList" border style="margin-top: 10px">
-        <el-table-column prop="id" label="ID" width="80" align="center" />
+        <el-table-column prop="id" label="序号" width="80" align="center" />
         <el-table-column prop="name" label="名称" min-width="150" show-overflow-tooltip>
           <template #default="scope">
             <el-badge v-if="scope.row.status === 0 || scope.row.status === 2" is-dot class="badge-dot" />
@@ -89,8 +89,15 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
+        <el-table-column prop="sendStatus" label="寄送状态" width="100" align="center">
+          <template #default="scope">
+            <span v-if="!scope.row.sendFlag">-</span>
+            <el-tag v-else-if="scope.row.sendStatus" type="success">已寄送</el-tag>
+            <el-tag v-else type="warning">未寄送</el-tag>
+          </template>
+        </el-table-column>
         <!-- 操作列 -->
-        <el-table-column label="操作" width="150" align="center" fixed="right">
+        <el-table-column label="操作" width="200" align="center" fixed="right">
           <template #default="scope">
             <el-button v-if="scope.row.status === 0 || scope.row.status === 2" type="primary" link
               @click="handleTaskSubmit(scope.row)" title="递交">
@@ -104,6 +111,17 @@
                 <DocumentCopy />
               </el-icon>
             </el-button>
+            <el-button 
+              v-if="scope.row.sendFlag && !scope.row.sendStatus" 
+              v-hasPermi="['taskCenter:submission:send']"
+              type="primary" 
+              link
+              @click="handleSend(scope.row)" 
+              title="寄送">
+              <el-icon>
+                <Promotion />
+              </el-icon>
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -142,12 +160,12 @@
 import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import type { FormInstance } from 'element-plus';
-import { listSubmissionTasks, submitDocument, listSubmissionAuditLog } from '@/api/home/taskCenter/submission';
+import { listSubmissionTasks, submitDocument, listSubmissionAuditLog, sendDocument } from '@/api/home/taskCenter/submission';
 import fileUpload from '@/components/FileUpload/index.vue';
 import DictTag from '@/components/DictTag/index.vue';
 import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
 import AuditLogDialog from '@/components/AuditLogDialog/index.vue';
-import { Upload, DocumentCopy } from '@element-plus/icons-vue';
+import { Upload, DocumentCopy, Promotion } from '@element-plus/icons-vue';
 import { SubmissionTaskVO, SubmissionTaskSubmitForm } from '@/api/home/taskCenter/submission/types';
 
 const { proxy } = getCurrentInstance() as any;
@@ -387,6 +405,29 @@ const submitSubmitForm = () => {
   });
 };
 
+/**
+ * 处理寄送
+ */
+const handleSend = (row: SubmissionTaskVO) => {
+  ElMessageBox.confirm('确认已完成寄送?', '提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(async () => {
+    try {
+      await sendDocument(row.id);
+      ElMessage.success('寄送确认成功');
+      // 刷新任务列表
+      await getTaskList();
+    } catch (error) {
+      console.error('寄送确认失败:', error);
+      ElMessage.error('寄送确认失败');
+    }
+  }).catch(() => {
+    // 用户取消操作
+  });
+};
+
 onMounted(() => {
   getTaskList();
 });

+ 1 - 1
src/views/home/workbench/index.vue

@@ -143,7 +143,7 @@ onUnmounted(() => {
 
         <!-- 项目表格 -->
         <el-table v-loading="loading" :data="projectList" border style="width: 100%; margin-top: 15px">
-          <el-table-column prop="id" label="ID" width="80" align="center" />
+          <el-table-column prop="id" label="序号" width="80" align="center" />
           <el-table-column prop="code" label="项目编号" min-width="150" />
           <el-table-column prop="name" label="项目名称" min-width="180" show-overflow-tooltip />
 

+ 9 - 0
src/views/search/index.vue

@@ -338,5 +338,14 @@ onMounted(() => {
       white-space: nowrap;
     }
   }
+
+  // 确保表格单元格内容不换行
+  :deep(.el-table__cell) {
+    .cell {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
 }
 </style>

+ 35 - 35
src/views/setting/applet/index.vue → src/views/setting/agreement/index.vue

@@ -3,47 +3,47 @@
     <el-card v-loading="loading" shadow="never">
       <template #header>
         <div class="card-header">
-          <span class="text-lg font-semibold">{{ t('applet.title') }}</span>
+          <span class="text-lg font-semibold">{{ t('agreement.title') }}</span>
         </div>
       </template>
 
-      <el-tabs v-if="appletData" v-model="activeTab" type="border-card">
-        <el-tab-pane :label="t('applet.tab.userAgreement')" name="userAgreement">
+      <el-tabs v-if="agreementData" v-model="activeTab" type="border-card">
+        <el-tab-pane :label="t('agreement.tab.userAgreement')" name="userAgreement">
           <el-tabs v-model="userAgreementLang" type="card" class="mb-4">
-            <el-tab-pane :label="t('applet.lang.chinese')" name="zh_CN">
+            <el-tab-pane :label="t('agreement.lang.chinese')" name="zh_CN">
               <editor v-model="userAgreementData.zh_CN" :min-height="400"/>
             </el-tab-pane>
-            <el-tab-pane :label="t('applet.lang.english')" name="en_US">
+            <el-tab-pane :label="t('agreement.lang.english')" name="en_US">
               <editor v-model="userAgreementData.en_US" :min-height="400"/>
             </el-tab-pane>
           </el-tabs>
           <div class="mt-4">
-            <el-button type="primary" :loading="saveLoading" @click="handleSave">{{ t('applet.button.save') }}</el-button>
+            <el-button type="primary" :loading="saveLoading" @click="handleSave">{{ t('agreement.button.save') }}</el-button>
           </div>
         </el-tab-pane>
-        <el-tab-pane :label="t('applet.tab.privacyAgreement')" name="privacyAgreement">
+        <el-tab-pane :label="t('agreement.tab.privacyAgreement')" name="privacyAgreement">
           <el-tabs v-model="privacyAgreementLang" type="card" class="mb-4">
-            <el-tab-pane :label="t('applet.lang.chinese')" name="zh_CN">
+            <el-tab-pane :label="t('agreement.lang.chinese')" name="zh_CN">
               <editor v-model="privacyAgreementData.zh_CN" :min-height="400"/>
             </el-tab-pane>
-            <el-tab-pane :label="t('applet.lang.english')" name="en_US">
+            <el-tab-pane :label="t('agreement.lang.english')" name="en_US">
               <editor v-model="privacyAgreementData.en_US" :min-height="400"/>
             </el-tab-pane>
           </el-tabs>
           <div class="mt-4">
-            <el-button type="primary" :loading="saveLoading" @click="handleSave">{{ t('applet.button.save') }}</el-button>
+            <el-button type="primary" :loading="saveLoading" @click="handleSave">{{ t('agreement.button.save') }}</el-button>
           </div>
         </el-tab-pane>
       </el-tabs>
 
-      <el-empty v-else :description="t('applet.message.noData')" />
+      <el-empty v-else :description="t('agreement.message.noData')" />
     </el-card>
   </div>
 </template>
 
-<script setup name="Applet" lang="ts">
-import { getApplet, updateApplet } from '@/api/setting/applet';
-import { AppletVO } from '@/api/setting/applet/types';
+<script setup name="Agreement" lang="ts">
+import { getAgreement, updateAgreement } from '@/api/setting/agreement';
+import { AgreementVO } from '@/api/setting/agreement/types';
 import { useI18n } from 'vue-i18n';
 
 const { t } = useI18n();
@@ -54,7 +54,7 @@ const saveLoading = ref(false);
 const activeTab = ref('userAgreement');
 const userAgreementLang = ref('zh_CN');
 const privacyAgreementLang = ref('zh_CN');
-const appletData = ref<AppletVO | null>(null);
+const agreementData = ref<AgreementVO | null>(null);
 
 // 用户协议数据(中英文)
 const userAgreementData = ref({
@@ -79,7 +79,7 @@ const parseAgreementData = (data: string) => {
   if (!data) {
     return { zh_CN: '', en_US: '' };
   }
-  
+
   try {
     // 尝试BASE64解码
     const decoded = decodeURIComponent(escape(atob(data)));
@@ -106,24 +106,24 @@ const parseAgreementData = (data: string) => {
   }
 };
 
-/** 获取小程序设置数据 */
+/** 获取协议设置数据 */
 const fetchData = async () => {
   loading.value = true;
   try {
-    const res = await getApplet(1);
-    appletData.value = res.data;
-    
+    const res = await getAgreement(1);
+    agreementData.value = res.data;
+
     // 解析用户协议
-    if (appletData.value?.userAgreement) {
-      userAgreementData.value = parseAgreementData(appletData.value.userAgreement);
+    if (agreementData.value?.userAgreement) {
+      userAgreementData.value = parseAgreementData(agreementData.value.userAgreement);
     }
-    
+
     // 解析隐私协议
-    if (appletData.value?.privacyAgreement) {
-      privacyAgreementData.value = parseAgreementData(appletData.value.privacyAgreement);
+    if (agreementData.value?.privacyAgreement) {
+      privacyAgreementData.value = parseAgreementData(agreementData.value.privacyAgreement);
     }
   } catch (error) {
-    console.error(t('applet.message.fetchFailed'), error);
+    console.error(t('agreement.message.fetchFailed'), error);
   } finally {
     loading.value = false;
   }
@@ -131,8 +131,8 @@ const fetchData = async () => {
 
 /** 保存数据 */
 const handleSave = async () => {
-  if (!appletData.value) return;
-  
+  if (!agreementData.value) return;
+
   saveLoading.value = true;
   try {
     // 将中英文协议整理成JSON格式,然后进行BASE64编码
@@ -140,23 +140,23 @@ const handleSave = async () => {
       zh_CN: userAgreementData.value.zh_CN || '',
       en_US: userAgreementData.value.en_US || ''
     });
-    
+
     const privacyAgreementJson = JSON.stringify({
       zh_CN: privacyAgreementData.value.zh_CN || '',
       en_US: privacyAgreementData.value.en_US || ''
     });
-    
+
     const encodedData = {
-      ...appletData.value,
+      ...agreementData.value,
       id: 1, // 默认设置为1
       userAgreement: btoa(unescape(encodeURIComponent(userAgreementJson))),
       privacyAgreement: btoa(unescape(encodeURIComponent(privacyAgreementJson)))
     };
-    
-    await updateApplet(encodedData);
-    proxy?.$modal.msgSuccess(t('applet.message.saveSuccess'));
+
+    await updateAgreement(encodedData);
+    proxy?.$modal.msgSuccess(t('agreement.message.saveSuccess'));
   } catch (error) {
-    console.error(t('applet.message.saveFailed'), error);
+    console.error(t('agreement.message.saveFailed'), error);
   } finally {
     saveLoading.value = false;
   }

+ 1 - 1
src/views/system/client/index.vue

@@ -47,7 +47,7 @@
 
       <el-table v-loading="loading" :data="clientList" border @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column v-if="true" label="id" align="center" prop="id" />
+        <el-table-column v-if="true" label="序号" align="center" prop="id" />
         <el-table-column label="客户端id" align="center" prop="clientId" />
         <el-table-column label="客户端key" align="center" prop="clientKey" />
         <el-table-column label="客户端秘钥" align="center" prop="clientSecret" />

+ 1 - 1
src/views/workflow/task/myDocument.vue

@@ -45,7 +45,7 @@
           <el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
             <el-table-column type="selection" width="55" align="center" />
             <el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
-            <el-table-column v-if="false" align="center" prop="id" label="id"></el-table-column>
+            <el-table-column v-if="false" align="center" prop="id" label="序号"></el-table-column>
             <el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"> </el-table-column>
             <el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
             <el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>