Ver Fonte

上传逻辑重写

Huanyi há 3 meses atrás
pai
commit
61b70e1699

+ 0 - 100
README.md

@@ -1,100 +0,0 @@
-# 智能ETMF系统小程序
-
-基于 Vue3 + uni-app 开发的微信小程序项目。
-
-## 技术栈
-
-- **框架**: Vue 3.4+ (Composition API)
-- **构建工具**: Vite 5
-- **跨平台框架**: uni-app
-- **状态管理**: Pinia
-- **样式**: SCSS
-- **Node 版本**: >=18.0.0 (推荐 Node 22)
-- **包管理器**: npm >=9.0.0
-
-## 项目结构
-
-```
-intelligent-etmf-system-applet/
-├── pages/              # 页面目录
-│   └── index/         # 首页
-├── static/            # 静态资源
-│   └── tabbar/       # 底部导航栏图标
-├── App.vue            # 应用根组件
-├── main.js            # 入口文件
-├── manifest.json      # 应用配置
-├── pages.json         # 页面路由配置
-├── uni.scss           # 全局样式变量
-├── vite.config.js     # Vite配置
-└── package.json       # 依赖配置
-```
-
-## 开发环境要求
-
-- **Node.js**: 18.0.0 或更高版本(当前项目基于 Node 22)
-- **npm**: 9.0.0 或更高版本
-
-验证版本:
-```bash
-node -v  # 应该显示 v22.x.x
-npm -v   # 应该显示 9.x.x 或更高
-```
-
-## 开发指南
-
-### 安装依赖
-
-```bash
-npm install
-```
-
-### 运行项目
-
-开发微信小程序:
-```bash
-npm run dev:mp-weixin
-```
-
-开发H5:
-```bash
-npm run dev:h5
-```
-
-### 构建项目
-
-构建微信小程序:
-```bash
-npm run build:mp-weixin
-```
-
-构建H5:
-```bash
-npm run build:h5
-```
-
-## 微信小程序配置
-
-1. 在 `manifest.json` 中配置你的微信小程序 `appid`
-2. 使用微信开发者工具打开 `dist/dev/mp-weixin` 目录
-3. 开始调试和预览
-
-## 注意事项
-
-- 使用 Vue 3 Composition API 进行开发
-- 遵循 uni-app 开发规范
-- 静态资源放在 `static` 目录
-- 全局样式变量定义在 `uni.scss`
-
-## 开发建议
-
-1. **组件化开发**: 将可复用的UI组件放在 `components` 目录
-2. **状态管理**: 项目已集成 Pinia,适合 Vue 3 的现代状态管理方案
-3. **API管理**: 建议创建 `api` 目录统一管理接口
-4. **工具函数**: 创建 `utils` 目录存放公共方法
-5. **npm 配置**: 项目根目录的 `.npmrc` 文件包含 npm 配置,可根据需要调整镜像源
-
-## 相关文档
-
-- [uni-app 官方文档](https://uniapp.dcloud.net.cn/)
-- [Vue 3 文档](https://cn.vuejs.org/)
-- [微信小程序文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)

+ 15 - 0
apis/auth.js

@@ -133,6 +133,21 @@ export const updateGender = (data) => {
   })
 }
 
+/**
+ * 修改密码
+ * @param {Object} data - 密码数据
+ * @param {String} data.oldPassword - 旧密码
+ * @param {String} data.newPassword - 新密码
+ * @returns {Promise}
+ */
+export const updatePassword = (data) => {
+  return request({
+    url: '/applet/my/info/edit/password',
+    method: 'PUT',
+    data
+  })
+}
+
 /**
  * 上传文件到OSS
  * @param {String} filePath - 文件路径

+ 49 - 0
config/app.js

@@ -0,0 +1,49 @@
+/**
+ * 应用全局配置
+ * 注意:这些配置值会在运行时通过国际化系统动态获取
+ * 实际使用时请使用 t('common.app.fullName') 等方式获取国际化后的值
+ */
+
+import i18n from '@/i18n/index'
+
+/**
+ * 获取当前语言的应用名称
+ * @returns {String} 应用全名
+ */
+export const getAppName = () => {
+  return i18n.global.t('common.app.fullName')
+}
+
+/**
+ * 获取当前语言的应用简称
+ * @returns {String} 应用简称
+ */
+export const getAppShortName = () => {
+  return i18n.global.t('common.app.shortName')
+}
+
+/**
+ * 获取当前语言的小程序名称
+ * @returns {String} 小程序名称
+ */
+export const getMiniProgramName = () => {
+  return i18n.global.t('common.app.miniProgramName')
+}
+
+// 应用名称(默认值,建议使用上面的函数获取国际化后的值)
+export const APP_NAME = '道修远数智eTMF'
+
+// 应用简称(默认值,建议使用上面的函数获取国际化后的值)
+export const APP_SHORT_NAME = '数智eTMF'
+
+// 小程序名称(默认值,建议使用上面的函数获取国际化后的值)
+export const MINI_PROGRAM_NAME = '道修远数智eTMF'
+
+export default {
+  APP_NAME,
+  APP_SHORT_NAME,
+  MINI_PROGRAM_NAME,
+  getAppName,
+  getAppShortName,
+  getMiniProgramName
+}

+ 5 - 1
locales/common/en_US.js

@@ -1,7 +1,11 @@
 export default {
   app: {
     name: 'Intelligent eTMF System',
-    welcome: 'Welcome'
+    welcome: 'Welcome',
+    // Application title configuration
+    fullName: 'Daoxiuyuan Digital eTMF',
+    shortName: 'Digital eTMF',
+    miniProgramName: 'Daoxiuyuan Digital eTMF'
   },
   button: {
     confirm: 'Confirm',

+ 5 - 1
locales/common/zh_CN.js

@@ -1,7 +1,11 @@
 export default {
   app: {
     name: '智能eTMF系统',
-    welcome: '欢迎使用'
+    welcome: '欢迎使用',
+    // 应用标题配置
+    fullName: '道修远数智eTMF',
+    shortName: '数智eTMF',
+    miniProgramName: '道修远数智eTMF'
   },
   button: {
     confirm: '确定',

+ 3 - 1
locales/pages/login/en_US.js

@@ -1,6 +1,8 @@
+import { APP_NAME } from '@/config/app.js'
+
 export default {
   title: 'Login',
-  appTitle: 'Intelligent ETMF System',
+  appTitle: APP_NAME, // Keep backward compatibility, but recommend using t('common.app.fullName')
   welcome: 'Welcome',
   phonePlaceholder: 'Enter phone number',
   passwordPlaceholder: 'Enter password',

+ 3 - 1
locales/pages/login/zh_CN.js

@@ -1,6 +1,8 @@
+import { APP_NAME } from '@/config/app.js'
+
 export default {
   title: '登录',
-  appTitle: '智能ETMF系统',
+  appTitle: APP_NAME, // 保持向后兼容,但建议使用 t('common.app.fullName')
   welcome: '欢迎登录',
   phonePlaceholder: '请输入手机号',
   passwordPlaceholder: '请输入密码',

+ 2 - 1
pages/login/login.vue

@@ -9,7 +9,7 @@
     
     <!-- 顶部渐变背景区域 -->
     <view class="header-bg" :style="{ paddingTop: statusBarHeight + 'px' }">
-      <text class="app-title">{{ t('login.appTitle') }}</text>
+      <text class="app-title">{{ getAppName() }}</text>
       <text class="app-subtitle">{{ t('login.welcome') }}</text>
     </view>
     
@@ -87,6 +87,7 @@ import { useI18n } from 'vue-i18n'
 import { useUserStore } from '@/store/index'
 import { useLocaleStore } from '@/store/locale'
 import request from '@/utils/request.js'
+import { getAppName } from '@/config/app.js'
 
 const { t, locale } = useI18n()
 const userStore = useUserStore()

+ 276 - 1
pages/my/info/index.vue

@@ -65,6 +65,13 @@
           </view>
         </view>
       </view>
+      
+      <!-- 修改密码按钮 -->
+      <view class="password-btn-wrapper">
+        <view class="password-btn" @click="handleEditPassword">
+          <text class="password-btn-text">修改密码</text>
+        </view>
+      </view>
     </view>
     
     <!-- 修改头像弹窗 -->
@@ -161,13 +168,83 @@
         </view>
       </view>
     </view>
+    
+    <!-- 修改密码弹窗 -->
+    <view v-if="showPasswordModal" class="modal-overlay" @click="closePasswordModal">
+      <view class="modal-content" @click.stop>
+        <view class="modal-header">
+          <text class="modal-title">修改密码</text>
+          <view class="modal-close" @click="closePasswordModal">
+            <text class="close-icon">×</text>
+          </view>
+        </view>
+        <view class="modal-body">
+          <view class="password-input-wrapper">
+            <input 
+              class="modal-input" 
+              v-model="tempOldPassword" 
+              :password="!showOldPassword"
+              placeholder="请输入旧密码"
+              maxlength="20"
+            />
+            <text 
+              class="toggle-password" 
+              @click="showOldPassword = !showOldPassword"
+            >
+              {{ showOldPassword ? '👁' : '👁‍🗨' }}
+            </text>
+          </view>
+          
+          <view class="password-input-wrapper">
+            <input 
+              class="modal-input" 
+              v-model="tempNewPassword" 
+              :password="!showNewPassword"
+              placeholder="请输入新密码(至少6位)"
+              maxlength="20"
+            />
+            <text 
+              class="toggle-password" 
+              @click="showNewPassword = !showNewPassword"
+            >
+              {{ showNewPassword ? '👁' : '👁‍🗨' }}
+            </text>
+          </view>
+          
+          <view class="password-input-wrapper">
+            <input 
+              class="modal-input" 
+              v-model="tempConfirmPassword" 
+              :password="!showConfirmPassword"
+              placeholder="请再次输入新密码"
+              maxlength="20"
+            />
+            <text 
+              class="toggle-password" 
+              @click="showConfirmPassword = !showConfirmPassword"
+            >
+              {{ showConfirmPassword ? '👁' : '👁‍🗨' }}
+            </text>
+          </view>
+          
+          <view class="modal-actions">
+            <view class="action-btn cancel-btn" @click="closePasswordModal">
+              <text class="btn-text">取消</text>
+            </view>
+            <view class="action-btn confirm-btn" @click="confirmUpdatePassword">
+              <text class="btn-text">确认</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
 <script setup>
 import { ref, computed, onMounted } from 'vue'
 import { useI18n } from 'vue-i18n'
-import { getBasicInfo, updateAvatar, updateNickname, updateGender, uploadToOss } from '@/apis/auth'
+import { getBasicInfo, updateAvatar, updateNickname, updateGender, updatePassword, logout, uploadToOss } from '@/apis/auth'
 import { getDictDataByType } from '@/apis/dict'
 
 const { t, locale } = useI18n()
@@ -196,12 +273,19 @@ const genderDictList = ref([])
 const showAvatarModal = ref(false)
 const showNicknameModal = ref(false)
 const showGenderModal = ref(false)
+const showPasswordModal = ref(false)
 
 // 临时数据
 const tempAvatar = ref('')
 const tempAvatarOssId = ref('')
 const tempNickname = ref('')
 const tempGender = ref('')
+const tempOldPassword = ref('')
+const tempNewPassword = ref('')
+const tempConfirmPassword = ref('')
+const showOldPassword = ref(false)
+const showNewPassword = ref(false)
+const showConfirmPassword = ref(false)
 
 // 性别显示(为字典项添加displayLabel属性)
 const genderDictListWithLabel = computed(() => {
@@ -508,6 +592,146 @@ const confirmUpdateGender = async () => {
   }
 }
 
+// 打开修改密码弹窗
+const handleEditPassword = () => {
+  tempOldPassword.value = ''
+  tempNewPassword.value = ''
+  tempConfirmPassword.value = ''
+  showOldPassword.value = false
+  showNewPassword.value = false
+  showConfirmPassword.value = false
+  showPasswordModal.value = true
+}
+
+// 关闭修改密码弹窗
+const closePasswordModal = () => {
+  showPasswordModal.value = false
+  tempOldPassword.value = ''
+  tempNewPassword.value = ''
+  tempConfirmPassword.value = ''
+  showOldPassword.value = false
+  showNewPassword.value = false
+  showConfirmPassword.value = false
+}
+
+// 确认修改密码
+const confirmUpdatePassword = async () => {
+  // 验证旧密码
+  if (!tempOldPassword.value || !tempOldPassword.value.trim()) {
+    uni.showToast({
+      title: '请输入旧密码',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 验证新密码
+  if (!tempNewPassword.value || !tempNewPassword.value.trim()) {
+    uni.showToast({
+      title: '请输入新密码',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 验证新密码长度
+  if (tempNewPassword.value.length < 6) {
+    uni.showToast({
+      title: '新密码至少6位',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 验证确认密码
+  if (!tempConfirmPassword.value || !tempConfirmPassword.value.trim()) {
+    uni.showToast({
+      title: '请输入确认密码',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 验证两次密码是否一致
+  if (tempNewPassword.value !== tempConfirmPassword.value) {
+    uni.showToast({
+      title: '两次密码不一致',
+      icon: 'none'
+    })
+    return
+  }
+  
+  try {
+    uni.showLoading({
+      title: '修改中...',
+      mask: true
+    })
+    
+    const response = await updatePassword({
+      oldPassword: tempOldPassword.value.trim(),
+      newPassword: tempNewPassword.value.trim()
+    })
+    
+    uni.hideLoading()
+    
+    if (response && response.code === 200) {
+      uni.showToast({
+        title: '修改成功',
+        icon: 'success',
+        duration: 2000
+      })
+      
+      closePasswordModal()
+      
+      // 修改密码成功后,先调用退出登录接口,然后提示用户重新登录
+      setTimeout(async () => {
+        try {
+          // 调用退出登录接口
+          await logout()
+        } catch (error) {
+          console.error('退出登录失败:', error)
+          // 即使退出登录接口失败,也继续清除本地token
+        } finally {
+          // 清除本地token
+          uni.removeStorageSync('token')
+          
+          // 提示用户重新登录
+          uni.showModal({
+            title: '提示',
+            content: '密码修改成功,请重新登录',
+            showCancel: false,
+            success: () => {
+              // 跳转到登录页
+              uni.reLaunch({
+                url: '/pages/login/login'
+              })
+            }
+          })
+        }
+      }, 2000)
+    } else {
+      // 使用弹出框显示具体错误信息
+      uni.showModal({
+        title: '修改失败',
+        content: response.msg || '修改密码失败,请稍后重试',
+        showCancel: false,
+        confirmText: '确定'
+      })
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('修改密码失败:', error)
+    
+    // 使用弹出框显示错误信息
+    uni.showModal({
+      title: '修改失败',
+      content: error.msg || error.message || '网络错误,请稍后重试',
+      showCancel: false,
+      confirmText: '确定'
+    })
+  }
+}
+
 // 获取基本信息
 const fetchBasicInfo = async () => {
   try {
@@ -716,6 +940,36 @@ const handleBack = () => {
         }
       }
     }
+    
+    // 修改密码按钮
+    .password-btn-wrapper {
+      margin-top: 40rpx;
+      padding: 0 40rpx;
+      
+      .password-btn {
+        width: 100%;
+        height: 96rpx;
+        background: linear-gradient(135deg, #1ec9c9 0%, #17b3b3 100%);
+        border-radius: 48rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-shadow: 0 6rpx 20rpx rgba(30, 201, 201, 0.3);
+        transition: all 0.3s;
+        
+        &:active {
+          transform: scale(0.98);
+          box-shadow: 0 4rpx 16rpx rgba(30, 201, 201, 0.3);
+        }
+        
+        .password-btn-text {
+          font-size: 32rpx;
+          font-weight: 600;
+          color: #ffffff;
+          letter-spacing: 2rpx;
+        }
+      }
+    }
   }
   
   // 弹窗样式
@@ -861,6 +1115,27 @@ const handleBack = () => {
           }
         }
         
+        .password-input-wrapper {
+          position: relative;
+          margin-bottom: 32rpx;
+          
+          .modal-input {
+            margin-bottom: 0;
+            padding-right: 80rpx;
+          }
+          
+          .toggle-password {
+            position: absolute;
+            right: 24rpx;
+            top: 50%;
+            transform: translateY(-50%);
+            font-size: 40rpx;
+            cursor: pointer;
+            padding: 8rpx;
+            z-index: 10;
+          }
+        }
+        
         .picker-value {
           width: 100%;
           padding: 28rpx 24rpx;

+ 25 - 5
pages/scan/fileSelect/index.vue

@@ -51,6 +51,7 @@
       
       <view v-if="fileList.length === 0 && !loading" class="empty-tip">
         <text class="empty-text">暂无待递交文件</text>
+        <text class="empty-hint">您可以直接上传文件</text>
       </view>
       
       <view v-if="loading" class="loading-tip">
@@ -59,13 +60,20 @@
     </scroll-view>
     
     <!-- 底部提交按钮 -->
-    <view class="submit-footer" v-if="fileList.length > 0">
-      <view class="submit-btn secondary" @click="handleDirectUpload">
+    <view class="submit-footer">
+      <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 
+        class="submit-btn" 
+        :class="{ 'full-width': fileList.length === 0 }"
+        @click="fileList.length > 0 ? handleSubmitAll() : handleDirectUpload()"
+      >
+        <text class="submit-text">{{ fileList.length > 0 ? '提交' : '直接上传' }}</text>
+      </view> -->
     </view>
   </view>
 </template>
@@ -387,11 +395,19 @@ const formatDate = (dateStr) => {
     .empty-tip {
       padding: 200rpx 0;
       text-align: center;
+      display: flex;
+      flex-direction: column;
+      gap: 16rpx;
       
       .empty-text {
         font-size: 28rpx;
         color: #999999;
       }
+      
+      .empty-hint {
+        font-size: 26rpx;
+        color: #1ec9c9;
+      }
     }
     
     .loading-tip {
@@ -444,6 +460,10 @@ const formatDate = (dateStr) => {
         font-weight: 600;
         color: #ffffff;
       }
+      
+      &.full-width {
+        flex: 1;
+      }
     }
   }
 }

+ 493 - 120
pages/scan/folderSelect/index.vue

@@ -17,55 +17,113 @@
       <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 class="form-container">
       <view v-if="loading" class="loading-tip">
         <text class="loading-text">加载中...</text>
       </view>
-    </scroll-view>
+      
+      <view v-else class="form-content">
+        <!-- 国家-中心联动选择 -->
+        <view class="form-item">
+          <view class="form-label">国家 - 中心:</view>
+          <picker 
+            mode="multiSelector" 
+            :range="pickerRange"
+            :value="[selectedCountryIndex, selectedCenterIndex]"
+            @change="handleMultiPickerChange"
+            @columnchange="handleColumnChange"
+          >
+            <view class="picker-display">
+              <text class="picker-text" :class="{ placeholder: selectedCountryIndex === -1 || selectedCenterIndex === -1 }">
+                {{ getDisplayText() }}
+              </text>
+              <text class="picker-arrow">›</text>
+            </view>
+          </picker>
+        </view>
+        
+        <!-- 名称输入 -->
+        <view class="form-item">
+          <view class="form-label">名称:</view>
+          <view class="input-wrapper">
+            <input
+              v-model="fileName"
+              type="text"
+              placeholder="请输入名称"
+              class="input-field"
+            />
+          </view>
+        </view>
+        
+        <!-- 提交按钮 -->
+        <button 
+          class="confirm-btn" 
+          :disabled="selectedCountryIndex < 0 || selectedCenterIndex < 0 || !fileName.trim()"
+          @click="handleSubmit"
+        >
+          提交
+        </button>
+        
+        <!-- 调试信息 -->
+        <view class="debug-section">
+          <view class="debug-title">调试信息:</view>
+          <view class="debug-item">国家数量: {{ countryList.length }}</view>
+          <view class="debug-item">中心数量: {{ centerList.length }}</view>
+          <view class="debug-item">选中国家: {{ selectedCountryIndex === -1 ? '未选择' : countryList[selectedCountryIndex]?.name }}</view>
+          <view class="debug-item">选中中心: {{ selectedCenterIndex === -1 ? '未选择' : centerList[selectedCenterIndex]?.name }}</view>
+        </view>
+        
+        <!-- 原始数据展示 -->
+        <view class="raw-data-section">
+          <view class="raw-data-title" @click="showRawData = !showRawData">
+            原始数据 {{ showRawData ? '▼' : '▶' }}
+          </view>
+          <view v-if="showRawData" class="json-display">{{ JSON.stringify(rawData, null, 2) }}</view>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
 <script>
-import { getFolderList, getFolderPermission } from '@/apis/scan'
-import TreeView from '@/components/TreeView/index.vue'
+import { getFolderList } from '@/apis/scan'
+import request from '@/utils/request.js'
 
 export default {
-  components: {
-    TreeView
-  },
   data() {
     return {
       statusBarHeight: 0,
       projectId: 0,
       projectName: '',
-      folderTree: [],
+      rawData: null,
       loading: false,
-      permission: '*' // 权限配置
+      showRawData: false,
+      
+      // 原始数据列表(用于查找独立的中心)
+      allFolders: [],
+      
+      // 国家列表
+      countryList: [],
+      selectedCountryIndex: 0, // 默认选中第一个(NA)
+      
+      // 中心列表
+      centerList: [],
+      selectedCenterIndex: 0, // 默认选中第一个(NA)
+      
+      // 名称输入
+      fileName: ''
+    }
+  },
+  computed: {
+    // 构建选择器的 range 数据
+    pickerRange() {
+      // 第一列:国家名称数组
+      const countryNames = this.countryList.map(item => item.name)
+      // 第二列:中心显示名称数组
+      const centerNames = this.centerList.map(item => item.displayName || item.name)
+      
+      return [countryNames, centerNames]
     }
   },
   onLoad(options) {
@@ -84,7 +142,6 @@ export default {
     
     // 加载数据
     if (this.projectId) {
-      this.loadPermission()
       this.loadFolderList()
     } else {
       console.error('projectId 为空,无法加载文件夹列表')
@@ -96,31 +153,6 @@ export default {
       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
@@ -136,47 +168,20 @@ export default {
         })
         
         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))
+        console.log('完整响应:', response)
         
-        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)
+        // 保存原始数据
+        this.rawData = response
+        
+        // 解析数据
+        if (response.code === 200 && response.data && Array.isArray(response.data)) {
+          this.allFolders = response.data
+          this.parseCountryList(response.data)
         }
+        
       } catch (error) {
         console.error('========== 加载失败 ==========')
         console.error('错误信息:', error)
-        console.error('错误堆栈:', error.stack)
         uni.showToast({
           title: '加载失败',
           icon: 'none'
@@ -187,12 +192,205 @@ export default {
       }
     },
     
-    // 选择文件夹
-    handleSelectFolder(folder) {
-      console.log('========== 选择文件夹 ==========')
-      console.log('文件夹:', folder.name)
-      console.log('文件夹ID:', folder.id)
-      console.log('完整路径:', folder.fullPath)
+    // 解析国家列表(type === 1 为国家)
+    parseCountryList(data) {
+      const countries = data.filter(item => {
+        return item.type === 1 // 国家类型
+      })
+      
+      // 在列表开头添加 NA 选项
+      this.countryList = [
+        { id: 'NA', name: 'NA', type: 1, children: [], displayName: 'NA' },
+        ...countries
+      ]
+      
+      console.log('解析后的国家列表:', this.countryList)
+      
+      // 初始化时加载第一个国家(NA)的中心列表
+      if (this.countryList.length > 0) {
+        this.loadCenterList(0)
+      }
+    },
+    
+    // 加载中心列表
+    loadCenterList(countryIndex) {
+      if (countryIndex >= 0 && this.countryList[countryIndex]) {
+        const country = this.countryList[countryIndex]
+        
+        // 如果选择的是 NA(第一个选项)
+        if (country.id === 'NA') {
+          // 获取所有不属于任何国家的中心
+          this.centerList = this.getIndependentCenters()
+        } else {
+          // 选择了具体国家,显示该国家下的所有中心(包括子中心)
+          const allCenters = this.getAllCentersRecursive(country.children || [])
+          
+          // 在列表开头添加 NA 选项
+          this.centerList = [
+            { id: 'NA', name: 'NA', type: 2, displayName: 'NA' },
+            ...allCenters
+          ]
+        }
+        
+        console.log('国家:', country.name, '中心列表:', this.centerList)
+      } else {
+        this.centerList = [{ id: 'NA', name: 'NA', type: 2, displayName: 'NA' }]
+      }
+    },
+    
+    // 递归获取所有中心(包括子中心)
+    getAllCentersRecursive(nodes, prefix = '') {
+      let centers = []
+      
+      nodes.forEach(node => {
+        // 如果是中心类型(type === 2)
+        if (node.type === 2) {
+          // 添加当前中心,名称带层级前缀
+          const displayName = prefix ? `${prefix} > ${node.name}` : node.name
+          centers.push({
+            ...node,
+            displayName: displayName, // 用于显示的名称
+            originalName: node.name   // 保留原始名称
+          })
+          
+          // 如果有子节点,递归获取
+          if (node.children && node.children.length > 0) {
+            const subCenters = this.getAllCentersRecursive(node.children, displayName)
+            centers = centers.concat(subCenters)
+          }
+        } else if (node.children && node.children.length > 0) {
+          // 如果不是中心但有子节点,继续递归
+          const subCenters = this.getAllCentersRecursive(node.children, prefix)
+          centers = centers.concat(subCenters)
+        }
+      })
+      
+      return centers
+    },
+    
+    // 多列选择器列变化
+    handleColumnChange(e) {
+      const column = e.detail.column // 哪一列变化了
+      const value = e.detail.value   // 变化后的值
+      
+      console.log('列变化:', column, '值:', value)
+      
+      // 如果是第一列(国家)变化
+      if (column === 0) {
+        this.selectedCountryIndex = value
+        // 重新加载中心列表
+        this.loadCenterList(value)
+        // 重置中心选择为第一个
+        this.selectedCenterIndex = 0
+      }
+    },
+    
+    // 多列选择器确认
+    handleMultiPickerChange(e) {
+      const values = e.detail.value
+      this.selectedCountryIndex = values[0]
+      this.selectedCenterIndex = values[1]
+      
+      console.log('选择完成 - 国家索引:', this.selectedCountryIndex, '中心索引:', this.selectedCenterIndex)
+      
+      if (this.countryList[this.selectedCountryIndex]) {
+        console.log('选中国家:', this.countryList[this.selectedCountryIndex].name)
+      }
+      if (this.centerList[this.selectedCenterIndex]) {
+        console.log('选中中心:', this.centerList[this.selectedCenterIndex].name)
+      }
+    },
+    
+    // 获取显示文本
+    getDisplayText() {
+      if (this.selectedCountryIndex >= 0 && this.selectedCenterIndex >= 0) {
+        const country = this.countryList[this.selectedCountryIndex]
+        const center = this.centerList[this.selectedCenterIndex]
+        
+        if (country && center) {
+          const centerName = center.displayName || center.name
+          return `${country.name} - ${centerName}`
+        }
+      }
+      return '请选择国家和中心'
+    },
+    
+    // 获取所有不属于任何国家的中心
+    getIndependentCenters() {
+      // 收集所有国家下的中心ID
+      const centerIdsInCountries = new Set()
+      
+      this.allFolders.forEach(folder => {
+        if (folder.type === 1 && folder.children) { // 国家类型
+          this.collectAllCenterIds(folder.children, centerIdsInCountries)
+        }
+      })
+      
+      // 找出所有不在国家下的中心(直接在根目录下的中心)
+      const independentCenters = this.allFolders.filter(folder => {
+        return folder.type === 2 && !centerIdsInCountries.has(folder.id)
+      })
+      
+      // 递归获取这些独立中心及其子中心
+      const allIndependentCenters = this.getAllCentersRecursive(independentCenters)
+      
+      // 在列表开头添加 NA 选项
+      return [
+        { id: 'NA', name: 'NA', type: 2, displayName: 'NA' },
+        ...allIndependentCenters
+      ]
+    },
+    
+    // 收集所有中心ID(递归)
+    collectAllCenterIds(nodes, centerIds) {
+      nodes.forEach(node => {
+        if (node.type === 2) {
+          centerIds.add(node.id)
+        }
+        if (node.children && node.children.length > 0) {
+          this.collectAllCenterIds(node.children, centerIds)
+        }
+      })
+    },
+    
+    // 中心选择变化(保留以防需要)
+    handleCenterChange(e) {
+      const index = parseInt(e.detail.value)
+      this.selectedCenterIndex = index
+      
+      if (index >= 0 && this.centerList[index]) {
+        console.log('选中中心:', this.centerList[index].name)
+      }
+    },
+    
+    // 提交
+    async handleSubmit() {
+      if (this.selectedCountryIndex < 0) {
+        uni.showToast({
+          title: '请选择国家',
+          icon: 'none'
+        })
+        return
+      }
+      
+      if (this.selectedCenterIndex < 0) {
+        uni.showToast({
+          title: '请选择中心',
+          icon: 'none'
+        })
+        return
+      }
+      
+      if (!this.fileName.trim()) {
+        uni.showToast({
+          title: '请输入名称',
+          icon: 'none'
+        })
+        return
+      }
+      
+      const selectedCountry = this.countryList[this.selectedCountryIndex]
+      const selectedCenter = this.centerList[this.selectedCenterIndex]
       
       // 从全局数据中获取扫描的fileBase64List
       const fileBase64List = getApp().globalData.scannedFileBase64List
@@ -205,16 +403,70 @@ export default {
         return
       }
       
-      // 将文件夹信息存储到全局数据
-      getApp().globalData.selectedFolder = {
-        folderId: folder.id,
-        folderPath: folder.fullPath
+      try {
+        uni.showLoading({
+          title: '提交中...',
+          mask: true
+        })
+        
+        // 构建请求参数
+        const params = {
+          projectId: this.projectId,
+          country: selectedCountry.id === 'NA' ? 0 : selectedCountry.id,
+          center: selectedCenter.id === 'NA' ? 0 : selectedCenter.id,
+          name: this.fileName.trim(),
+          files: fileBase64List
+        }
+        
+        console.log('========== 提交参数 ==========')
+        console.log('params:', params)
+        
+        // 调用上传接口
+        const response = await request({
+          url: '/applet/scan/upload',
+          method: 'POST',
+          data: params
+        })
+        
+        console.log('========== 上传响应 ==========')
+        console.log('response:', response)
+        
+        uni.hideLoading()
+        
+        if (response.code === 200) {
+          uni.showToast({
+            title: '提交成功',
+            icon: 'success',
+            duration: 2000
+          })
+          
+          // 清空全局数据
+          getApp().globalData.scannedFileBase64List = []
+          
+          // 延迟返回首页
+          setTimeout(() => {
+            uni.reLaunch({
+              url: '/pages/home/index'
+            })
+          }, 2000)
+        } else {
+          throw new Error(response.msg || '提交失败')
+        }
+        
+      } catch (error) {
+        uni.hideLoading()
+        console.error('========== 提交失败 ==========')
+        console.error('错误信息:', error)
+        uni.showToast({
+          title: error.message || '提交失败',
+          icon: 'none'
+        })
       }
-      
-      // 跳转到编辑上传文件页面
-      uni.navigateTo({
-        url: `/pages/scan/uploadEdit/index?folderId=${folder.id}&folderPath=${encodeURIComponent(folder.fullPath)}`
-      })
+    },
+    
+    // 确认选择(保留旧方法以防其他地方调用)
+    handleConfirm() {
+      this.handleSubmit()
     }
   }
 }
@@ -290,24 +542,145 @@ export default {
     }
   }
   
-  // 文件夹列
-  .folder-list {
+  // 表单容器
+  .form-container {
     flex: 1;
-    background: #ffffff;
+    overflow-y: auto;
   }
   
-  .empty-tip {
-    padding: 200rpx 0;
-    text-align: center;
+  .form-content {
+    padding: 32rpx;
+  }
+  
+  .form-item {
+    margin-bottom: 32rpx;
     
-    .empty-text {
+    .form-label {
       font-size: 28rpx;
-      color: #999999;
+      color: #333333;
+      font-weight: 600;
+      margin-bottom: 16rpx;
+    }
+    
+    .picker-display {
+      background: #ffffff;
+      border: 2rpx solid #e0e0e0;
+      border-radius: 12rpx;
+      padding: 24rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      transition: all 0.3s;
+      
+      &.disabled {
+        background: #f5f5f5;
+        opacity: 0.6;
+      }
+      
+      .picker-text {
+        flex: 1;
+        font-size: 28rpx;
+        color: #333333;
+        
+        &.placeholder {
+          color: #999999;
+        }
+      }
+      
+      .picker-arrow {
+        font-size: 40rpx;
+        color: #999999;
+        font-weight: 300;
+        margin-left: 16rpx;
+      }
+    }
+  }
+  
+  .confirm-btn {
+    width: 100%;
+    height: 88rpx;
+    background: linear-gradient(135deg, #1ec9c9 0%, #1eb8b8 100%);
+    color: #ffffff;
+    font-size: 32rpx;
+    font-weight: 600;
+    border-radius: 16rpx;
+    border: none;
+    margin-top: 32rpx;
+    transition: all 0.3s;
+    box-shadow: 0 6rpx 20rpx rgba(30, 201, 201, 0.3);
+    
+    &:disabled {
+      background: #cccccc;
+      box-shadow: none;
+      opacity: 0.6;
+    }
+    
+    &:active:not(:disabled) {
+      opacity: 0.9;
+      transform: scale(0.98);
+    }
+    
+    &::after {
+      border: none;
+    }
+  }
+  
+  // 调试信息
+  .debug-section {
+    margin-top: 48rpx;
+    padding: 24rpx;
+    background: #fff3cd;
+    border-radius: 12rpx;
+    border: 1rpx solid #ffc107;
+    
+    .debug-title {
+      font-size: 28rpx;
+      font-weight: 600;
+      color: #856404;
+      margin-bottom: 12rpx;
+    }
+    
+    .debug-item {
+      font-size: 24rpx;
+      color: #856404;
+      line-height: 1.8;
+    }
+  }
+  
+  // 原始数据
+  .raw-data-section {
+    margin-top: 32rpx;
+    
+    .raw-data-title {
+      font-size: 28rpx;
+      font-weight: 600;
+      color: #666666;
+      padding: 16rpx;
+      background: #f5f5f5;
+      border-radius: 8rpx;
+      cursor: pointer;
+      
+      &:active {
+        background: #e0e0e0;
+      }
+    }
+    
+    .json-display {
+      margin-top: 16rpx;
+      background: #f5f5f5;
+      padding: 24rpx;
+      border-radius: 8rpx;
+      font-size: 22rpx;
+      color: #333333;
+      font-family: 'Courier New', monospace;
+      line-height: 1.6;
+      word-break: break-all;
+      white-space: pre-wrap;
     }
   }
   
   .loading-tip {
-    padding: 32rpx 0;
+    padding: 200rpx 0;
     text-align: center;
     
     .loading-text {

+ 1 - 2
pages/scan/index.vue

@@ -280,13 +280,12 @@ const handleSelectImage = async () => {
   })
 }
 
-// 导入文档
 // 导入文档
 const handleSelectFile = () => {
   uni.chooseMessageFile({
     count: 1,
     type: 'file',
-    extension: ['.pdf', '.doc', '.docx'],
+    extension: ['.pdf'],
     success: async (res) => {
       try {
         uni.showLoading({

+ 46 - 8
pages/scan/uploadEdit/index.vue

@@ -311,8 +311,7 @@ export default {
     },
     
     // 预览文件
-    // 预览文件
-    handlePreview(file, index) {
+    async handlePreview(file, index) {
       console.log('预览文件:', file.name)
       console.log('文件索引:', index)
       
@@ -324,13 +323,52 @@ export default {
         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)}`
+      uni.showLoading({
+        title: '加载中...',
+        mask: true
       })
+      
+      try {
+        // 将 base64 转换为临时文件
+        const fs = uni.getFileSystemManager()
+        const fileName = `${Date.now()}_${file.name}.pdf`
+        const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`
+        
+        // 将 base64 写入文件
+        fs.writeFileSync(
+          filePath,
+          file.base64,
+          'base64'
+        )
+        
+        console.log('临时文件路径:', filePath)
+        
+        uni.hideLoading()
+        
+        // 使用 uni.openDocument 打开文件
+        uni.openDocument({
+          filePath: filePath,
+          fileType: 'pdf',
+          showMenu: true,
+          success: () => {
+            console.log('文档打开成功')
+          },
+          fail: (err) => {
+            console.error('文档打开失败:', err)
+            uni.showToast({
+              title: '文档打开失败',
+              icon: 'none'
+            })
+          }
+        })
+      } catch (error) {
+        uni.hideLoading()
+        console.error('预览失败:', error)
+        uni.showToast({
+          title: '预览失败',
+          icon: 'none'
+        })
+      }
     },
     
     // 上移文件

BIN
修改性别.png


BIN
修改昵称.png