Procházet zdrojové kódy

接口基本对接完成

Huanyi před 3 měsíci
rodič
revize
ac519fc914

+ 0 - 5
App.vue

@@ -7,11 +7,6 @@ const userStore = useUserStore()
 const localeStore = useLocaleStore()
 
 onLaunch(() => {
-  // 初始化全局数据
-  if (!getApp().globalData) {
-    getApp().globalData = {}
-  }
-  
   // 初始化语言设置
   localeStore.initLocale()
 })

+ 91 - 0
apis/auth.js

@@ -77,3 +77,94 @@ export const getTaskDocuments = (params) => {
     data: params
   })
 }
+
+/**
+ * 获取驳回理由
+ * @param {Number} documentId - 文档ID
+ * @returns {Promise}
+ */
+export const getRejection = (documentId) => {
+  return request({
+    url: '/applet/myTask/getRejection',
+    method: 'GET',
+    data: { documentId }
+  })
+}
+
+/**
+ * 修改头像
+ * @param {Object} data - 头像数据
+ * @param {String} data.avatar - 头像OSS ID
+ * @returns {Promise}
+ */
+export const updateAvatar = (data) => {
+  return request({
+    url: '/applet/my/info/edit/avatar',
+    method: 'PUT',
+    data
+  })
+}
+
+/**
+ * 修改昵称
+ * @param {Object} data - 昵称数据
+ * @param {String} data.nickname - 昵称
+ * @returns {Promise}
+ */
+export const updateNickname = (data) => {
+  return request({
+    url: '/applet/my/info/edit/nickname',
+    method: 'PUT',
+    data
+  })
+}
+
+/**
+ * 修改性别
+ * @param {Object} data - 性别数据
+ * @param {String} data.gender - 性别字典值
+ * @returns {Promise}
+ */
+export const updateGender = (data) => {
+  return request({
+    url: '/applet/my/info/edit/gender',
+    method: 'PUT',
+    data
+  })
+}
+
+/**
+ * 上传文件到OSS
+ * @param {String} filePath - 文件路径
+ * @returns {Promise}
+ */
+export const uploadToOss = (filePath) => {
+  return new Promise((resolve, reject) => {
+    const token = uni.getStorageSync('token') || ''
+    const language = uni.getStorageSync('locale') || 'zh-CN'
+    const CLIENT_ID = '2f847927afb2b3ebeefc870c13d623f2'
+    const BASE_URL = 'http://192.168.1.118:8080'
+    
+    uni.uploadFile({
+      url: BASE_URL + '/common/resource/oss/upload',
+      filePath: filePath,
+      name: 'file',
+      header: {
+        'Content-Language': language.replace('-', '_'),
+        'clientid': CLIENT_ID,
+        'Authorization': 'Bearer ' + token
+      },
+      success: (res) => {
+        try {
+          const data = JSON.parse(res.data)
+          resolve(data)
+        } catch (error) {
+          reject(error)
+        }
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}

+ 94 - 0
apis/scan.js

@@ -30,3 +30,97 @@ export const scanUploadBatch = (data) => {
     data
   })
 }
+
+/**
+ * 获取待递交文件列表
+ * @param {Object} params - 查询参数
+ * @param {String} params.name - 文件名(可选)
+ * @param {Number} params.pageNum - 页码
+ * @param {Number} params.pageSize - 每页数量
+ * @returns {Promise}
+ */
+export const getToSubmitList = (params) => {
+  return request({
+    url: '/applet/scan/listToSubmit',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 提交扫描文件到指定文档
+ * @param {Object} data - 提交数据
+ * @param {Number} data.documentId - 文档ID
+ * @param {Array<String>} data.fileBase64List - base64格式的文件列表
+ * @returns {Promise}
+ */
+export const uploadOnSubmit = (data) => {
+  return request({
+    url: '/applet/scan/uploadOnSubmit',
+    method: 'POST',
+    data
+  })
+}
+
+/**
+ * 获取项目列表
+ * @param {Object} params - 查询参数
+ * @param {String} params.code - 项目编号(可选)
+ * @param {String} params.name - 项目名称(可选)
+ * @param {Number} params.pageNum - 页码
+ * @param {Number} params.pageSize - 每页数量
+ * @returns {Promise}
+ */
+export const getProjectList = (params) => {
+  return request({
+    url: '/applet/scan/listProject',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 获取文件夹列表(树状结构)
+ * @param {Object} params - 查询参数
+ * @param {Number} params.projectId - 项目ID
+ * @returns {Promise}
+ */
+export const getFolderList = (params) => {
+  return request({
+    url: '/applet/scan/listFolder',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 获取文件夹权限
+ * @param {Object} params - 查询参数
+ * @param {Number} params.projectId - 项目ID
+ * @returns {Promise}
+ */
+export const getFolderPermission = (params) => {
+  return request({
+    url: '/applet/scan/listFolderPermission',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 上传新文件
+ * @param {Object} data - 上传数据
+ * @param {Number} data.folderId - 文件夹ID
+ * @param {String} data.fileName - 文件名
+ * @param {Number} data.projectId - 项目ID
+ * @param {String} data.note - 备注
+ * @param {Array<String>} data.files - 文件列表(base64格式)
+ * @returns {Promise}
+ */
+export const uploadNew = (data) => {
+  return request({
+    url: '/applet/scan/uploadNew',
+    method: 'POST',
+    data
+  })
+}

+ 321 - 0
components/TreeView/index.vue

@@ -0,0 +1,321 @@
+<template>
+  <view class="tree-view">
+    <view
+      v-for="(item, index) in flattenedTree"
+      :key="item.id"
+      class="tree-item"
+      :style="{ paddingLeft: (item.level * 40 + 24) + 'rpx' }"
+    >
+      <!-- 展开/收起图标 -->
+      <view class="expand-icon" @click="toggleExpand(item)" v-if="item.hasChildren">
+        <text class="icon-text">{{ item.expanded ? '▼' : '▶' }}</text>
+      </view>
+      <view class="expand-icon placeholder" v-else></view>
+      
+      <!-- 文件夹图标 -->
+      <view class="folder-icon-wrapper">
+        <image 
+          :src="getIcon(item.type)" 
+          mode="aspectFit" 
+          class="folder-icon" 
+        />
+      </view>
+      
+      <!-- 文件夹名称 -->
+      <text class="folder-name">{{ item.name }}</text>
+      
+      <!-- 选择按钮 -->
+      <view 
+        class="select-btn" 
+        :class="{ disabled: !item.selectable }"
+        @click="handleSelect(item)"
+      >
+        <text class="select-text">{{ item.selectable ? '选择' : '无权限' }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'TreeView',
+  props: {
+    treeData: {
+      type: Array,
+      default: () => []
+    },
+    iconMap: {
+      type: Object,
+      default: () => ({
+        0: '/static/pages/scan/folderSelect/folder.svg',
+        1: '/static/pages/scan/folderSelect/country.svg',
+        2: '/static/pages/scan/folderSelect/center.svg'
+      })
+    },
+    // 权限配置:'*' 表示全部可选,'1,2,3' 表示只有这些ID及其子节点可选
+    permission: {
+      type: String,
+      default: '*'
+    }
+  },
+  data() {
+    return {
+      expandedIds: new Set(),
+      flattenedTree: [],
+      permissionIds: new Set(), // 有权限的节点ID集合
+      allowedNodeIds: new Set() // 允许选择的节点ID集合(包括子节点)
+    }
+  },
+  watch: {
+    treeData: {
+      handler() {
+        console.log('TreeView - treeData 变化:', this.treeData)
+        this.parsePermission()
+        this.buildFlattenedTree()
+      },
+      immediate: true,
+      deep: true
+    },
+    permission: {
+      handler() {
+        console.log('TreeView - permission 变化:', this.permission)
+        this.parsePermission()
+        this.buildFlattenedTree()
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    // 解析权限配置
+    parsePermission() {
+      console.log('========== 解析权限 ==========')
+      console.log('权限配置:', this.permission)
+      
+      this.permissionIds.clear()
+      this.allowedNodeIds.clear()
+      
+      // 如果是 '*',表示全部可选
+      if (this.permission === '*') {
+        console.log('权限为 *,全部节点可选')
+        return
+      }
+      
+      // 解析逗号分隔的ID列表
+      if (this.permission && this.permission.trim()) {
+        const ids = this.permission.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id))
+        ids.forEach(id => this.permissionIds.add(id))
+        console.log('有权限的节点ID:', Array.from(this.permissionIds))
+        
+        // 收集所有允许选择的节点(包括子节点)
+        this.collectAllowedNodes(this.treeData)
+        console.log('允许选择的节点ID:', Array.from(this.allowedNodeIds))
+      }
+    },
+    
+    // 递归收集允许选择的节点
+    collectAllowedNodes(nodes) {
+      if (!nodes || !Array.isArray(nodes)) return
+      
+      nodes.forEach(node => {
+        // 如果当前节点有权限,则它及其所有子节点都可选
+        if (this.permissionIds.has(node.id)) {
+          this.addNodeAndChildren(node)
+        } else if (node.children && node.children.length > 0) {
+          // 继续检查子节点
+          this.collectAllowedNodes(node.children)
+        }
+      })
+    },
+    
+    // 添加节点及其所有子节点到允许列表
+    addNodeAndChildren(node) {
+      this.allowedNodeIds.add(node.id)
+      if (node.children && node.children.length > 0) {
+        node.children.forEach(child => {
+          this.addNodeAndChildren(child)
+        })
+      }
+    },
+    
+    // 检查节点是否可选
+    isNodeSelectable(nodeId) {
+      // 如果权限为 '*',全部可选
+      if (this.permission === '*') {
+        return true
+      }
+      // 否则检查是否在允许列表中
+      return this.allowedNodeIds.has(nodeId)
+    },
+    
+    // 将树形数据扁平化
+    buildFlattenedTree() {
+      console.log('========== 开始构建扁平化树 ==========')
+      this.flattenedTree = []
+      this.flattenNode(this.treeData, 0)
+      console.log('扁平化树构建完成,节点数:', this.flattenedTree.length)
+      console.log('扁平化树:', this.flattenedTree)
+    },
+    
+    // 递归扁平化节点
+    flattenNode(nodes, level, parentPath = []) {
+      if (!nodes || !Array.isArray(nodes)) {
+        console.log('节点不是数组:', nodes)
+        return
+      }
+      
+      nodes.forEach(node => {
+        const hasChildren = node.children && node.children.length > 0
+        const isExpanded = this.expandedIds.has(node.id)
+        const isSelectable = this.isNodeSelectable(node.id)
+        
+        // 构建当前节点的完整路径
+        const currentPath = [...parentPath, node.name]
+        const fullPath = currentPath.join('/')
+        
+        // 添加当前节点
+        this.flattenedTree.push({
+          id: node.id,
+          name: node.name,
+          type: node.type,
+          level: level,
+          hasChildren: hasChildren,
+          expanded: isExpanded,
+          selectable: isSelectable,
+          fullPath: fullPath, // 完整路径
+          originalNode: node
+        })
+        
+        console.log(`添加节点: ${node.name}, level: ${level}, hasChildren: ${hasChildren}, expanded: ${isExpanded}, selectable: ${isSelectable}, path: ${fullPath}`)
+        
+        // 如果展开且有子节点,递归处理子节点
+        if (isExpanded && hasChildren) {
+          console.log(`展开子节点: ${node.name}, 子节点数: ${node.children.length}`)
+          this.flattenNode(node.children, level + 1, currentPath)
+        }
+      })
+    },
+    
+    // 切换展开/收起
+    toggleExpand(item) {
+      console.log('========== 切换展开状态 ==========')
+      console.log('节点:', item.name)
+      console.log('当前展开状态:', item.expanded)
+      
+      if (item.expanded) {
+        this.expandedIds.delete(item.id)
+        console.log('收起节点')
+      } else {
+        this.expandedIds.add(item.id)
+        console.log('展开节点')
+      }
+      
+      // 重新构建扁平化树
+      this.buildFlattenedTree()
+    },
+    
+    // 获取图标
+    getIcon(type) {
+      return this.iconMap[type] || this.iconMap[0]
+    },
+    
+    // 选择节点
+    handleSelect(item) {
+      console.log('选择节点:', item.name, '是否可选:', item.selectable)
+      console.log('完整路径:', item.fullPath)
+      
+      if (!item.selectable) {
+        uni.showToast({
+          title: '无权限选择此文件夹',
+          icon: 'none',
+          duration: 2000
+        })
+        return
+      }
+      
+      // 返回节点信息,包含完整路径
+      this.$emit('select', {
+        ...item.originalNode,
+        fullPath: item.fullPath
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.tree-view {
+  .tree-item {
+    display: flex;
+    align-items: center;
+    padding: 20rpx 24rpx;
+    border-bottom: 1rpx solid #f5f5f5;
+    background: #ffffff;
+    
+    &:active {
+      background: #f8f8f8;
+    }
+    
+    .expand-icon {
+      width: 40rpx;
+      height: 40rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-right: 8rpx;
+      
+      &.placeholder {
+        visibility: hidden;
+      }
+      
+      .icon-text {
+        font-size: 20rpx;
+        color: #999999;
+      }
+    }
+    
+    .folder-icon-wrapper {
+      width: 48rpx;
+      height: 48rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-right: 16rpx;
+      
+      .folder-icon {
+        width: 48rpx;
+        height: 48rpx;
+      }
+    }
+    
+    .folder-name {
+      flex: 1;
+      font-size: 28rpx;
+      color: #333333;
+    }
+    
+    .select-btn {
+      padding: 8rpx 24rpx;
+      background: #1ec9c9;
+      border-radius: 24rpx;
+      
+      &:active {
+        background: #1ab8b8;
+      }
+      
+      &.disabled {
+        background: #cccccc;
+        
+        &:active {
+          background: #cccccc;
+        }
+      }
+      
+      .select-text {
+        font-size: 24rpx;
+        color: #ffffff;
+        font-weight: 500;
+      }
+    }
+  }
+}
+</style>

+ 3 - 0
main.js

@@ -10,6 +10,9 @@ export function createApp() {
   app.use(pinia)
   app.use(i18n)
   
+  // 初始化全局数据
+  app.config.globalProperties.$globalData = {}
+  
   return {
     app
   }

+ 2 - 2
manifest.json

@@ -1,6 +1,6 @@
 {
     "name" : "intelligent-etmf-system-applet",
-    "appid" : "__UNI__F09429B",
+    "appid" : "__UNI__57C0DCB",
     "description" : "智能ETMF系统小程序",
     "versionName" : "1.0.0",
     "versionCode" : "100",
@@ -26,7 +26,7 @@
     },
     "quickapp" : {},
     "mp-weixin" : {
-        "appid" : "",
+        "appid" : "wxb82bdf2d6c33d224",
         "setting" : {
             "urlCheck" : false,
             "es6" : true,

+ 28 - 0
pages.json

@@ -44,6 +44,34 @@
         "enablePullDownRefresh": false
       }
     },
+    {
+      "path": "pages/scan/fileSelect/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/scan/projectSelect/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/scan/folderSelect/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/scan/uploadEdit/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
     {
       "path": "pages/my/index",
       "style": {

+ 27 - 13
pages/home/index.vue

@@ -25,6 +25,9 @@
           v-model="searchKeyword"
           @confirm="handleSearch"
         />
+        <view class="search-btn" @click="handleSearch">
+          <text class="search-btn-text">搜索</text>
+        </view>
       </view>
       
       <!-- 轮播图 -->
@@ -47,7 +50,7 @@
       <!-- 智能扫描卡片 -->
       <view class="scan-card" @click="handleGoToScan">
         <view class="scan-icon-wrapper">
-          <image class="scan-icon" src="/static/pages/home/scan-icon.png" mode="aspectFit" />
+          <image class="scan-icon" src="/static/pages/home/smart-scan.svg" mode="aspectFit" />
         </view>
         <view class="scan-info">
           <text class="scan-title">{{ t('home.intelligentScan') }}</text>
@@ -59,10 +62,10 @@
       <view class="recent-section">
         <view class="section-header">
           <text class="section-title">{{ t('home.recentDocuments') }}</text>
-          <view class="more-btn" @click="handleViewMore">
+<!--          <view class="more-btn" @click="handleViewMore">
             <text class="more-text">{{ t('home.viewMore') }}</text>
             <text class="more-arrow">›</text>
-          </view>
+          </view> -->
         </view>
         
         <view class="document-list">
@@ -261,14 +264,7 @@ const handleLoadMore = () => {
 
 // 搜索
 const handleSearch = () => {
-  if (!searchKeyword.value.trim()) {
-    uni.showToast({
-      title: t('home.searchKeywordEmpty'),
-      icon: 'none'
-    })
-    return
-  }
-  
+  // 允许空搜索,默认为 ""
   // 重置分页
   pageNum.value = 1
   hasMore.value = true
@@ -373,11 +369,11 @@ const handleDocumentClick = (doc) => {
       align-items: center;
       margin-bottom: 24rpx;
       box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+      gap: 16rpx;
       
       .search-icon {
         width: 32rpx;
         height: 32rpx;
-        margin-right: 16rpx;
       }
       
       .search-input {
@@ -388,8 +384,27 @@ const handleDocumentClick = (doc) => {
       
       .search-placeholder {
         color: #999999;
+      }
+      
+      .search-btn {
+        padding: 8rpx 24rpx;
+        background: #1ec9c9;
+        border-radius: 24rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        
+        &:active {
+          background: #1ab8b8;
+        }
+        
+        .search-btn-text {
+          font-size: 26rpx;
+          color: #ffffff;
+          font-weight: 500;
         }
       }
+      }
       
       // 轮播图
       .banner-section {
@@ -425,7 +440,6 @@ const handleDocumentClick = (doc) => {
       .scan-icon-wrapper {
         width: 88rpx;
         height: 88rpx;
-        background: linear-gradient(135deg, #1ec9c9 0%, #17b3b3 100%);
         border-radius: 16rpx;
         display: flex;
         align-items: center;

+ 4 - 38
pages/my/index.vue

@@ -30,10 +30,6 @@
             <text class="phone">{{ displayPhone }}</text>
           </view>
         </view>
-        <view class="vip-badge" @click.stop="handleVipClick">
-          <text class="vip-text">{{ t('my.normalMember') }}</text>
-          <text class="arrow">›</text>
-        </view>
       </view>
       
       <!-- 我的任务 -->
@@ -45,14 +41,14 @@
         <view class="task-grid">
           <view class="task-item" @click="handleTaskClick('pending')">
             <view class="task-icon-wrapper">
-              <image class="task-icon" src="/static/pages/my/task-pending.png" mode="aspectFit" />
+              <image class="task-icon" src="/static/pages/my/toSubmit.svg" mode="aspectFit" />
               <view v-if="taskCounts.pending > 0" class="task-badge">{{ taskCounts.pending }}</view>
             </view>
             <text class="task-label">{{ t('my.toSubmit') }}</text>
           </view>
           <view class="task-item" @click="handleTaskClick('reviewing')">
             <view class="task-icon-wrapper">
-              <image class="task-icon" src="/static/pages/my/task-reviewing.png" mode="aspectFit" />
+              <image class="task-icon" src="/static/pages/my/toAudit.svg" mode="aspectFit" />
               <view v-if="taskCounts.reviewing > 0" class="task-badge">{{ taskCounts.reviewing }}</view>
             </view>
             <text class="task-label">{{ t('my.toAudit') }}</text>
@@ -82,13 +78,13 @@
           </view>
         </view>
         
-        <view class="menu-item" @click="handleAbout">
+        <!-- <view class="menu-item" @click="handleAbout">
           <view class="menu-left">
             <image class="menu-icon" src="/static/pages/my/icon-about.png" mode="aspectFit" />
             <text class="menu-label">{{ t('my.aboutUs') }}</text>
           </view>
           <text class="arrow">›</text>
-        </view>
+        </view> -->
       </view>
       
       <!-- 退出登录按钮 -->
@@ -235,14 +231,6 @@ const handleUserCardClick = () => {
   })
 }
 
-// VIP点击
-const handleVipClick = () => {
-  uni.showToast({
-    title: t('my.memberFeature'),
-    icon: 'none'
-  })
-}
-
 // 任务点击
 const handleTaskClick = (type) => {
   let status = null
@@ -397,7 +385,6 @@ const handleLogout = () => {
         margin-bottom: 24rpx;
         display: flex;
         align-items: center;
-        justify-content: space-between;
         box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
         
         &:active {
@@ -434,27 +421,6 @@ const handleLogout = () => {
           }
         }
       }
-      
-      .vip-badge {
-        background: linear-gradient(135deg, #666666 0%, #555555 100%);
-        border-radius: 30rpx;
-        padding: 12rpx 24rpx;
-        display: flex;
-        align-items: center;
-        gap: 8rpx;
-        
-        .vip-text {
-          font-size: 24rpx;
-          color: #ffffff;
-          font-weight: 500;
-        }
-        
-        .arrow {
-          font-size: 32rpx;
-          color: #ffffff;
-          font-weight: 300;
-        }
-        }
       }
       
       // 我的任务

+ 644 - 14
pages/my/info/index.vue

@@ -23,17 +23,27 @@
         <!-- 头像 -->
         <view class="info-item avatar-item">
           <text class="item-label">{{ t('pagesContent.my.info.avatar') }}</text>
-          <image 
-            class="avatar-image" 
-            :src="basicInfo.avatar || '/static/default-avatar.svg'" 
-            mode="aspectFill"
-          />
+          <view class="avatar-wrapper">
+            <image 
+              class="avatar-image" 
+              :src="basicInfo.avatar || '/static/default-avatar.svg'" 
+              mode="aspectFill"
+            />
+            <view class="edit-btn" @click="handleEditAvatar">
+              <text class="edit-btn-text">修改</text>
+            </view>
+          </view>
         </view>
         
         <!-- 昵称 -->
         <view class="info-item">
           <text class="item-label">{{ t('pagesContent.my.info.nickname') }}</text>
-          <text class="item-value">{{ basicInfo.nickname || '-' }}</text>
+          <view class="value-wrapper">
+            <text class="item-value">{{ basicInfo.nickname || '-' }}</text>
+            <view class="edit-btn" @click="handleEditNickname">
+              <text class="edit-btn-text">修改</text>
+            </view>
+          </view>
         </view>
         
         <!-- 手机号 -->
@@ -45,8 +55,108 @@
         <!-- 性别 -->
         <view class="info-item">
           <text class="item-label">{{ t('pagesContent.my.info.gender') }}</text>
-          <view class="gender-value">
-            <text class="item-value" :class="genderClass">{{ genderDisplay }}</text>
+          <view class="value-wrapper">
+            <view class="gender-value">
+              <text class="item-value" :class="genderClass">{{ genderDisplay }}</text>
+            </view>
+            <view class="edit-btn" @click="handleEditGender">
+              <text class="edit-btn-text">修改</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 修改头像弹窗 -->
+    <view v-if="showAvatarModal" class="modal-overlay" @click="closeAvatarModal">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">修改头像</text>
+          <view class="modal-close" @click="closeAvatarModal">
+            <text class="close-icon">×</text>
+          </view>
+        </view>
+        <view class="modal-body">
+          <view class="upload-area" @click="handleSelectImage">
+            <image 
+              v-if="tempAvatar" 
+              class="preview-image" 
+              :src="tempAvatar" 
+              mode="aspectFill"
+            />
+            <view v-else class="upload-placeholder">
+              <text class="placeholder-text">点击选择图片</text>
+            </view>
+          </view>
+          <view class="modal-actions">
+            <view class="action-btn cancel-btn" @click="closeAvatarModal">
+              <text class="btn-text">取消</text>
+            </view>
+            <view class="action-btn confirm-btn" @click="confirmUpdateAvatar">
+              <text class="btn-text">确认</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 修改昵称弹窗 -->
+    <view v-if="showNicknameModal" class="modal-overlay" @click="closeNicknameModal">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">修改昵称</text>
+          <view class="modal-close" @click="closeNicknameModal">
+            <text class="close-icon">×</text>
+          </view>
+        </view>
+        <view class="modal-body">
+          <input 
+            class="modal-input" 
+            v-model="tempNickname" 
+            placeholder="请输入昵称"
+            maxlength="20"
+          />
+          <view class="modal-actions">
+            <view class="action-btn cancel-btn" @click="closeNicknameModal">
+              <text class="btn-text">取消</text>
+            </view>
+            <view class="action-btn confirm-btn" @click="confirmUpdateNickname">
+              <text class="btn-text">确认</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 修改性别弹窗 -->
+    <view v-if="showGenderModal" class="modal-overlay" @click="closeGenderModal">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">修改性别</text>
+          <view class="modal-close" @click="closeGenderModal">
+            <text class="close-icon">×</text>
+          </view>
+        </view>
+        <view class="modal-body">
+          <picker 
+            mode="selector" 
+            :range="genderDictListWithLabel" 
+            range-key="displayLabel"
+            :value="selectedGenderIndex"
+            @change="handleGenderChange"
+          >
+            <view class="picker-value">
+              <text class="value-text">{{ selectedGenderLabel || '请选择性别' }}</text>
+              <text class="arrow-icon">▼</text>
+            </view>
+          </picker>
+          <view class="modal-actions">
+            <view class="action-btn cancel-btn" @click="closeGenderModal">
+              <text class="btn-text">取消</text>
+            </view>
+            <view class="action-btn confirm-btn" @click="confirmUpdateGender">
+              <text class="btn-text">确认</text>
+            </view>
           </view>
         </view>
       </view>
@@ -57,7 +167,7 @@
 <script setup>
 import { ref, computed, onMounted } from 'vue'
 import { useI18n } from 'vue-i18n'
-import { getBasicInfo } from '@/apis/auth'
+import { getBasicInfo, updateAvatar, updateNickname, updateGender, uploadToOss } from '@/apis/auth'
 import { getDictDataByType } from '@/apis/dict'
 
 const { t, locale } = useI18n()
@@ -82,6 +192,53 @@ const loading = ref(false)
 // 性别字典数据
 const genderDictList = ref([])
 
+// 弹窗状态
+const showAvatarModal = ref(false)
+const showNicknameModal = ref(false)
+const showGenderModal = ref(false)
+
+// 临时数据
+const tempAvatar = ref('')
+const tempAvatarOssId = ref('')
+const tempNickname = ref('')
+const tempGender = ref('')
+
+// 性别显示(为字典项添加displayLabel属性)
+const genderDictListWithLabel = computed(() => {
+  return genderDictList.value.map(item => {
+    try {
+      const labelObj = JSON.parse(item.dictLabel)
+      const localeKey = locale.value.replace('-', '_')
+      return {
+        ...item,
+        displayLabel: labelObj[localeKey] || labelObj['zh_CN'] || item.dictLabel
+      }
+    } catch (error) {
+      return {
+        ...item,
+        displayLabel: item.dictLabel
+      }
+    }
+  })
+})
+
+// 选中的性别索引
+const selectedGenderIndex = computed(() => {
+  if (!tempGender.value) return 0
+  return genderDictListWithLabel.value.findIndex(item => 
+    String(item.dictValue) === String(tempGender.value)
+  )
+})
+
+// 选中的性别标签
+const selectedGenderLabel = computed(() => {
+  if (!tempGender.value) return ''
+  const item = genderDictListWithLabel.value.find(item => 
+    String(item.dictValue) === String(tempGender.value)
+  )
+  return item?.displayLabel || ''
+})
+
 // 性别显示
 const genderDisplay = computed(() => {
   if (basicInfo.value.gender === null || basicInfo.value.gender === undefined || basicInfo.value.gender === '') return '-'
@@ -142,6 +299,215 @@ const fetchGenderDict = async () => {
   }
 }
 
+// 打开修改头像弹窗
+const handleEditAvatar = () => {
+  tempAvatar.value = basicInfo.value.avatar || ''
+  tempAvatarOssId.value = ''
+  showAvatarModal.value = true
+}
+
+// 关闭修改头像弹窗
+const closeAvatarModal = () => {
+  showAvatarModal.value = false
+  tempAvatar.value = ''
+  tempAvatarOssId.value = ''
+}
+
+// 选择图片
+const handleSelectImage = () => {
+  uni.chooseImage({
+    count: 1,
+    sizeType: ['compressed'],
+    sourceType: ['album', 'camera'],
+    success: async (res) => {
+      tempAvatar.value = res.tempFilePaths[0]
+    }
+  })
+}
+
+// 确认修改头像
+const confirmUpdateAvatar = async () => {
+  if (!tempAvatar.value) {
+    uni.showToast({
+      title: '请选择图片',
+      icon: 'none'
+    })
+    return
+  }
+  
+  try {
+    uni.showLoading({
+      title: '上传中...',
+      mask: true
+    })
+    
+    // 先上传图片到OSS
+    const uploadResponse = await uploadToOss(tempAvatar.value)
+    
+    if (uploadResponse && uploadResponse.code === 200 && uploadResponse.data) {
+      // 使用返回的ossId更新头像
+      const updateResponse = await updateAvatar({
+        avatar: uploadResponse.data.ossId
+      })
+      
+      uni.hideLoading()
+      
+      if (updateResponse && updateResponse.code === 200) {
+        uni.showToast({
+          title: '修改成功',
+          icon: 'success'
+        })
+        
+        closeAvatarModal()
+        // 重新获取基本信息
+        fetchBasicInfo()
+      } else {
+        uni.showToast({
+          title: updateResponse.msg || '修改失败',
+          icon: 'none'
+        })
+      }
+    } else {
+      uni.hideLoading()
+      uni.showToast({
+        title: uploadResponse.msg || '上传失败',
+        icon: 'none'
+      })
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('修改头像失败:', error)
+    uni.showToast({
+      title: '修改失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 打开修改昵称弹窗
+const handleEditNickname = () => {
+  tempNickname.value = basicInfo.value.nickname || ''
+  showNicknameModal.value = true
+}
+
+// 关闭修改昵称弹窗
+const closeNicknameModal = () => {
+  showNicknameModal.value = false
+  tempNickname.value = ''
+}
+
+// 确认修改昵称
+const confirmUpdateNickname = async () => {
+  if (!tempNickname.value || !tempNickname.value.trim()) {
+    uni.showToast({
+      title: '请输入昵称',
+      icon: 'none'
+    })
+    return
+  }
+  
+  try {
+    uni.showLoading({
+      title: '修改中...',
+      mask: true
+    })
+    
+    const response = await updateNickname({
+      nickname: tempNickname.value.trim()
+    })
+    
+    uni.hideLoading()
+    
+    if (response && response.code === 200) {
+      uni.showToast({
+        title: '修改成功',
+        icon: 'success'
+      })
+      
+      closeNicknameModal()
+      // 重新获取基本信息
+      fetchBasicInfo()
+    } else {
+      uni.showToast({
+        title: response.msg || '修改失败',
+        icon: 'none'
+      })
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('修改昵称失败:', error)
+    uni.showToast({
+      title: '修改失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 打开修改性别弹窗
+const handleEditGender = () => {
+  tempGender.value = basicInfo.value.gender || ''
+  showGenderModal.value = true
+}
+
+// 关闭修改性别弹窗
+const closeGenderModal = () => {
+  showGenderModal.value = false
+  tempGender.value = ''
+}
+
+// 性别选择变化
+const handleGenderChange = (e) => {
+  const index = e.detail.value
+  tempGender.value = genderDictListWithLabel.value[index].dictValue
+}
+
+// 确认修改性别
+const confirmUpdateGender = async () => {
+  if (!tempGender.value && tempGender.value !== 0) {
+    uni.showToast({
+      title: '请选择性别',
+      icon: 'none'
+    })
+    return
+  }
+  
+  try {
+    uni.showLoading({
+      title: '修改中...',
+      mask: true
+    })
+    
+    const response = await updateGender({
+      gender: tempGender.value
+    })
+    
+    uni.hideLoading()
+    
+    if (response && response.code === 200) {
+      uni.showToast({
+        title: '修改成功',
+        icon: 'success'
+      })
+      
+      closeGenderModal()
+      // 重新获取基本信息
+      fetchBasicInfo()
+    } else {
+      uni.showToast({
+        title: response.msg || '修改失败',
+        icon: 'none'
+      })
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('修改性别失败:', error)
+    uni.showToast({
+      title: '修改失败',
+      icon: 'none'
+    })
+  }
+}
+
 // 获取基本信息
 const fetchBasicInfo = async () => {
   try {
@@ -270,11 +636,40 @@ const handleBack = () => {
         }
         
         &.avatar-item {
-          .avatar-image {
-            width: 100rpx;
-            height: 100rpx;
-            border-radius: 50rpx;
-            border: 4rpx solid #6ec7f5;
+          .avatar-wrapper {
+            display: flex;
+            align-items: center;
+            gap: 24rpx;
+            
+            .avatar-image {
+              width: 100rpx;
+              height: 100rpx;
+              border-radius: 50rpx;
+              border: 4rpx solid #6ec7f5;
+            }
+          }
+        }
+        
+        .value-wrapper {
+          display: flex;
+          align-items: center;
+          gap: 16rpx;
+        }
+        
+        .edit-btn {
+          padding: 10rpx 24rpx;
+          background: linear-gradient(135deg, #1ec9c9 0%, #17b3b3 100%);
+          border-radius: 20rpx;
+          
+          &:active {
+            opacity: 0.8;
+          }
+          
+          .edit-btn-text {
+            font-size: 24rpx;
+            color: #ffffff;
+            font-weight: 500;
+            white-space: nowrap;
           }
         }
         
@@ -322,5 +717,240 @@ const handleBack = () => {
       }
     }
   }
+  
+  // 弹窗样式
+  .modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.6);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+    backdrop-filter: blur(4rpx);
+    
+    .modal-content {
+      width: 620rpx;
+      max-width: 90%;
+      background: #ffffff;
+      border-radius: 24rpx;
+      overflow: hidden;
+      box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
+      animation: modalSlideIn 0.3s ease-out;
+      
+      @keyframes modalSlideIn {
+        from {
+          opacity: 0;
+          transform: translateY(-40rpx) scale(0.95);
+        }
+        to {
+          opacity: 1;
+          transform: translateY(0) scale(1);
+        }
+      }
+      
+      .modal-header {
+        padding: 40rpx 32rpx 24rpx;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        background: linear-gradient(135deg, #f8fcff 0%, #ffffff 100%);
+        
+        .modal-title {
+          font-size: 34rpx;
+          font-weight: 600;
+          color: #1a1a1a;
+          letter-spacing: 1rpx;
+        }
+        
+        .modal-close {
+          width: 56rpx;
+          height: 56rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 50%;
+          background: rgba(0, 0, 0, 0.04);
+          transition: all 0.2s;
+          
+          &:active {
+            background: rgba(0, 0, 0, 0.08);
+            transform: scale(0.9);
+          }
+          
+          .close-icon {
+            font-size: 44rpx;
+            color: #666666;
+            line-height: 1;
+            font-weight: 300;
+          }
+        }
+      }
+      
+      .modal-body {
+        padding: 32rpx;
+        
+        .upload-area {
+          width: 100%;
+          height: 420rpx;
+          border: 3rpx dashed #d9d9d9;
+          border-radius: 16rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          overflow: hidden;
+          margin-bottom: 32rpx;
+          background: #fafafa;
+          transition: all 0.3s;
+          position: relative;
+          
+          &:active {
+            border-color: #1ec9c9;
+            background: #f0fffe;
+          }
+          
+          .preview-image {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+          }
+          
+          .upload-placeholder {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            gap: 16rpx;
+            
+            &::before {
+              content: '+';
+              font-size: 80rpx;
+              color: #1ec9c9;
+              font-weight: 300;
+              line-height: 1;
+            }
+            
+            .placeholder-text {
+              font-size: 28rpx;
+              color: #999999;
+            }
+          }
+        }
+        
+        .modal-input {
+          width: 100%;
+          padding: 28rpx 24rpx;
+          border: 2rpx solid #e8e8e8;
+          border-radius: 16rpx;
+          font-size: 30rpx;
+          color: #333333;
+          margin-bottom: 32rpx;
+          box-sizing: border-box;
+          background: #fafafa;
+          transition: all 0.3s;
+          line-height: 1.5;
+          min-height: 88rpx;
+          
+          &:focus {
+            border-color: #1ec9c9;
+            background: #ffffff;
+            box-shadow: 0 0 0 4rpx rgba(30, 201, 201, 0.1);
+          }
+        }
+        
+        .picker-value {
+          width: 100%;
+          padding: 28rpx 24rpx;
+          border: 2rpx solid #e8e8e8;
+          border-radius: 16rpx;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          margin-bottom: 32rpx;
+          background: #fafafa;
+          transition: all 0.2s;
+          box-sizing: border-box;
+          min-height: 88rpx;
+          
+          &:active {
+            background: #f0f0f0;
+          }
+          
+          .value-text {
+            font-size: 30rpx;
+            color: #333333;
+            flex: 1;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+          
+          .arrow-icon {
+            font-size: 24rpx;
+            color: #999999;
+            transition: transform 0.3s;
+            margin-left: 16rpx;
+            flex-shrink: 0;
+          }
+        }
+        
+        .modal-actions {
+          display: flex;
+          gap: 20rpx;
+          margin-top: 8rpx;
+          
+          .action-btn {
+            flex: 1;
+            padding: 28rpx;
+            border-radius: 16rpx;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s;
+            box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+            
+            &:active {
+              transform: scale(0.98);
+            }
+            
+            .btn-text {
+              font-size: 30rpx;
+              font-weight: 500;
+              letter-spacing: 1rpx;
+            }
+            
+            &.cancel-btn {
+              background: #f5f5f5;
+              box-shadow: none;
+              
+              &:active {
+                background: #e8e8e8;
+              }
+              
+              .btn-text {
+                color: #666666;
+              }
+            }
+            
+            &.confirm-btn {
+              background: linear-gradient(135deg, #1ec9c9 0%, #17b3b3 100%);
+              box-shadow: 0 4rpx 16rpx rgba(30, 201, 201, 0.3);
+              
+              &:active {
+                box-shadow: 0 2rpx 8rpx rgba(30, 201, 201, 0.3);
+              }
+              
+              .btn-text {
+                color: #ffffff;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
 }
 </style>

+ 192 - 7
pages/my/taskDocuments/index.vue

@@ -69,6 +69,14 @@
                 <text class="meta-text" v-if="doc.submitter">{{ t('taskDocuments.submitter') }}:{{ doc.submitter }}</text>
               </view>
             </view>
+            <!-- 审核拒绝状态显示查看驳回理由按钮 -->
+            <view 
+              v-if="doc.status === 2" 
+              class="rejection-btn"
+              @click.stop="handleViewRejection(doc)"
+            >
+              <text class="rejection-btn-text">查看驳回理由</text>
+            </view>
           </view>
           
           <!-- 加载状态 -->
@@ -88,13 +96,28 @@
         </view>
       </view>
     </scroll-view>
+    
+    <!-- 驳回理由弹窗 -->
+    <view v-if="showRejectionModal" class="modal-overlay" @click="closeRejectionModal">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">驳回理由</text>
+          <view class="modal-close" @click="closeRejectionModal">
+            <text class="close-icon">×</text>
+          </view>
+        </view>
+        <view class="modal-body">
+          <text class="rejection-text">{{ rejectionReason }}</text>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
 <script setup>
 import { ref, onMounted, computed } from 'vue'
 import { useI18n } from 'vue-i18n'
-import { getTaskDocuments } from '@/apis/auth'
+import { getTaskDocuments, getRejection } from '@/apis/auth'
 
 const { t } = useI18n()
 
@@ -109,12 +132,12 @@ const statusOptions = computed(() => [
   { label: t('taskDocuments.status.all'), value: null },
   { label: t('taskDocuments.status.unUpload'), value: 0 },
   { label: t('taskDocuments.status.unAudit'), value: 1 },
-  { label: t('taskDocuments.status.auditReject'), value: 2 },
-  { label: t('taskDocuments.status.unFiling'), value: 3 },
-  { label: t('taskDocuments.status.filing'), value: 4 },
-  { label: t('taskDocuments.status.unQualityControl'), value: 5 },
-  { label: t('taskDocuments.status.qualityControlPass'), value: 6 },
-  { label: t('taskDocuments.status.qualityControlReject'), value: 7 }
+  { label: t('taskDocuments.status.auditReject'), value: 2 }
+  // { label: t('taskDocuments.status.unFiling'), value: 3 },
+  // { label: t('taskDocuments.status.filing'), value: 4 },
+  // { label: t('taskDocuments.status.unQualityControl'), value: 5 },
+  // { label: t('taskDocuments.status.qualityControlPass'), value: 6 },
+  // { label: t('taskDocuments.status.qualityControlReject'), value: 7 }
 ])
 
 // 当前选中的状态标签
@@ -136,6 +159,10 @@ const total = ref(0)
 const loading = ref(false)
 const hasMore = ref(true)
 
+// 驳回理由弹窗
+const showRejectionModal = ref(false)
+const rejectionReason = ref('')
+
 // 页面标题
 const pageTitle = computed(() => {
   if (taskStatus.value === 0) return t('taskDocuments.toSubmit')
@@ -279,6 +306,32 @@ const handleStatusChange = (e) => {
 
 // 点击文档
 const handleDocumentClick = (doc) => {
+  // 状态 0: 待递交 - 跳转到扫描页面
+  if (doc.status === 0) {
+    uni.navigateTo({
+      url: '/pages/scan/index'
+    })
+    return
+  }
+  
+  // 状态 2: 审核拒绝 - 全屏预览文档
+  if (doc.status === 2) {
+    if (!doc.url) {
+      uni.showToast({
+        title: t('taskDocuments.invalidUrl'),
+        icon: 'none'
+      })
+      return
+    }
+    
+    // 使用 web-view 全屏预览文档
+    uni.navigateTo({
+      url: `/pages/home/documentViewer/index?url=${encodeURIComponent(doc.url)}&name=${encodeURIComponent(doc.name || '文档预览')}`
+    })
+    return
+  }
+  
+  // 其他状态 - 默认处理
   if (!doc.url) {
     uni.showToast({
       title: t('taskDocuments.invalidUrl'),
@@ -303,6 +356,51 @@ const handleDocumentClick = (doc) => {
   })
 }
 
+// 查看驳回理由
+const handleViewRejection = async (doc) => {
+  if (!doc.id) {
+    uni.showToast({
+      title: '文档ID无效',
+      icon: 'none'
+    })
+    return
+  }
+  
+  try {
+    uni.showLoading({
+      title: '加载中...',
+      mask: true
+    })
+    
+    const response = await getRejection(doc.id)
+    
+    uni.hideLoading()
+    
+    if (response && response.code === 200 && response.data) {
+      rejectionReason.value = response.data.rejection || '暂无驳回理由'
+      showRejectionModal.value = true
+    } else {
+      uni.showToast({
+        title: response.msg || '获取驳回理由失败',
+        icon: 'none'
+      })
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('获取驳回理由失败:', error)
+    uni.showToast({
+      title: '获取驳回理由失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 关闭驳回理由弹窗
+const closeRejectionModal = () => {
+  showRejectionModal.value = false
+  rejectionReason.value = ''
+}
+
 // 返回
 const handleBack = () => {
   uni.navigateBack({
@@ -493,6 +591,7 @@ const handleBack = () => {
             display: flex;
             flex-direction: column;
             gap: 12rpx;
+            min-width: 0;
             
             .doc-name {
               font-size: 28rpx;
@@ -514,6 +613,25 @@ const handleBack = () => {
               }
             }
           }
+          
+          .rejection-btn {
+            margin-left: 16rpx;
+            padding: 12rpx 20rpx;
+            background: linear-gradient(135deg, #ff6b6b 0%, #ff5252 100%);
+            border-radius: 8rpx;
+            flex-shrink: 0;
+            
+            &:active {
+              opacity: 0.8;
+            }
+            
+            .rejection-btn-text {
+              font-size: 24rpx;
+              color: #ffffff;
+              font-weight: 500;
+              white-space: nowrap;
+            }
+          }
         }
         
         // 加载更多
@@ -551,5 +669,72 @@ const handleBack = () => {
       }
     }
   }
+  
+  // 驳回理由弹窗
+  .modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+    
+    .modal-content {
+      width: 600rpx;
+      background: #ffffff;
+      border-radius: 16rpx;
+      overflow: hidden;
+      
+      .modal-header {
+        padding: 32rpx 24rpx;
+        border-bottom: 1rpx solid #f0f0f0;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        
+        .modal-title {
+          font-size: 32rpx;
+          font-weight: 600;
+          color: #333333;
+        }
+        
+        .modal-close {
+          width: 48rpx;
+          height: 48rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          
+          &:active {
+            opacity: 0.6;
+          }
+          
+          .close-icon {
+            font-size: 48rpx;
+            color: #999999;
+            line-height: 1;
+            font-weight: 300;
+          }
+        }
+      }
+      
+      .modal-body {
+        padding: 32rpx 24rpx;
+        max-height: 400rpx;
+        overflow-y: auto;
+        
+        .rejection-text {
+          font-size: 28rpx;
+          color: #666666;
+          line-height: 1.6;
+          word-break: break-all;
+        }
+      }
+    }
+  }
 }
 </style>

+ 450 - 0
pages/scan/fileSelect/index.vue

@@ -0,0 +1,450 @@
+<template>
+  <view class="file-select-page">
+    <!-- 顶部导航栏 -->
+    <view class="header-bg" :style="{ paddingTop: statusBarHeight + 'px' }">
+      <view class="header-content">
+        <view class="back-btn" @click="handleBack">
+          <text class="back-icon">‹</text>
+        </view>
+        <text class="header-title">请选择对应缺失文件</text>
+        <view class="placeholder"></view>
+      </view>
+    </view>
+    
+    <!-- 搜索框 -->
+    <view class="search-bar">
+      <input 
+        class="search-input" 
+        v-model="searchName" 
+        placeholder="搜索文件名"
+        @confirm="handleSearch"
+      />
+      <view class="search-btn" @click="handleSearch">
+        <text class="search-text">搜索</text>
+      </view>
+    </view>
+    
+    <!-- 文件列表 -->
+    <scroll-view 
+      scroll-y 
+      class="file-list"
+      @scrolltolower="handleLoadMore"
+    >
+      <view 
+        v-for="item in fileList" 
+        :key="item.id" 
+        class="file-item"
+        @click="handleSelectFile(item)"
+      >
+        <view class="file-icon-wrapper">
+          <image src="/static/icon/document.svg" mode="aspectFit" class="file-icon" />
+        </view>
+        <view class="file-info">
+          <text class="file-name">{{ item.name }}</text>
+          <text class="file-detail">项目: {{ item.project }}</text>
+          <text class="file-detail">文件夹: {{ item.folder }}</text>
+          <text class="file-detail">创建人: {{ item.createBy }}</text>
+          <text class="file-detail">截止日期: {{ formatDate(item.deadline) }}</text>
+        </view>
+
+      </view>
+      
+      <view v-if="fileList.length === 0 && !loading" class="empty-tip">
+        <text class="empty-text">暂无待递交文件</text>
+      </view>
+      
+      <view v-if="loading" class="loading-tip">
+        <text class="loading-text">加载中...</text>
+      </view>
+    </scroll-view>
+    
+    <!-- 底部提交按钮 -->
+    <view class="submit-footer" v-if="fileList.length > 0">
+      <view class="submit-btn secondary" @click="handleDirectUpload">
+        <text class="submit-text">直接上传</text>
+      </view>
+      <view class="submit-btn" @click="handleSubmitAll">
+        <text class="submit-text">提交</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getToSubmitList, uploadOnSubmit } from '@/apis/scan'
+
+// 状态栏高度
+const statusBarHeight = ref(0)
+
+// 文件列表
+const fileList = ref([])
+const searchName = ref('')
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const loading = ref(false)
+
+onMounted(() => {
+  // 获取系统信息
+  const windowInfo = uni.getWindowInfo()
+  statusBarHeight.value = windowInfo.statusBarHeight || 0
+  
+  // 加载文件列表
+  loadFileList()
+})
+
+// 返回上一页
+const handleBack = () => {
+  uni.navigateBack()
+}
+
+// 加载文件列表
+const loadFileList = async () => {
+  if (loading.value) return
+  
+  try {
+    loading.value = true
+    
+    const response = await getToSubmitList({
+      name: searchName.value,
+      pageNum: pageNum.value,
+      pageSize: pageSize.value
+    })
+    
+    if (response.code === 200) {
+      if (pageNum.value === 1) {
+        fileList.value = response.rows || []
+      } else {
+        fileList.value = [...fileList.value, ...(response.rows || [])]
+      }
+      total.value = response.total || 0
+    }
+  } catch (error) {
+    console.error('加载文件列表失败:', error)
+    uni.showToast({
+      title: '加载失败',
+      icon: 'none'
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 搜索
+const handleSearch = () => {
+  pageNum.value = 1
+  fileList.value = []
+  loadFileList()
+}
+
+// 加载更多
+const handleLoadMore = () => {
+  if (loading.value) return
+  if (fileList.value.length >= total.value) return
+  
+  pageNum.value++
+  loadFileList()
+}
+
+// 选择文件
+const handleSelectFile = async (file) => {
+  try {
+    // 从全局数据中获取扫描的fileBase64List
+    const fileBase64List = getApp().globalData.scannedFileBase64List
+    
+    if (!fileBase64List || fileBase64List.length === 0) {
+      uni.showToast({
+        title: '未找到扫描文件',
+        icon: 'none'
+      })
+      return
+    }
+    
+    uni.showLoading({
+      title: '提交中...',
+      mask: true
+    })
+    
+    // 调用上传接口
+    const response = await uploadOnSubmit({
+      documentId: file.id,
+      fileBase64List: fileBase64List
+    })
+    
+    uni.hideLoading()
+    
+    if (response.code === 200) {
+      // 清除全局数据
+      getApp().globalData.scannedFileBase64List = null
+      
+      uni.showToast({
+        title: '提交成功',
+        icon: 'success',
+        duration: 2000
+      })
+      
+      // 返回首页
+      setTimeout(() => {
+        uni.reLaunch({
+          url: '/pages/home/index'
+        })
+      }, 2000)
+    } else {
+      throw new Error(response.msg || '提交失败')
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('提交失败:', error)
+    uni.showToast({
+      title: error.message || '提交失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 底部提交按钮(与点击文件相同的逻辑,但需要先选择文件)
+const handleSubmitAll = () => {
+  if (fileList.value.length === 0) {
+    uni.showToast({
+      title: '暂无可选文件',
+      icon: 'none'
+    })
+    return
+  }
+  
+  uni.showToast({
+    title: '请点击选择对应的文件',
+    icon: 'none'
+  })
+}
+
+// 直接上传 - 跳转到项目选择页面
+const handleDirectUpload = () => {
+  // 从全局数据中获取扫描的fileBase64List
+  const fileBase64List = getApp().globalData.scannedFileBase64List
+  
+  if (!fileBase64List || fileBase64List.length === 0) {
+    uni.showToast({
+      title: '未找到扫描文件',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 跳转到项目选择页面
+  uni.navigateTo({
+    url: '/pages/scan/projectSelect/index'
+  })
+}
+
+// 格式化日期(只显示到天)
+const formatDate = (dateStr) => {
+  if (!dateStr) return ''
+  // 如果日期格式是 "2026-01-04 10:58:37",只取前10位
+  return dateStr.split(' ')[0]
+}
+</script>
+
+<style lang="scss" scoped>
+.file-select-page {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f5f5;
+  
+  // 顶部导航栏
+  .header-bg {
+    background: linear-gradient(180deg, #1ec9c9 0%, #1eb8b8 100%);
+    position: relative;
+    z-index: 100;
+    
+    .header-content {
+      height: 88rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 0 24rpx;
+      
+      .back-btn {
+        width: 60rpx;
+        height: 60rpx;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        
+        .back-icon {
+          font-size: 56rpx;
+          color: #ffffff;
+          font-weight: 300;
+        }
+      }
+      
+      .header-title {
+        flex: 1;
+        text-align: center;
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+      
+      .placeholder {
+        width: 60rpx;
+      }
+    }
+  }
+  
+  // 搜索框
+  .search-bar {
+    padding: 24rpx;
+    background: #ffffff;
+    display: flex;
+    gap: 16rpx;
+    
+    .search-input {
+      flex: 1;
+      height: 64rpx;
+      padding: 0 24rpx;
+      background: #f5f5f5;
+      border-radius: 32rpx;
+      font-size: 28rpx;
+    }
+    
+    .search-btn {
+      width: 120rpx;
+      height: 64rpx;
+      background: #1ec9c9;
+      border-radius: 32rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &:active {
+        background: #1ab8b8;
+      }
+      
+      .search-text {
+        font-size: 28rpx;
+        color: #ffffff;
+        font-weight: 500;
+      }
+    }
+  }
+  
+  // 文件列表
+  .file-list {
+    flex: 1;
+    background: #ffffff;
+    
+    .file-item {
+      padding: 24rpx;
+      display: flex;
+      align-items: center;
+      border-bottom: 1rpx solid #f5f5f5;
+      
+      &:active {
+        background: #f8f8f8;
+      }
+      
+      .file-icon-wrapper {
+        width: 80rpx;
+        height: 80rpx;
+        background: #f5f5f5;
+        border-radius: 12rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 24rpx;
+        
+        .file-icon {
+          width: 48rpx;
+          height: 48rpx;
+        }
+      }
+      
+      .file-info {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        gap: 6rpx;
+        
+        .file-name {
+          font-size: 32rpx;
+          font-weight: 600;
+          color: #333333;
+          margin-bottom: 4rpx;
+        }
+        
+        .file-detail {
+          font-size: 26rpx;
+          color: #666666;
+          line-height: 1.5;
+        }
+      }
+    }
+    
+    .empty-tip {
+      padding: 200rpx 0;
+      text-align: center;
+      
+      .empty-text {
+        font-size: 28rpx;
+        color: #999999;
+      }
+    }
+    
+    .loading-tip {
+      padding: 32rpx 0;
+      text-align: center;
+      
+      .loading-text {
+        font-size: 28rpx;
+        color: #999999;
+      }
+    }
+  }
+  
+  // 底部提交按钮
+  .submit-footer {
+    background: #ffffff;
+    padding: 24rpx 32rpx;
+    padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+    box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
+    display: flex;
+    gap: 16rpx;
+    
+    .submit-btn {
+      flex: 1;
+      height: 88rpx;
+      background: linear-gradient(135deg, #1ec9c9 0%, #1eb8b8 100%);
+      border-radius: 44rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-shadow: 0 4rpx 16rpx rgba(30, 201, 201, 0.3);
+      
+      &:active {
+        transform: scale(0.98);
+        opacity: 0.9;
+      }
+      
+      &.secondary {
+        background: #ffffff;
+        border: 2rpx solid #1ec9c9;
+        box-shadow: none;
+        
+        .submit-text {
+          color: #1ec9c9;
+        }
+      }
+      
+      .submit-text {
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+    }
+  }
+}
+</style>

+ 219 - 0
pages/scan/folderSelect/components/FolderTreeItem.vue

@@ -0,0 +1,219 @@
+<template>
+  <view class="folder-tree-item">
+    <!-- 文件夹项 -->
+    <view 
+      class="folder-item" 
+      :style="{ paddingLeft: (level * 40 + 24) + 'rpx' }"
+      @click="handleToggle"
+    >
+      <!-- 展开/收起图标 -->
+      <view class="expand-icon" v-if="hasChildren">
+        <text class="icon-text">{{ isExpanded ? '▼' : '▶' }}</text>
+      </view>
+      <view class="expand-icon placeholder" v-else></view>
+      
+      <!-- 文件夹图标 -->
+      <view class="folder-icon-wrapper">
+        <image 
+          :src="getFolderIcon(folder.type)" 
+          mode="aspectFit" 
+          class="folder-icon" 
+        />
+      </view>
+      
+      <!-- 文件夹名称 -->
+      <text class="folder-name">{{ folder.name }}</text>
+      
+      <!-- 选择按钮 -->
+      <view class="select-btn" @click.stop="handleSelect">
+        <text class="select-text">选择</text>
+      </view>
+    </view>
+    
+    <!-- 子文件夹 -->
+    <view v-if="isExpanded && hasChildren" class="children">
+      <view
+        v-for="child in folder.children"
+        :key="child.id"
+      >
+        <folder-tree-item
+          :folder="child"
+          :level="level + 1"
+          @select="handleChildSelect"
+        />
+      </view>
+    </view>
+    
+    <!-- 调试:显示展开状态 -->
+    <view v-if="hasChildren" style="padding: 10rpx 24rpx; background: #e3f2fd; font-size: 20rpx; color: #1976d2;">
+      展开状态: {{ isExpanded ? '已展开' : '已收起' }} | 子节点数: {{ folder.children.length }}
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'FolderTreeItem',
+  // 递归组件需要在这里注册自己
+  components: {
+    FolderTreeItem: () => import('./FolderTreeItem.vue')
+  },
+  props: {
+    folder: {
+      type: Object,
+      required: true
+    },
+    level: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      isExpanded: false
+    }
+  },
+  computed: {
+    hasChildren() {
+      const result = this.folder.children && this.folder.children.length > 0
+      console.log(`文件夹 ${this.folder.name} hasChildren:`, result, '子节点数:', this.folder.children?.length || 0)
+      return result
+    }
+  },
+  mounted() {
+    console.log('========== FolderTreeItem mounted ==========')
+    console.log('文件夹名称:', this.folder.name)
+    console.log('层级:', this.level)
+    console.log('类型:', this.folder.type)
+    console.log('完整 folder 对象:', this.folder)
+    console.log('children:', this.folder.children)
+    console.log('children 是否为数组:', Array.isArray(this.folder.children))
+    console.log('children 长度:', this.folder.children?.length || 0)
+    console.log('hasChildren 计算结果:', this.hasChildren)
+  },
+  methods: {
+    // 获取文件夹图标
+    getFolderIcon(type) {
+      switch (type) {
+        case 0:
+          // 普通文件夹
+          return '/static/pages/scan/folderSelect/folder.svg'
+        case 1:
+          // 国家
+          return '/static/pages/scan/folderSelect/country.svg'
+        case 2:
+          // 中心
+          return '/static/pages/scan/folderSelect/center.svg'
+        default:
+          return '/static/pages/scan/folderSelect/folder.svg'
+      }
+    },
+    
+    // 切换展开/收起
+    handleToggle() {
+      console.log('========== 点击切换 ==========')
+      console.log('文件夹:', this.folder.name)
+      console.log('当前展开状态:', this.isExpanded)
+      console.log('有子节点:', this.hasChildren)
+      console.log('子节点数量:', this.folder.children?.length || 0)
+      
+      if (this.hasChildren) {
+        this.isExpanded = !this.isExpanded
+        console.log('切换后展开状态:', this.isExpanded)
+        
+        // 强制更新
+        this.$forceUpdate()
+        
+        // 延迟检查子节点是否渲染
+        setTimeout(() => {
+          console.log('延迟检查 - 当前展开状态:', this.isExpanded)
+          console.log('延迟检查 - 子节点:', this.folder.children)
+        }, 100)
+      }
+    },
+    
+    // 选择当前文件夹
+    handleSelect() {
+      this.$emit('select', this.folder)
+    },
+    
+    // 子节点选择事件
+    handleChildSelect(folder) {
+      this.$emit('select', folder)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.folder-tree-item {
+  .folder-item {
+    display: flex;
+    align-items: center;
+    padding: 20rpx 24rpx;
+    border-bottom: 1rpx solid #f5f5f5;
+    
+    &:active {
+      background: #f8f8f8;
+    }
+    
+    .expand-icon {
+      width: 40rpx;
+      height: 40rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-right: 8rpx;
+      
+      &.placeholder {
+        visibility: hidden;
+      }
+      
+      .icon-text {
+        font-size: 20rpx;
+        color: #999999;
+      }
+    }
+    
+    .folder-icon-wrapper {
+      width: 48rpx;
+      height: 48rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-right: 16rpx;
+      
+      .folder-icon {
+        width: 48rpx;
+        height: 48rpx;
+      }
+    }
+    
+    .folder-name {
+      flex: 1;
+      font-size: 28rpx;
+      color: #333333;
+    }
+    
+    .select-btn {
+      padding: 8rpx 24rpx;
+      background: #1ec9c9;
+      border-radius: 24rpx;
+      
+      &:active {
+        background: #1ab8b8;
+      }
+      
+      .select-text {
+        font-size: 24rpx;
+        color: #ffffff;
+        font-weight: 500;
+      }
+    }
+  }
+  
+  .children {
+    background: #fafafa;
+  }
+}
+</style>

+ 319 - 0
pages/scan/folderSelect/index.vue

@@ -0,0 +1,319 @@
+<template>
+  <view class="folder-select-page">
+    <!-- 顶部导航栏 -->
+    <view class="header-bg" :style="{ paddingTop: statusBarHeight + 'px' }">
+      <view class="header-content">
+        <view class="back-btn" @click="handleBack">
+          <text class="back-icon">‹</text>
+        </view>
+        <text class="header-title">选择文件夹</text>
+        <view class="placeholder"></view>
+      </view>
+    </view>
+    
+    <!-- 项目信息 -->
+    <view class="project-info-bar">
+      <text class="project-label">当前项目:</text>
+      <text class="project-name">{{ projectName }}</text>
+    </view>
+    
+    <!-- 文件夹树状列表 -->
+    <scroll-view scroll-y class="folder-list">
+      <!-- 调试信息 -->
+      <view class="debug-info" style="padding: 20rpx; background: #fff3cd; border-bottom: 1rpx solid #ffc107;">
+        <text style="font-size: 24rpx; color: #856404; display: block;">调试信息:</text>
+        <text style="font-size: 24rpx; color: #856404; display: block;">文件夹数量: {{ folderTree.length }}</text>
+        <text style="font-size: 24rpx; color: #856404; display: block;">加载状态: {{ loading ? '加载中' : '已完成' }}</text>
+        <text style="font-size: 24rpx; color: #856404; display: block;">folderTree 是否为数组: {{ Array.isArray(folderTree) ? '是' : '否' }}</text>
+        <view v-if="folderTree.length > 0" style="margin-top: 10rpx;">
+          <text style="font-size: 24rpx; color: #856404; display: block;">第一个文件夹: {{ folderTree[0].name }}</text>
+          <text style="font-size: 24rpx; color: #856404; display: block;">第一个文件夹子节点数: {{ folderTree[0].children?.length || 0 }}</text>
+        </view>
+      </view>
+      
+      <!-- 使用树形组件 -->
+      <tree-view
+        v-if="folderTree.length > 0"
+        :tree-data="folderTree"
+        :permission="permission"
+        @select="handleSelectFolder"
+      />
+      
+      <view v-if="folderTree.length === 0 && !loading" class="empty-tip">
+        <text class="empty-text">暂无文件夹</text>
+      </view>
+      
+      <view v-if="loading" class="loading-tip">
+        <text class="loading-text">加载中...</text>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+import { getFolderList, getFolderPermission } from '@/apis/scan'
+import TreeView from '@/components/TreeView/index.vue'
+
+export default {
+  components: {
+    TreeView
+  },
+  data() {
+    return {
+      statusBarHeight: 0,
+      projectId: 0,
+      projectName: '',
+      folderTree: [],
+      loading: false,
+      permission: '*' // 权限配置
+    }
+  },
+  onLoad(options) {
+    console.log('页面参数:', options)
+    
+    this.projectId = parseInt(options.projectId) || 0
+    this.projectName = decodeURIComponent(options.projectName || '')
+    
+    console.log('解析后的projectId:', this.projectId)
+    console.log('解析后的projectName:', this.projectName)
+  },
+  onReady() {
+    // 获取系统信息
+    const windowInfo = uni.getWindowInfo()
+    this.statusBarHeight = windowInfo.statusBarHeight || 0
+    
+    // 加载数据
+    if (this.projectId) {
+      this.loadPermission()
+      this.loadFolderList()
+    } else {
+      console.error('projectId 为空,无法加载文件夹列表')
+    }
+  },
+  methods: {
+    // 返回上一页
+    handleBack() {
+      uni.navigateBack()
+    },
+    
+    // 加载权限
+    async loadPermission() {
+      try {
+        console.log('========== 开始加载权限 ==========')
+        console.log('projectId:', this.projectId)
+        
+        const response = await getFolderPermission({
+          projectId: this.projectId
+        })
+        
+        console.log('权限响应:', response)
+        
+        if (response.code === 200 && response.data) {
+          this.permission = response.data.content || '*'
+          console.log('权限配置:', this.permission)
+        } else {
+          console.error('权限响应异常:', response)
+          this.permission = '*'
+        }
+      } catch (error) {
+        console.error('加载权限失败:', error)
+        this.permission = '*'
+      }
+    },
+    
+    // 加载文件夹列表
+    async loadFolderList() {
+      if (this.loading) return
+      
+      try {
+        this.loading = true
+        
+        console.log('========== 开始加载文件夹列表 ==========')
+        console.log('projectId:', this.projectId)
+        
+        const response = await getFolderList({
+          projectId: this.projectId
+        })
+        
+        console.log('========== 后端返回的原始数据 ==========')
+        console.log('response:', response)
+        console.log('response.code:', response.code)
+        console.log('response.msg:', response.msg)
+        console.log('response.data:', response.data)
+        console.log('response.data 类型:', typeof response.data)
+        console.log('response.data 是否为数组:', Array.isArray(response.data))
+        
+        if (response.code === 200 && response.data) {
+          this.folderTree = response.data
+          
+          console.log('========== 赋值后的 folderTree ==========')
+          console.log('folderTree:', this.folderTree)
+          console.log('folderTree 长度:', this.folderTree.length)
+          console.log('folderTree 是否为数组:', Array.isArray(this.folderTree))
+          
+          // 打印第一层数据的详细信息
+          this.folderTree.forEach((folder, index) => {
+            console.log(`========== 第 ${index} 个文件夹 ==========`)
+            console.log('完整对象:', folder)
+            console.log('id:', folder.id)
+            console.log('name:', folder.name)
+            console.log('type:', folder.type)
+            console.log('children:', folder.children)
+            console.log('children 类型:', typeof folder.children)
+            console.log('children 是否为数组:', Array.isArray(folder.children))
+            console.log('children 长度:', folder.children?.length || 0)
+            
+            // 如果有子节点,打印第一个子节点
+            if (folder.children && folder.children.length > 0) {
+              console.log('第一个子节点:', folder.children[0])
+            }
+          })
+        } else {
+          console.error('========== 响应异常 ==========')
+          console.error('响应码不是200或没有data')
+          console.error('完整响应:', response)
+        }
+      } catch (error) {
+        console.error('========== 加载失败 ==========')
+        console.error('错误信息:', error)
+        console.error('错误堆栈:', error.stack)
+        uni.showToast({
+          title: '加载失败',
+          icon: 'none'
+        })
+      } finally {
+        this.loading = false
+        console.log('========== 加载完成 ==========')
+      }
+    },
+    
+    // 选择文件夹
+    handleSelectFolder(folder) {
+      console.log('========== 选择文件夹 ==========')
+      console.log('文件夹:', folder.name)
+      console.log('文件夹ID:', folder.id)
+      console.log('完整路径:', folder.fullPath)
+      
+      // 从全局数据中获取扫描的fileBase64List
+      const fileBase64List = getApp().globalData.scannedFileBase64List
+      
+      if (!fileBase64List || fileBase64List.length === 0) {
+        uni.showToast({
+          title: '未找到扫描文件',
+          icon: 'none'
+        })
+        return
+      }
+      
+      // 将文件夹信息存储到全局数据
+      getApp().globalData.selectedFolder = {
+        folderId: folder.id,
+        folderPath: folder.fullPath
+      }
+      
+      // 跳转到编辑上传文件页面
+      uni.navigateTo({
+        url: `/pages/scan/uploadEdit/index?folderId=${folder.id}&folderPath=${encodeURIComponent(folder.fullPath)}`
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.folder-select-page {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f5f5;
+  
+  // 顶部导航栏
+  .header-bg {
+    background: linear-gradient(180deg, #1ec9c9 0%, #1eb8b8 100%);
+    position: relative;
+    z-index: 100;
+    
+    .header-content {
+      height: 88rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 0 24rpx;
+      
+      .back-btn {
+        width: 60rpx;
+        height: 60rpx;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        
+        .back-icon {
+          font-size: 56rpx;
+          color: #ffffff;
+          font-weight: 300;
+        }
+      }
+      
+      .header-title {
+        flex: 1;
+        text-align: center;
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+      
+      .placeholder {
+        width: 60rpx;
+      }
+    }
+  }
+  
+  // 项目信息栏
+  .project-info-bar {
+    padding: 24rpx;
+    background: #ffffff;
+    display: flex;
+    align-items: center;
+    border-bottom: 1rpx solid #f5f5f5;
+    
+    .project-label {
+      font-size: 28rpx;
+      color: #666666;
+      margin-right: 8rpx;
+    }
+    
+    .project-name {
+      font-size: 28rpx;
+      font-weight: 600;
+      color: #1ec9c9;
+    }
+  }
+  
+  // 文件夹列表
+  .folder-list {
+    flex: 1;
+    background: #ffffff;
+  }
+  
+  .empty-tip {
+    padding: 200rpx 0;
+    text-align: center;
+    
+    .empty-text {
+      font-size: 28rpx;
+      color: #999999;
+    }
+  }
+  
+  .loading-tip {
+    padding: 32rpx 0;
+    text-align: center;
+    
+    .loading-text {
+      font-size: 28rpx;
+      color: #999999;
+    }
+  }
+}
+</style>

+ 145 - 26
pages/scan/index.vue

@@ -63,7 +63,7 @@
         </scroll-view>
       </view>
       
-      <!-- 上传按钮(多页模式且有图片时显示-->
+      <!-- 上传按钮(多页模式且有图片时显示�?-->
       <view 
         v-if="scanMode === 'multiple' && imageBase64List.length > 0" 
         class="upload-btn"
@@ -84,7 +84,7 @@
       <!-- 底部操作按钮 -->
       <view class="action-buttons">
         <view class="action-btn" @click="handleSelectImage">
-          <image class="btn-icon" src="/static/pages/scan/image.png" mode="aspectFit" />
+          <image class="btn-icon" src="/static/pages/scan/import-pic.svg" mode="aspectFit" />
           <text class="btn-text">导入图片</text>
         </view>
         
@@ -93,11 +93,12 @@
         </view>
         
         <view class="action-btn" @click="handleSelectFile">
-          <image class="btn-icon" src="/static/pages/scan/file.png" mode="aspectFit" />
+          <image class="btn-icon" src="/static/pages/scan/import-doc.svg" mode="aspectFit" />
           <text class="btn-text">导入文档</text>
         </view>
       </view>
     </view>
+    
   </view>
 </template>
 
@@ -117,10 +118,10 @@ const scanMode = ref('single')
 // 图片base64数组(用于多页模式)
 const imageBase64List = ref([])
 
-// 图片临时路径数组(用于显示缩略图
+// 图片临时路径数组(用于显示缩略图�?
 const imageTempPaths = ref([])
 
-// PDF数据数组(多页模式,包含路径和base64
+// PDF数据数组(多页模式,包含路径和base64�?
 const pdfDataList = ref([])
 
 // 缓存的ossId
@@ -132,6 +133,9 @@ const pdfFilePath = ref('')
 // 是否显示PDF缩略图
 const showPdfThumbnail = ref(false)
 
+// 暂存扫描的PDF base64(用于单页模式)
+const scannedPdfBase64 = ref('')
+
 // 相机上下文
 let cameraContext = null
 
@@ -208,7 +212,7 @@ const handleCapture = () => {
     success: async (res) => {
       try {
         uni.showLoading({
-          title: '处理中...',
+          title: '处理中..',
           mask: true
         })
         
@@ -250,7 +254,7 @@ const handleSelectImage = async () => {
     success: async (res) => {
       try {
         uni.showLoading({
-          title: '处理中...',
+          title: '处理�?..',
           mask: true
         })
         
@@ -276,30 +280,127 @@ const handleSelectImage = async () => {
   })
 }
 
+// 导入文档
 // 导入文档
 const handleSelectFile = () => {
   uni.chooseMessageFile({
     count: 1,
     type: 'file',
-    success: (res) => {
-      uni.showLoading({
-        title: '处理中...',
-        mask: true
-      })
-      
-      // TODO: 上传文件到服务器
-      setTimeout(() => {
-        uni.hideLoading()
+    extension: ['.pdf', '.doc', '.docx'],
+    success: async (res) => {
+      try {
+        uni.showLoading({
+          title: '处理中...',
+          mask: true
+        })
         
+        const file = res.tempFiles[0]
+        
+        // 将文件转换为base64
+        const base64Data = await fileToBase64(file.path)
+        
+        if (scanMode.value === 'single') {
+          // 单页模式:直接处理文档
+          await handleDocumentUpload(base64Data)
+        } else {
+          // 多页模式:添加到列表
+          await handleDocumentInMultipleMode(base64Data, file.name)
+        }
+      } catch (error) {
+        uni.hideLoading()
+        console.error('处理文档失败:', error)
         uni.showToast({
-          title: '文档导入成功',
-          icon: 'success'
+          title: '处理失败',
+          icon: 'none'
         })
-      }, 1000)
+      }
+    },
+    fail: (err) => {
+      console.error('选择文档失败:', err)
+      uni.showToast({
+        title: '选择文档失败',
+        icon: 'none'
+      })
     }
   })
 }
 
+// 将文件转换为base64
+const fileToBase64 = (filePath) => {
+  return new Promise((resolve, reject) => {
+    uni.getFileSystemManager().readFile({
+      filePath: filePath,
+      encoding: 'base64',
+      success: (res) => {
+        resolve(res.data)
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}
+
+// 单页模式处理文档
+const handleDocumentUpload = async (base64Data) => {
+  try {
+    // 直接将文档base64存储到全局数据中(作为数组)
+    getApp().globalData.scannedFileBase64List = [base64Data]
+    
+    uni.hideLoading()
+    
+    uni.showToast({
+      title: '文档导入成功',
+      icon: 'success',
+      duration: 1500
+    })
+    
+    // 延迟跳转到文件选择页面
+    setTimeout(() => {
+      uni.navigateTo({
+        url: '/pages/scan/fileSelect/index'
+      })
+    }, 1500)
+  } catch (error) {
+    uni.hideLoading()
+    console.error('处理文档失败:', error)
+    uni.showToast({
+      title: error.message || '处理失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 多页模式处理文档
+const handleDocumentInMultipleMode = async (base64Data, fileName) => {
+  try {
+    // 生成临时文件路径用于显示
+    const tempPath = await base64ToTempFile(base64Data)
+    
+    // 添加到PDF数据列表
+    pdfDataList.value.push({
+      path: tempPath,
+      base64: base64Data,
+      name: fileName || `文档_${Date.now()}`
+    })
+    
+    uni.hideLoading()
+    
+    uni.showToast({
+      title: `已添加第${pdfDataList.value.length}个文档`,
+      icon: 'success',
+      duration: 1500
+    })
+  } catch (error) {
+    uni.hideLoading()
+    console.error('处理文档失败:', error)
+    uni.showToast({
+      title: error.message || '处理失败',
+      icon: 'none'
+    })
+  }
+}
+
 // 将图片转换为base64
 const imageToBase64 = (filePath) => {
   return new Promise((resolve, reject) => {
@@ -330,9 +431,17 @@ const uploadSingleImage = async (base64Data) => {
       // 缓存ossId
       cachedOssId.value = response.data.ossId
       
-      // 处理PDF预览
+      // 暂存扫描的PDF base64
       if (response.data.fileBase64) {
-        await handlePdfPreview(response.data.fileBase64)
+        scannedPdfBase64.value = response.data.fileBase64
+        
+        // 将fileBase64存储到全局数据中(作为数组)
+        getApp().globalData.scannedFileBase64List = [response.data.fileBase64]
+        
+        // 跳转到文件选择页面
+        uni.navigateTo({
+          url: '/pages/scan/fileSelect/index'
+        })
       } else {
         uni.showToast({
           title: '上传成功',
@@ -421,10 +530,14 @@ const uploadAndScanImage = async (base64Data) => {
       // 将PDF转换为临时文件
       const pdfPath = await base64ToTempFile(response.data.fileBase64)
       
-      // 添加到PDF数据列表(包含路径和base64)
+      // 生成文件名(使用时间戳或从服务器返回的文件名
+      const fileName = response.data.fileName || response.data.originalName || `扫描文档_${Date.now()}`
+      
+      // 添加到PDF数据列表(包含路径、base64和文件名
       pdfDataList.value.push({
         path: pdfPath,
-        base64: response.data.fileBase64
+        base64: response.data.fileBase64,
+        name: fileName
       })
       
       uni.showToast({
@@ -478,7 +591,7 @@ const handleViewAllPdf = () => {
     return
   }
   
-  // 使用全局数据存储PDF数据(包含路径和base64
+  // 使用全局数据存储PDF数据(包含路径和base64�?
   getApp().globalData.pdfData = pdfDataList.value
   
   uni.navigateTo({
@@ -619,6 +732,12 @@ const handleClosePdf = () => {
       position: relative;
     }
     
+    .camera-placeholder {
+      flex: 1;
+      width: 100%;
+      background: #000000;
+    }
+    
     // 模式切换按钮 - 在底部操作栏上方
     .mode-switch-wrapper {
       position: absolute;
@@ -662,7 +781,7 @@ const handleClosePdf = () => {
       }
     }
     
-    // 多页模式缩略图列
+    // 多页模式缩略图列�?
     .thumbnail-list {
       position: absolute;
       bottom: 220rpx;
@@ -877,4 +996,4 @@ const handleClosePdf = () => {
     }
   }
 }
-</style>
+</style>

+ 91 - 22
pages/scan/pdfViewer/index.vue

@@ -7,9 +7,7 @@
           <text class="back-icon">‹</text>
         </view>
         <text class="header-title">PDF管理 (共{{ pdfList.length }}页)</text>
-        <view class="action-btn" @click="handleUploadAll">
-          <text class="action-text">上传</text>
-        </view>
+        <view class="placeholder"></view>
       </view>
     </view>
     
@@ -23,9 +21,9 @@
         <view class="pdf-icon-wrapper">
           <image src="/static/icon/pdf.svg" mode="aspectFit" class="pdf-icon" />
         </view>
-        <view class="pdf-info" @click="handlePreviewPdf(item.path, index)">
-          <text class="pdf-name">{{ index + 1 }}</text>
-          <text class="pdf-tip">点击预览</text>
+        <view class="pdf-info" @click="handlePreviewPdf(item.path, index)" @longpress="handleEditName(index)">
+          <text class="pdf-name">{{ item.name || `文档_${index + 1}` }}</text>
+          <text class="pdf-tip">点击预览 · 长按编辑名称</text>
         </view>
         
         <!-- 操作按钮 -->
@@ -54,6 +52,13 @@
         <text class="empty-text">暂无PDF文档</text>
       </view>
     </scroll-view>
+    
+    <!-- 底部提交按钮 -->
+    <view class="submit-footer">
+      <view class="submit-btn" @click="handleSubmit">
+        <text class="submit-text">提交</text>
+      </view>
+    </view>
   </view>
 </template>
 
@@ -84,7 +89,8 @@ onMounted(() => {
     pdfList.value = app.globalData.pdfData.map(item => ({
       id: generateId(),
       path: item.path,
-      base64: item.base64
+      base64: item.base64,
+      name: item.name || `文档_${Date.now()}`
     }))
   }
 })
@@ -153,7 +159,7 @@ const handleMoveDown = (index) => {
 const handleDelete = (index) => {
   uni.showModal({
     title: '确认删除',
-    content: `确定要删除第${index + 1}页吗?`,
+    content: `确定要删除"${pdfList.value[index].name || `文档_${index + 1}`}"吗?`,
     confirmText: '删除',
     cancelText: '取消',
     success: (res) => {
@@ -170,6 +176,51 @@ const handleDelete = (index) => {
   })
 }
 
+// 编辑文件名
+const handleEditName = (index) => {
+  const currentName = pdfList.value[index].name || `文档_${index + 1}`
+  
+  uni.showModal({
+    title: '编辑文件名',
+    content: currentName,
+    editable: true,
+    placeholderText: '请输入文件名',
+    success: (res) => {
+      if (res.confirm && res.content && res.content.trim()) {
+        pdfList.value[index].name = res.content.trim()
+        
+        uni.showToast({
+          title: '修改成功',
+          icon: 'success',
+          duration: 1000
+        })
+      }
+    }
+  })
+}
+
+// 提交 - 跳转到文件选择页面
+const handleSubmit = () => {
+  if (pdfList.value.length === 0) {
+    uni.showToast({
+      title: '请先添加PDF',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 按顺序提取base64数组
+  const base64Array = pdfList.value.map(item => item.base64)
+  
+  // 将base64数组存储到全局数据中
+  getApp().globalData.scannedFileBase64List = base64Array
+  
+  // 跳转到文件选择页面
+  uni.navigateTo({
+    url: '/pages/scan/fileSelect/index'
+  })
+}
+
 // 上传所有PDF
 const handleUploadAll = async () => {
   if (pdfList.value.length === 0) {
@@ -274,20 +325,8 @@ const handleUploadAll = async () => {
         color: #ffffff;
       }
       
-      .action-btn {
-        width: 80rpx;
-        height: 56rpx;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        background: rgba(255, 255, 255, 0.2);
-        border-radius: 28rpx;
-        
-        .action-text {
-          font-size: 26rpx;
-          color: #ffffff;
-          font-weight: 500;
-        }
+      .placeholder {
+        width: 60rpx;
       }
     }
   }
@@ -401,5 +440,35 @@ const handleUploadAll = async () => {
       }
     }
   }
+  
+  // 底部提交按钮
+  .submit-footer {
+    background: #ffffff;
+    padding: 24rpx 32rpx;
+    padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+    box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
+    
+    .submit-btn {
+      width: 100%;
+      height: 88rpx;
+      background: linear-gradient(135deg, #1ec9c9 0%, #1eb8b8 100%);
+      border-radius: 44rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-shadow: 0 4rpx 16rpx rgba(30, 201, 201, 0.3);
+      
+      &:active {
+        transform: scale(0.98);
+        opacity: 0.9;
+      }
+      
+      .submit-text {
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+    }
+  }
 }
 </style>

+ 394 - 0
pages/scan/projectSelect/index.vue

@@ -0,0 +1,394 @@
+<template>
+  <view class="project-select-page">
+    <!-- 顶部导航栏 -->
+    <view class="header-bg" :style="{ paddingTop: statusBarHeight + 'px' }">
+      <view class="header-content">
+        <view class="back-btn" @click="handleBack">
+          <text class="back-icon">‹</text>
+        </view>
+        <text class="header-title">选择项目</text>
+        <view class="placeholder"></view>
+      </view>
+    </view>
+    
+    <!-- 搜索框 -->
+    <view class="search-bar">
+      <input 
+        class="search-input" 
+        v-model="searchName" 
+        placeholder="搜索项目名称或编号"
+        @confirm="handleSearch"
+      />
+      <view class="search-btn" @click="handleSearch">
+        <text class="search-text">搜索</text>
+      </view>
+    </view>
+    
+    <!-- 项目列表 -->
+    <scroll-view 
+      scroll-y 
+      class="project-list"
+      @scrolltolower="handleLoadMore"
+    >
+      <view 
+        v-for="item in projectList" 
+        :key="item.id" 
+        class="project-item"
+        @click="handleSelectProject(item)"
+      >
+        <view class="project-icon-wrapper">
+          <image 
+            v-if="item.iconUrl" 
+            :src="item.iconUrl" 
+            mode="aspectFit" 
+            class="project-icon" 
+          />
+          <image 
+            v-else 
+            src="/static/icon/project.svg" 
+            mode="aspectFit" 
+            class="project-icon" 
+          />
+        </view>
+        <view class="project-info">
+          <text class="project-name">{{ item.name }}</text>
+          <text class="project-detail">项目编号: {{ item.code }}</text>
+          <text class="project-detail">项目类型: {{ getProjectTypeLabel(item.type) }}</text>
+          <text class="project-detail">开始时间: {{ formatDate(item.startTime) }}</text>
+        </view>
+      </view>
+      
+      <view v-if="projectList.length === 0 && !loading" class="empty-tip">
+        <text class="empty-text">暂无项目</text>
+      </view>
+      
+      <view v-if="loading" class="loading-tip">
+        <text class="loading-text">加载中...</text>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { getProjectList } from '@/apis/scan'
+import { getDictDataByType } from '@/apis/dict'
+
+const { locale } = useI18n()
+
+// 状态栏高度
+const statusBarHeight = ref(0)
+
+// 项目列表
+const projectList = ref([])
+const searchName = ref('')
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const loading = ref(false)
+
+// 字典数据(原始数据)
+const projectTypeDictData = ref([])
+
+// 当前语言
+const currentLocale = computed(() => {
+  // 将 zh-CN 转换为 zh_CN 格式
+  return locale.value.replace('-', '_')
+})
+
+onMounted(() => {
+  // 获取系统信息
+  const windowInfo = uni.getWindowInfo()
+  statusBarHeight.value = windowInfo.statusBarHeight || 0
+  
+  // 加载字典数据
+  loadDictData()
+  
+  // 加载项目列表
+  loadProjectList()
+})
+
+// 加载字典数据
+const loadDictData = async () => {
+  try {
+    const response = await getDictDataByType('project_type')
+    
+    if (response && response.code === 200 && response.data) {
+      projectTypeDictData.value = response.data
+    }
+  } catch (error) {
+    console.error('加载字典数据失败:', error)
+  }
+}
+
+// 解析国际化的字典标签
+const parseDictLabel = (dictLabel) => {
+  try {
+    // 尝试解析 JSON 格式的标签
+    const labelObj = JSON.parse(dictLabel)
+    // 返回当前语言对应的标签,如果不存在则返回中文或第一个可用的值
+    return labelObj[currentLocale.value] || labelObj['zh_CN'] || labelObj['en_US'] || dictLabel
+  } catch (e) {
+    // 如果不是 JSON 格式,直接返回原值
+    return dictLabel
+  }
+}
+
+// 获取项目类型显示文本
+const getProjectTypeLabel = (typeValue) => {
+  const dictItem = projectTypeDictData.value.find(item => item.dictValue === typeValue)
+  if (dictItem) {
+    return parseDictLabel(dictItem.dictLabel)
+  }
+  return typeValue || '-'
+}
+
+// 返回上一页
+const handleBack = () => {
+  uni.navigateBack()
+}
+
+// 加载项目列表
+const loadProjectList = async () => {
+  if (loading.value) return
+  
+  try {
+    loading.value = true
+    
+    const params = {
+      pageNum: pageNum.value,
+      pageSize: pageSize.value
+    }
+    
+    // 搜索关键词可以同时匹配项目名称和编号
+    if (searchName.value && searchName.value.trim()) {
+      params.name = searchName.value.trim()
+      params.code = searchName.value.trim()
+    }
+    
+    const response = await getProjectList(params)
+    
+    if (response.code === 200) {
+      if (pageNum.value === 1) {
+        projectList.value = response.rows || []
+      } else {
+        projectList.value = [...projectList.value, ...(response.rows || [])]
+      }
+      total.value = response.total || 0
+    }
+  } catch (error) {
+    console.error('加载项目列表失败:', error)
+    uni.showToast({
+      title: '加载失败',
+      icon: 'none'
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 搜索
+const handleSearch = () => {
+  // 重置分页
+  pageNum.value = 1
+  projectList.value = []
+  loadProjectList()
+}
+
+// 加载更多
+const handleLoadMore = () => {
+  if (loading.value) return
+  if (projectList.value.length >= total.value) return
+  
+  pageNum.value++
+  loadProjectList()
+}
+
+// 选择项目
+const handleSelectProject = async (project) => {
+  // 从全局数据中获取扫描的fileBase64List
+  const fileBase64List = getApp().globalData.scannedFileBase64List
+  
+  if (!fileBase64List || fileBase64List.length === 0) {
+    uni.showToast({
+      title: '未找到扫描文件',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 跳转到文件夹选择页面
+  uni.navigateTo({
+    url: `/pages/scan/folderSelect/index?projectId=${project.id}&projectName=${encodeURIComponent(project.name)}`
+  })
+}
+
+// 格式化日期(只显示到天)
+const formatDate = (dateStr) => {
+  if (!dateStr) return ''
+  // 如果日期格式是 "2026-01-04 10:58:37",只取前10位
+  return dateStr.split(' ')[0]
+}
+</script>
+
+<style lang="scss" scoped>
+.project-select-page {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f5f5;
+  
+  // 顶部导航栏
+  .header-bg {
+    background: linear-gradient(180deg, #1ec9c9 0%, #1eb8b8 100%);
+    position: relative;
+    z-index: 100;
+    
+    .header-content {
+      height: 88rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 0 24rpx;
+      
+      .back-btn {
+        width: 60rpx;
+        height: 60rpx;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        
+        .back-icon {
+          font-size: 56rpx;
+          color: #ffffff;
+          font-weight: 300;
+        }
+      }
+      
+      .header-title {
+        flex: 1;
+        text-align: center;
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+      
+      .placeholder {
+        width: 60rpx;
+      }
+    }
+  }
+  
+  // 搜索框
+  .search-bar {
+    padding: 24rpx;
+    background: #ffffff;
+    display: flex;
+    gap: 16rpx;
+    
+    .search-input {
+      flex: 1;
+      height: 64rpx;
+      padding: 0 24rpx;
+      background: #f5f5f5;
+      border-radius: 32rpx;
+      font-size: 28rpx;
+    }
+    
+    .search-btn {
+      width: 120rpx;
+      height: 64rpx;
+      background: #1ec9c9;
+      border-radius: 32rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &:active {
+        background: #1ab8b8;
+      }
+      
+      .search-text {
+        font-size: 28rpx;
+        color: #ffffff;
+        font-weight: 500;
+      }
+    }
+  }
+  
+  // 项目列表
+  .project-list {
+    flex: 1;
+    background: #ffffff;
+    
+    .project-item {
+      padding: 24rpx;
+      display: flex;
+      align-items: center;
+      border-bottom: 1rpx solid #f5f5f5;
+      
+      &:active {
+        background: #f8f8f8;
+      }
+      
+      .project-icon-wrapper {
+        width: 80rpx;
+        height: 80rpx;
+        background: #f5f5f5;
+        border-radius: 12rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 24rpx;
+        
+        .project-icon {
+          width: 48rpx;
+          height: 48rpx;
+        }
+      }
+      
+      .project-info {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        gap: 6rpx;
+        
+        .project-name {
+          font-size: 32rpx;
+          font-weight: 600;
+          color: #333333;
+          margin-bottom: 4rpx;
+        }
+        
+        .project-detail {
+          font-size: 26rpx;
+          color: #666666;
+          line-height: 1.5;
+        }
+      }
+    }
+    
+    .empty-tip {
+      padding: 200rpx 0;
+      text-align: center;
+      
+      .empty-text {
+        font-size: 28rpx;
+        color: #999999;
+      }
+    }
+    
+    .loading-tip {
+      padding: 32rpx 0;
+      text-align: center;
+      
+      .loading-text {
+        font-size: 28rpx;
+        color: #999999;
+      }
+    }
+  }
+}
+</style>

+ 802 - 0
pages/scan/uploadEdit/index.vue

@@ -0,0 +1,802 @@
+<template>
+  <view class="upload-edit-page">
+    <!-- 顶部导航栏 -->
+    <view class="header-bg" :style="{ paddingTop: statusBarHeight + 'px' }">
+      <view class="header-content">
+        <view class="back-btn" @click="handleBack">
+          <text class="back-icon">‹</text>
+        </view>
+        <text class="header-title">编辑上传文件</text>
+        <view class="placeholder"></view>
+      </view>
+    </view>
+    
+    <!-- 页面内容 -->
+    <scroll-view scroll-y class="page-content">
+      <!-- 文件夹信息卡片 -->
+      <view class="info-card">
+        <view class="info-row">
+          <text class="info-label">文件夹路径:</text>
+          <text class="info-value">{{ folderPath }}</text>
+        </view>
+      </view>
+      
+      <!-- 文件列表 -->
+      <view class="file-list-section">
+        <view class="section-title">文件列表 ({{ fileList.length }})</view>
+        <view class="file-list">
+          <view 
+            v-for="(file, index) in fileList" 
+            :key="index"
+            class="file-item"
+          >
+            <view class="file-main" @click="handlePreview(file, index)">
+              <view class="file-icon-wrapper">
+                <image src="/static/icon/pdf.svg" mode="aspectFit" class="file-icon" />
+              </view>
+              <view class="file-info">
+                <text class="file-name">{{ file.name }}</text>
+                <text class="file-tip">点击预览</text>
+              </view>
+            </view>
+            
+            <!-- 操作按钮 -->
+            <view class="file-actions">
+              <view 
+                v-if="index > 0" 
+                class="action-icon" 
+                @click="handleMoveUp(index)"
+              >
+                <text class="icon-text">↑</text>
+              </view>
+              <view 
+                v-if="index < fileList.length - 1" 
+                class="action-icon" 
+                @click="handleMoveDown(index)"
+              >
+                <text class="icon-text">↓</text>
+              </view>
+              <view class="action-icon delete" @click="handleDelete(index)">
+                <text class="icon-text">×</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+    
+    <!-- 底部操作按钮 -->
+    <view class="footer-actions">
+      <view class="action-btn secondary" @click="handleCancel">
+        <text class="action-text">取消</text>
+      </view>
+      <view class="action-btn primary" @click="handleUpload">
+        <text class="action-text">上传</text>
+      </view>
+    </view>
+    
+    <!-- 上传弹出框 -->
+    <view v-if="showUploadDialog" class="dialog-overlay" @click="closeDialog">
+      <view class="dialog-content" @click.stop>
+        <view class="dialog-header">
+          <text class="dialog-title">上传文件</text>
+          <view class="dialog-close" @click="closeDialog">
+            <text class="close-icon">×</text>
+          </view>
+        </view>
+        
+        <view class="dialog-body">
+          <view class="form-item">
+            <text class="form-label">文件名</text>
+            <input 
+              class="form-input" 
+              v-model="uploadForm.fileName" 
+              placeholder="请输入文件名"
+              maxlength="100"
+            />
+          </view>
+          
+          <view class="form-item">
+            <text class="form-label">备注</text>
+            <textarea 
+              class="form-textarea" 
+              v-model="uploadForm.note" 
+              placeholder="请输入备注(可选)"
+              maxlength="500"
+              :auto-height="true"
+            />
+          </view>
+        </view>
+        
+        <view class="dialog-footer">
+          <view class="dialog-btn cancel" @click="closeDialog">
+            <text class="btn-text">取消</text>
+          </view>
+          <view class="dialog-btn confirm" @click="confirmUpload">
+            <text class="btn-text">确认</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { uploadNew } from '@/apis/scan.js'
+
+export default {
+  data() {
+    return {
+      statusBarHeight: 0,
+      folderId: 0,
+      projectId: 0,
+      folderPath: '',
+      fileList: [], // 文件列表,每个元素包含 { base64, name, path }
+      showUploadDialog: false,
+      uploadForm: {
+        fileName: '',
+        note: ''
+      }
+    }
+  },
+  onLoad(options) {
+    console.log('========== 编辑上传文件页面 ==========')
+    console.log('页面参数:', options)
+    
+    this.folderId = parseInt(options.folderId) || 0
+    this.projectId = parseInt(options.projectId) || 0
+    this.folderPath = decodeURIComponent(options.folderPath || '')
+    
+    console.log('文件夹ID:', this.folderId)
+    console.log('项目ID:', this.projectId)
+    console.log('文件夹路径:', this.folderPath)
+  },
+  onReady() {
+    // 获取系统信息
+    const windowInfo = uni.getWindowInfo()
+    this.statusBarHeight = windowInfo.statusBarHeight || 0
+    
+    // 从全局数据获取文件列表
+    const fileBase64List = getApp().globalData.scannedFileBase64List
+    const pdfData = getApp().globalData.pdfData
+    
+    console.log('fileBase64List:', fileBase64List)
+    console.log('pdfData:', pdfData)
+    
+    if (fileBase64List && fileBase64List.length > 0) {
+      // 如果有 pdfData(包含文件名和路径),使用它
+      if (pdfData && pdfData.length > 0) {
+        this.fileList = pdfData.map((item, index) => ({
+          base64: item.base64 || fileBase64List[index],
+          name: item.name || `文件_${index + 1}`,
+          path: item.path
+        }))
+      } else {
+        // 否则只有 base64 数据
+        this.fileList = fileBase64List.map((base64, index) => ({
+          base64: base64,
+          name: `文件_${index + 1}`,
+          path: null
+        }))
+      }
+      console.log('文件列表:', this.fileList)
+    } else {
+      console.error('未找到文件列表')
+      uni.showToast({
+        title: '未找到文件',
+        icon: 'none'
+      })
+    }
+  },
+  methods: {
+    // 返回上一页
+    handleBack() {
+      uni.navigateBack()
+    },
+    
+    // 取消
+    handleCancel() {
+      uni.showModal({
+        title: '提示',
+        content: '确定要取消上传吗?',
+        confirmText: '确定',
+        cancelText: '继续编辑',
+        success: (res) => {
+          if (res.confirm) {
+            // 清除全局数据
+            getApp().globalData.scannedFileBase64List = null
+            getApp().globalData.selectedFolder = null
+            
+            // 返回首页
+            uni.reLaunch({
+              url: '/pages/home/index'
+            })
+          }
+        }
+      })
+    },
+    
+    // 上传
+    handleUpload() {
+      if (this.fileList.length === 0) {
+        uni.showToast({
+          title: '没有可上传的文件',
+          icon: 'none'
+        })
+        return
+      }
+      
+      // 显示上传弹出框
+      this.showUploadDialog = true
+    },
+    
+    // 关闭弹出框
+    closeDialog() {
+      this.showUploadDialog = false
+      this.uploadForm = {
+        fileName: '',
+        note: ''
+      }
+    },
+    
+    // 确认上传
+    async confirmUpload() {
+      // 验证文件名
+      if (!this.uploadForm.fileName.trim()) {
+        uni.showToast({
+          title: '请输入文件名',
+          icon: 'none'
+        })
+        return
+      }
+      
+      console.log('========== 开始上传 ==========')
+      console.log('文件夹ID:', this.folderId)
+      console.log('项目ID:', this.projectId)
+      console.log('文件名:', this.uploadForm.fileName)
+      console.log('备注:', this.uploadForm.note)
+      console.log('文件数量:', this.fileList.length)
+      
+      // 准备上传数据(在关闭弹出框之前)
+      const uploadData = {
+        folderId: this.folderId,
+        fileName: this.uploadForm.fileName.trim(),
+        projectId: this.projectId,
+        note: this.uploadForm.note.trim(),
+        files: this.fileList.map(item => item.base64)
+      }
+      
+      // 关闭弹出框
+      this.closeDialog()
+      
+      // 显示加载提示
+      uni.showLoading({
+        title: '上传中...',
+        mask: true
+      })
+      
+      try {
+        // 调用上传接口
+        await uploadNew(uploadData)
+        
+        uni.hideLoading()
+        
+        uni.showToast({
+          title: '上传成功',
+          icon: 'success',
+          duration: 2000
+        })
+        
+        // 清除全局数据
+        getApp().globalData.scannedFileBase64List = null
+        getApp().globalData.selectedFolder = null
+        getApp().globalData.pdfData = null
+        
+        // 返回首页
+        setTimeout(() => {
+          uni.reLaunch({
+            url: '/pages/home/index'
+          })
+        }, 2000)
+      } catch (error) {
+        console.error('上传失败:', error)
+        uni.hideLoading()
+        
+        uni.showToast({
+          title: error.msg || '上传失败',
+          icon: 'none',
+          duration: 2000
+        })
+      }
+    },
+    
+    // 预览文件
+    // 预览文件
+    handlePreview(file, index) {
+      console.log('预览文件:', file.name)
+      console.log('文件索引:', index)
+      
+      if (!file.base64) {
+        uni.showToast({
+          title: '文件数据异常',
+          icon: 'none'
+        })
+        return
+      }
+      
+      // 将 base64 转换为 Data URL
+      const dataUrl = `data:application/pdf;base64,${file.base64}`
+      
+      // 跳转到文档查看器页面
+      uni.navigateTo({
+        url: `/pages/home/documentViewer/index?name=${encodeURIComponent(file.name)}&url=${encodeURIComponent(dataUrl)}`
+      })
+    },
+    
+    // 上移文件
+    handleMoveUp(index) {
+      if (index === 0) return
+      
+      const temp = this.fileList[index]
+      this.fileList[index] = this.fileList[index - 1]
+      this.fileList[index - 1] = temp
+      
+      // 触发响应式更新
+      this.fileList = [...this.fileList]
+      
+      uni.showToast({
+        title: '已上移',
+        icon: 'success',
+        duration: 1000
+      })
+    },
+    
+    // 下移文件
+    handleMoveDown(index) {
+      if (index === this.fileList.length - 1) return
+      
+      const temp = this.fileList[index]
+      this.fileList[index] = this.fileList[index + 1]
+      this.fileList[index + 1] = temp
+      
+      // 触发响应式更新
+      this.fileList = [...this.fileList]
+      
+      uni.showToast({
+        title: '已下移',
+        icon: 'success',
+        duration: 1000
+      })
+    },
+    
+    // 删除文件
+    handleDelete(index) {
+      // 不允许删除最后一个文件
+      if (this.fileList.length <= 1) {
+        uni.showToast({
+          title: '至少保留一个文件',
+          icon: 'none',
+          duration: 2000
+        })
+        return
+      }
+      
+      uni.showModal({
+        title: '确认删除',
+        content: `确定要删除"${this.fileList[index].name}"吗?`,
+        confirmText: '删除',
+        cancelText: '取消',
+        success: (res) => {
+          if (res.confirm) {
+            this.fileList.splice(index, 1)
+            
+            uni.showToast({
+              title: '已删除',
+              icon: 'success',
+              duration: 1000
+            })
+          }
+        }
+      })
+    },
+    
+    // 格式化文件大小
+    formatFileSize(bytes) {
+      if (!bytes) return '0 B'
+      
+      const k = 1024
+      const sizes = ['B', 'KB', 'MB', 'GB']
+      const i = Math.floor(Math.log(bytes) / Math.log(k))
+      
+      return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.upload-edit-page {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f5f5;
+  
+  // 顶部导航栏
+  .header-bg {
+    background: linear-gradient(180deg, #1ec9c9 0%, #1eb8b8 100%);
+    position: relative;
+    z-index: 100;
+    
+    .header-content {
+      height: 88rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 0 24rpx;
+      
+      .back-btn {
+        width: 60rpx;
+        height: 60rpx;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        
+        .back-icon {
+          font-size: 56rpx;
+          color: #ffffff;
+          font-weight: 300;
+        }
+      }
+      
+      .header-title {
+        flex: 1;
+        text-align: center;
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+      
+      .placeholder {
+        width: 60rpx;
+      }
+    }
+  }
+  
+  // 页面内容
+  .page-content {
+    flex: 1;
+    padding: 24rpx;
+    
+    // 信息卡片
+    .info-card {
+      background: #ffffff;
+      border-radius: 16rpx;
+      padding: 32rpx 24rpx;
+      margin-bottom: 24rpx;
+      box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+      
+      .info-row {
+        display: flex;
+        align-items: flex-start;
+        margin-bottom: 20rpx;
+        
+        &:last-child {
+          margin-bottom: 0;
+        }
+        
+        .info-label {
+          font-size: 28rpx;
+          color: #666666;
+          min-width: 180rpx;
+        }
+        
+        .info-value {
+          flex: 1;
+          font-size: 28rpx;
+          color: #333333;
+          font-weight: 500;
+          word-break: break-all;
+        }
+      }
+    }
+    
+    // 文件列表区域
+    .file-list-section {
+      .section-title {
+        font-size: 30rpx;
+        font-weight: 600;
+        color: #333333;
+        margin-bottom: 16rpx;
+      }
+      
+      .file-list {
+        background: #ffffff;
+        border-radius: 16rpx;
+        overflow: hidden;
+        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+        
+        .file-item {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 24rpx;
+          border-bottom: 1rpx solid #f5f5f5;
+          
+          &:last-child {
+            border-bottom: none;
+          }
+          
+          .file-main {
+            flex: 1;
+            display: flex;
+            align-items: center;
+            
+            &:active {
+              opacity: 0.7;
+            }
+            
+            .file-icon-wrapper {
+              width: 64rpx;
+              height: 64rpx;
+              background: #f5f5f5;
+              border-radius: 12rpx;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              margin-right: 16rpx;
+              
+              .file-icon {
+                width: 40rpx;
+                height: 40rpx;
+              }
+            }
+            
+            .file-info {
+              flex: 1;
+              display: flex;
+              flex-direction: column;
+              gap: 8rpx;
+              
+              .file-name {
+                font-size: 28rpx;
+                color: #333333;
+                font-weight: 500;
+              }
+              
+              .file-tip {
+                font-size: 24rpx;
+                color: #999999;
+              }
+            }
+          }
+          
+          .file-actions {
+            display: flex;
+            align-items: center;
+            gap: 12rpx;
+            margin-left: 16rpx;
+            
+            .action-icon {
+              width: 56rpx;
+              height: 56rpx;
+              border-radius: 50%;
+              background: #f5f5f5;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              
+              &:active {
+                background: #e0e0e0;
+              }
+              
+              &.delete {
+                background: #ffe5e5;
+                
+                &:active {
+                  background: #ffcccc;
+                }
+                
+                .icon-text {
+                  color: #ff4444;
+                  font-size: 40rpx;
+                }
+              }
+              
+              .icon-text {
+                font-size: 32rpx;
+                color: #666666;
+                font-weight: 600;
+                line-height: 1;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  
+  // 底部操作按钮
+  .footer-actions {
+    background: #ffffff;
+    padding: 24rpx 32rpx;
+    padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+    box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
+    display: flex;
+    gap: 16rpx;
+    
+    .action-btn {
+      flex: 1;
+      height: 88rpx;
+      border-radius: 44rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &:active {
+        transform: scale(0.98);
+        opacity: 0.9;
+      }
+      
+      &.secondary {
+        background: #ffffff;
+        border: 2rpx solid #1ec9c9;
+        
+        .action-text {
+          color: #1ec9c9;
+        }
+      }
+      
+      &.primary {
+        background: linear-gradient(135deg, #1ec9c9 0%, #1eb8b8 100%);
+        box-shadow: 0 4rpx 16rpx rgba(30, 201, 201, 0.3);
+        
+        .action-text {
+          color: #ffffff;
+        }
+      }
+      
+      .action-text {
+        font-size: 32rpx;
+        font-weight: 600;
+      }
+    }
+  }
+  
+  // 弹出框遮罩
+  .dialog-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+    
+    .dialog-content {
+      width: 600rpx;
+      background: #ffffff;
+      border-radius: 24rpx;
+      overflow: hidden;
+      
+      .dialog-header {
+        padding: 32rpx 32rpx 24rpx;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        border-bottom: 1rpx solid #f5f5f5;
+        
+        .dialog-title {
+          font-size: 32rpx;
+          font-weight: 600;
+          color: #333333;
+        }
+        
+        .dialog-close {
+          width: 48rpx;
+          height: 48rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          
+          &:active {
+            opacity: 0.7;
+          }
+          
+          .close-icon {
+            font-size: 48rpx;
+            color: #999999;
+            line-height: 1;
+          }
+        }
+      }
+      
+      .dialog-body {
+        padding: 32rpx;
+        
+        .form-item {
+          margin-bottom: 32rpx;
+          
+          &:last-child {
+            margin-bottom: 0;
+          }
+          
+          .form-label {
+            display: block;
+            font-size: 28rpx;
+            color: #333333;
+            margin-bottom: 16rpx;
+            font-weight: 500;
+          }
+          
+          .form-input {
+            width: 100%;
+            height: 80rpx;
+            padding: 0 24rpx;
+            background: #f5f5f5;
+            border-radius: 12rpx;
+            font-size: 28rpx;
+            color: #333333;
+            box-sizing: border-box;
+          }
+          
+          .form-textarea {
+            width: 100%;
+            min-height: 120rpx;
+            padding: 16rpx 24rpx;
+            background: #f5f5f5;
+            border-radius: 12rpx;
+            font-size: 28rpx;
+            color: #333333;
+            box-sizing: border-box;
+          }
+        }
+      }
+      
+      .dialog-footer {
+        padding: 24rpx 32rpx 32rpx;
+        display: flex;
+        gap: 16rpx;
+        
+        .dialog-btn {
+          flex: 1;
+          height: 80rpx;
+          border-radius: 40rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          
+          &:active {
+            transform: scale(0.98);
+            opacity: 0.9;
+          }
+          
+          &.cancel {
+            background: #f5f5f5;
+            
+            .btn-text {
+              color: #666666;
+            }
+          }
+          
+          &.confirm {
+            background: linear-gradient(135deg, #1ec9c9 0%, #1eb8b8 100%);
+            
+            .btn-text {
+              color: #ffffff;
+            }
+          }
+          
+          .btn-text {
+            font-size: 30rpx;
+            font-weight: 600;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
static/pages/home/smart-scan.svg


+ 1 - 0
static/pages/my/toAudit.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767592152622" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7103" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M361.699556 921.6H91.022222V102.4h757.76v223.004444H796.444444v-170.666666H143.36v714.524444h218.339556V921.6z" fill="#1ec9c9" p-id="7104"></path><path d="M242.346667 562.062222h159.288889V614.4h-159.288889zM242.346667 417.564444h244.963555v52.337778H242.346667zM242.346667 273.066667h455.111111v52.337777h-455.111111zM807.822222 731.591111H667.875556V591.644444h52.337777v87.608889H807.822222v52.337778z" fill="#1ec9c9" p-id="7105"></path><path d="M705.422222 518.826667A175.217778 175.217778 0 1 1 530.204444 694.044444 175.445333 175.445333 0 0 1 705.422222 518.826667m0-52.337778a227.555556 227.555556 0 1 0 227.555556 227.555555 227.555556 227.555556 0 0 0-227.555556-227.555555z" fill="#1ec9c9" p-id="7106"></path></svg>

+ 1 - 0
static/pages/my/toSubmit.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767592097171" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5960" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M697.546 0.32a95.33 95.33 0 0 1 95.267 95.074v380.233a291.173 291.173 0 0 0-174.666 57.838l0.192-2.303a23.8 23.8 0 0 0-23.864-23.8H198.502a23.8 23.8 0 1 0 0 47.6h393.99a292.836 292.836 0 0 0-73.77 110.75H198.438a23.8 23.8 0 1 0 0 47.665h306.977a291.237 291.237 0 0 0 59.053 237.494H95.495A95.33 95.33 0 0 1 0.228 855.86V301.538a46.705 46.705 0 0 1 47.666-47.601h158.03a46.641 46.641 0 0 0 47.601-47.665V47.985c0-26.36 21.37-47.665 47.665-47.665zM594.54 348.883h-253.49a23.8 23.8 0 0 0 0 47.601h253.49a23.8 23.8 0 0 0 0-47.601zM182.699 20.921c12.604-12.668 22.201-7.997 22.201 9.47v129.751a46.641 46.641 0 0 1-47.665 47.665H35.29c-17.466 0-22.2-11.068-11.132-23.8z" fill="#1ec9c9" p-id="5961"></path><path d="M875.027 771.535a30.71 30.71 0 1 0 61.293 0 30.71 30.71 0 0 0-61.293 0z m-109.662 0a30.71 30.71 0 1 0 61.293 0 30.71 30.71 0 0 0-61.293 0z m-109.661 0a30.71 30.71 0 1 0 61.292 0 30.71 30.71 0 0 0-61.292 0z m137.109-222.97a219.387 219.387 0 1 1 0 438.71 219.387 219.387 0 0 1 0-438.71z" fill="#1ec9c9" p-id="5962"></path></svg>

+ 1 - 0
static/pages/scan/folderSelect/center.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767582445258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3289" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M417.6 900.4h200v1.8h-200zM369.6 962.2h296c2.3 0 4.5-0.7 6.3-1.8H363.3c1.9 1.2 4 1.8 6.3 1.8z" fill="#1ec9c9" p-id="3290"></path><path d="M912.4 901h-40.7V379.4c0-22.3-18.1-40.4-40.4-40.4H660.7v-86.2c0-6.6-5.4-12-12-12h-65.6v-72.2c0-6.6-5.4-12-12-12h-40.2c0.4-1.9 0.6-4 0.6-6V92.5c0-16.6-13.4-30-30-30s-30 13.4-30 30v58.1c0 2.1 0.2 4.1 0.6 6h-41.7c-6.6 0-12 5.4-12 12v72.2h-65.6c-6.6 0-12 5.4-12 12V339H192c-22.2 0-40.3 18.1-40.3 40.3V901H111c-16.5 0-30 13.5-30 30s13.5 30 30 30h801.3c16.5 0 30-13.5 30-30s-13.4-30-29.9-30z m-656.2-1.9h-44.5v-500h44.5v500z m84.5 0h-44.5v-500h44.5v500z m127.7-692.4h64.7v34.2h-64.7v-34.2z m132.3 132.4v561.8h-200v-600h200v38.2z m116.8 560h-56.7v-500h56.7v500z m94.3 0h-54.3v-500h54.3v500z" fill="#1ec9c9" p-id="3291"></path><path d="M449.8 371.8h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 441.5h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 512.1h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 581.8h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 653.8h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 723.5h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 794.1h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20zM449.8 863.8h100c11 0 20-9 20-20s-9-20-20-20h-100c-11 0-20 9-20 20s9 20 20 20z" fill="#1ec9c9" p-id="3292"></path></svg>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
static/pages/scan/folderSelect/country.svg


+ 1 - 0
static/pages/scan/folderSelect/folder.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767582468463" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4408" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M912 208H427.872l-50.368-94.176A63.936 63.936 0 0 0 321.056 80H112c-35.296 0-64 28.704-64 64v736c0 35.296 28.704 64 64 64h800c35.296 0 64-28.704 64-64v-608c0-35.296-28.704-64-64-64z m-800-64h209.056l68.448 128H912v97.984c-0.416 0-0.8-0.128-1.216-0.128H113.248c-0.416 0-0.8 0.128-1.248 0.128V144z m0 736v-96l1.248-350.144 798.752 1.216V784h0.064v96H112z" fill="#1ec9c9" p-id="4409"></path></svg>

+ 1 - 0
static/pages/scan/import-doc.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1767686077094" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2866" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M624.3584 0c11.776 0 23.1168 4.6848 31.488 13.056l285.5424 285.5424c8.3456 8.3456 13.056 19.6864 13.056 31.488v560.3584A133.5552 133.5552 0 0 1 820.864 1024H449.536a133.9392 133.9392 0 0 0 52.1216-66.7904h319.232a66.7904 66.7904 0 0 0 66.7904-66.7648v-534.272h-189.2096A100.1728 100.1728 0 0 1 598.272 256V66.7904H197.5552A66.7904 66.7904 0 0 0 130.816 133.5552V512h-22.272c-15.1552 0-30.208 2.56-44.5184 7.6032V133.5552A133.5552 133.5552 0 0 1 197.5552 0zM354.7392 589.9008a32.1792 32.1792 0 0 1 32.0512 32.1024v205.696c0 19.0464-23.04 28.5952-36.5056 15.104l-67.2256-67.2-39.424 40.192-1.1264 1.3568c-20.0704 24.1152-40.7808 48.9728-40.7808 84.2752 0 36.5568 17.5872 55.1168 34.7648 67.2768 4.2496 2.9952 2.5856 9.8048-2.5856 10.112-41.8304 2.3552-74.1632-0.3328-113.3312-30.5152-34.2272-26.368-49.5104-102.6048-14.1568-157.5168 31.3856-48.8192 57.088-76.032 84.224-104.8064L192 684.5184l-58.112-58.112c-13.4912-13.4656-3.9168-36.5056 15.104-36.5056h205.7472z m354.816 155.8272a33.3824 33.3824 0 1 1 0 66.816h-200.3456v-66.816z m0-155.8272a33.3824 33.3824 0 1 1 0 66.816h-200.3456v-11.1616a132.864 132.864 0 0 0-12.1088-55.6544zM442.4448 267.136a66.7904 66.7904 0 0 1 66.7648 66.7648v111.3088A66.7904 66.7904 0 0 1 442.4448 512h-111.3088a66.7904 66.7904 0 0 1-66.7904-66.7904v-111.3088a66.7904 66.7904 0 0 1 66.7904-66.7648h111.3088z m0 66.7648h-111.3088v111.3088h111.3088v-111.3088z m222.592-217.1904V256c0 18.432 14.9504 33.3824 33.408 33.3824h139.264L665.0368 116.736z" p-id="2867" fill="#1abbbb"></path></svg>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
static/pages/scan/import-pic.svg


+ 4 - 3
utils/request.js

@@ -7,6 +7,7 @@ import { t } from './i18n.js'
 const CLIENT_ID = '2f847927afb2b3ebeefc870c13d623f2'
 
 // 基础 URL(根据实际情况修改)
+// const BASE_URL = 'https://www.production.com/api'
 const BASE_URL = 'http://192.168.1.118:8080'
 
 /**
@@ -107,13 +108,13 @@ const request = (options) => {
             break
             
           case 500:
-            // 系统错误
+            // 系统错误 - 直接显示后端返回的 msg
             uni.showToast({
-              title: t('common.error.serverError'),
+              title: msg || t('common.error.serverError'),
               icon: 'none',
               duration: 2000
             })
-            reject({ code, msg: t('common.error.serverError') })
+            reject({ code, msg: msg || t('common.error.serverError') })
             break
             
           case 501:

binární
修改性别.png


binární
修改昵称.png


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů