فهرست منبع

基本完成对接

Huanyi 3 ماه پیش
والد
کامیت
1eb56c8a28

+ 0 - 320
ARCHITECTURE.md

@@ -1,320 +0,0 @@
-# 项目架构说明
-
-## 页面结构
-
-### 单页应用架构
-
-项目采用**单页面 + 组件切换**的架构,实现类似原生TabBar的效果,同时保持头部和底部固定不变。
-
-```
-pages/
-├── login/
-│   └── login.vue           # 登录页面(独立页面)
-└── index.vue               # 主容器页面(核心)
-
-pages-content/              # 内容组件目录(独立)
-├── home/
-│   └── index.vue           # 首页内容组件
-├── scan/
-│   └── index.vue           # 扫描内容组件
-└── my/
-    └── index.vue           # 我的内容组件
-```
-
-## 主容器页面 (pages/index.vue)
-
-### 结构组成
-
-```
-┌─────────────────────────────┐
-│   自定义头部(固定)          │  ← 白色背景,显示当前标题
-│   - 自适应状态栏高度         │     根据currentTab自动切换
-├─────────────────────────────┤
-│                             │
-│   主内容区(动态切换)        │  ← 使用 v-if 切换组件
-│   - HomePage 组件           │     - currentTab = 'home'
-│   - ScanPage 组件           │     - currentTab = 'scan'
-│   - MyPage 组件             │     - currentTab = 'mine'
-│                             │
-├─────────────────────────────┤
-│   自定义底部导航栏(固定)    │  ← 白色背景,三个菜单
-│   🏠 首页 | 📷 扫描 | 👤 我的 │     点击切换 currentTab
-└─────────────────────────────┘
-```
-
-### 核心代码逻辑
-
-```javascript
-// 当前激活的tab
-const currentTab = ref('home')
-
-// 切换tab - 只需要改变currentTab值
-const switchTab = (name) => {
-  if (currentTab.value === name) return
-  currentTab.value = name
-}
-
-// 标题自动跟随当前tab
-const currentPageTitle = computed(() => {
-  const titles = {
-    home: '首页',
-    scan: '扫描',
-    mine: '我的'
-  }
-  return titles[currentTab.value] || '首页'
-})
-```
-
-### 组件切换
-
-```vue
-<view class="main-content">
-  <!-- 通过 v-if 实现组件切换 -->
-  <HomePage v-if="currentTab === 'home'" />
-  <ScanPage v-else-if="currentTab === 'scan'" />
-  <MyPage v-else-if="currentTab === 'mine'" />
-</view>
-```
-
-## 内容组件
-
-### 首页组件 (pages-content/home/index.vue)
-
-```vue
-<template>
-  <view class="home-page">
-    <view class="page-header">
-      <text class="title">首页内容</text>
-    </view>
-    <view class="page-body">
-      <text class="placeholder">首页主体内容区域</text>
-    </view>
-  </view>
-</template>
-```
-
-**特点**:
-- 纯内容组件,无头部和底部
-- 有自己的内部结构和样式
-- 可独立开发和维护
-
-### 扫描组件 (pages-content/scan/index.vue)
-
-```vue
-<template>
-  <view class="scan-page">
-    <view class="page-header">
-      <text class="title">扫描内容</text>
-    </view>
-    <view class="page-body">
-      <text class="placeholder">扫描主体内容区域</text>
-    </view>
-  </view>
-</template>
-```
-
-**特点**:
-- 用于实现扫描相关功能
-- 可以调用相机API、扫码API等
-
-### 我的组件 (pages-content/my/index.vue)
-
-```vue
-<template>
-  <view class="my-page">
-    <view class="page-header">
-      <text class="title">我的内容</text>
-    </view>
-    <view class="page-body">
-      <text class="placeholder">我的主体内容区域</text>
-    </view>
-  </view>
-</template>
-```
-
-**特点**:
-- 用于个人中心相关功能
-- 可以显示用户信息、设置等
-
-## 页面跳转流程
-
-### 登录成功后跳转
-
-```javascript
-// pages/login/login.vue
-uni.navigateTo({
-  url: '/pages/index'  // 跳转到主容器页面
-})
-```
-
-### 主容器内切换
-
-```javascript
-// pages/index.vue
-switchTab('scan')  // 切换到扫描页
-// ↓
-currentTab.value = 'scan'
-// ↓
-<ScanPage /> 组件显示
-// ↓
-头部标题变为"扫描"
-// ↓
-底部导航高亮"扫描"
-```
-
-## 架构优势
-
-### 1. 性能优化
-
-| 操作 | 重新渲染 | 说明 |
-|------|---------|------|
-| 初始加载 | 完整渲染 | 加载主容器 + 首页组件 |
-| 切换tab | 只渲染内容 | 头部和底部不变 |
-| 返回登录 | 完整渲染 | 卸载主容器 |
-
-### 2. 开发便利
-
-- ✅ 每个内容组件独立开发
-- ✅ 头部和底部统一管理
-- ✅ 切换逻辑简单清晰
-- ✅ 易于维护和扩展
-
-### 3. 用户体验
-
-- ✅ 切换流畅,无闪烁
-- ✅ 头部标题自动更新
-- ✅ 底部高亮自动切换
-- ✅ 类似原生TabBar体验
-
-## 扩展指南
-
-### 添加新的Tab页面
-
-**1. 创建内容组件**
-
-```bash
-pages-content/newpage/index.vue
-```
-
-**2. 在主容器中引入**
-
-```vue
-<!-- pages/index.vue -->
-<script setup>
-import NewPage from '../pages-content/newpage/index.vue'
-</script>
-
-<template>
-  <NewPage v-else-if="currentTab === 'newpage'" />
-</template>
-```
-
-**3. 添加底部导航配置**
-
-```javascript
-const tabList = ref([
-  // ...现有配置
-  {
-    name: 'newpage',
-    label: '新页面',
-    icon: '🎯'
-  }
-])
-```
-
-**4. 添加标题映射**
-
-```javascript
-const currentPageTitle = computed(() => {
-  const titles = {
-    // ...
-    newpage: '新页面'
-  }
-  return titles[currentTab.value]
-})
-```
-
-### 自定义头部
-
-如果需要不同页面有不同的头部样式:
-
-```vue
-<!-- 方案1:使用 computed 动态样式 -->
-<view 
-  class="custom-header" 
-  :style="{ 
-    background: headerBackground,
-    paddingTop: statusBarHeight + 'px' 
-  }"
->
-  <text>{{ currentPageTitle }}</text>
-</view>
-
-<script>
-const headerBackground = computed(() => {
-  const colors = {
-    home: '#ffffff',
-    scan: '#667eea',
-    mine: '#ffffff'
-  }
-  return colors[currentTab.value]
-})
-</script>
-```
-
-### 组件通信
-
-如果需要在内容组件间共享数据:
-
-**使用 Pinia Store**
-```javascript
-// store/tab.js
-export const useTabStore = defineStore('tab', {
-  state: () => ({
-    sharedData: null
-  })
-})
-
-// 在任意组件中使用
-import { useTabStore } from '@/store/tab'
-const tabStore = useTabStore()
-```
-
-## 配置文件
-
-### pages.json
-
-```json
-{
-  "pages": [
-    {
-      "path": "pages/login/login",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/index",
-      "style": {
-        "navigationStyle": "custom"  // 使用自定义导航栏
-      }
-    }
-  ]
-}
-```
-
-**注意**:
-- 不需要配置 `tabBar`
-- 所有tab功能都由 `pages/index.vue` 实现
-- `pages-content/*` 下的组件不需要在 `pages.json` 中注册
-
-## 总结
-
-这是一个**轻量级、高性能**的单页应用架构:
-
-- 🎯 **简单** - 只有一个主页面 + 三个内容组件
-- ⚡ **快速** - 切换tab只更新内容区域
-- 🎨 **灵活** - 每个组件独立开发维护
-- 📦 **可扩展** - 易于添加新的tab页面
-
-适合需要底部导航的小程序项目!

+ 6 - 3
App.vue

@@ -7,18 +7,21 @@ const userStore = useUserStore()
 const localeStore = useLocaleStore()
 
 onLaunch(() => {
-  console.log('App Launch')
+  // 初始化全局数据
+  if (!getApp().globalData) {
+    getApp().globalData = {}
+  }
   
   // 初始化语言设置
   localeStore.initLocale()
 })
 
 onShow(() => {
-  console.log('App Show')
+  // App显示
 })
 
 onHide(() => {
-  console.log('App Hide')
+  // App隐藏
 })
 </script>
 

+ 16 - 0
apis/auth.js

@@ -61,3 +61,19 @@ export const getTaskCount = () => {
     method: 'GET'
   })
 }
+/**
+ * 获取任务文档列表
+ * @param {Object} params - 查询参数
+ * @param {String} params.name - 文档名称(模糊搜索)
+ * @param {Number} params.status - 文档状态(0-待递交,1-待审核,2-审核拒绝,3-待归档,4-已归档,5-待质控,6-质控通过,7-质控拒绝)
+ * @param {Number} params.pageNum - 页码
+ * @param {Number} params.pageSize - 每页数量
+ * @returns {Promise}
+ */
+export const getTaskDocuments = (params) => {
+  return request({
+    url: '/applet/myTask/listDocument',
+    method: 'GET',
+    data: params
+  })
+}

+ 32 - 0
apis/scan.js

@@ -0,0 +1,32 @@
+/**
+ * 扫描相关 API
+ */
+import request from '@/utils/request'
+
+/**
+ * 扫描上传单个文件
+ * @param {Object} data - 上传数据
+ * @param {String} data.file - base64格式的文件
+ * @returns {Promise}
+ */
+export const scanUpload = (data) => {
+  return request({
+    url: '/applet/scan/scan',
+    method: 'POST',
+    data
+  })
+}
+
+/**
+ * 批量上传扫描文件
+ * @param {Object} data - 上传数据
+ * @param {Array} data.files - base64格式的文件数组
+ * @returns {Promise}
+ */
+export const scanUploadBatch = (data) => {
+  return request({
+    url: '/applet/scan/scan',
+    method: 'POST',
+    data
+  })
+}

BIN
home.png


+ 4 - 0
locales/index.js

@@ -13,6 +13,8 @@ import myZhCN from './pages/my/zh_CN'
 import myEnUS from './pages/my/en_US'
 import aggreementZhCN from './pages/my/aggreement/zh_CN'
 import aggreementEnUS from './pages/my/aggreement/en_US'
+import taskDocumentsZhCN from './pages/my/taskDocuments/zh_CN'
+import taskDocumentsEnUS from './pages/my/taskDocuments/en_US'
 
 // 导入pages-content模块
 import myContentZhCN from './pages-content/my/zh_CN'
@@ -31,6 +33,7 @@ export const messages = {
     scan: scanZhCN,
     my: myZhCN,
     aggreement: aggreementZhCN,
+    taskDocuments: taskDocumentsZhCN,
     pagesContent: {
       my: myContentZhCN
     },
@@ -45,6 +48,7 @@ export const messages = {
     scan: scanEnUS,
     my: myEnUS,
     aggreement: aggreementEnUS,
+    taskDocuments: taskDocumentsEnUS,
     pagesContent: {
       my: myContentEnUS
     },

+ 26 - 0
locales/pages/my/taskDocuments/en_US.js

@@ -0,0 +1,26 @@
+export default {
+  myTasks: 'My Tasks',
+  toSubmit: 'To Submit',
+  toAudit: 'To Audit',
+  searchPlaceholder: 'Search document name',
+  search: 'Search',
+  createTime: 'Create Time',
+  submitter: 'Submitter',
+  loading: 'Loading...',
+  noMore: 'No more',
+  empty: 'No documents',
+  loadFailed: 'Load failed',
+  invalidUrl: 'Invalid document URL',
+  allStatus: 'All Status',
+  status: {
+    all: 'All',
+    unUpload: 'To Submit',
+    unAudit: 'To Audit',
+    auditReject: 'Audit Rejected',
+    unFiling: 'To File',
+    filing: 'Filed',
+    unQualityControl: 'To QC',
+    qualityControlPass: 'QC Passed',
+    qualityControlReject: 'QC Rejected'
+  }
+}

+ 26 - 0
locales/pages/my/taskDocuments/zh_CN.js

@@ -0,0 +1,26 @@
+export default {
+  myTasks: '我的任务',
+  toSubmit: '待递交',
+  toAudit: '待审核',
+  searchPlaceholder: '搜索文档名称',
+  search: '搜索',
+  createTime: '创建时间',
+  submitter: '提交人',
+  loading: '加载中...',
+  noMore: '没有更多了',
+  empty: '暂无文档',
+  loadFailed: '加载失败',
+  invalidUrl: '文档地址无效',
+  allStatus: '全部状态',
+  status: {
+    all: '全部',
+    unUpload: '待递交',
+    unAudit: '待审核',
+    auditReject: '审核拒绝',
+    unFiling: '待归档',
+    filing: '已归档',
+    unQualityControl: '待质控',
+    qualityControlPass: '质控通过',
+    qualityControlReject: '质控拒绝'
+  }
+}

+ 30 - 0
pages.json

@@ -14,6 +14,15 @@
         "enablePullDownRefresh": false
       }
     },
+    {
+      "path": "pages/home/documentViewer/index",
+      "style": {
+        "navigationBarTitleText": "文档预览",
+        "navigationBarBackgroundColor": "#1ec9c9",
+        "navigationBarTextStyle": "white",
+        "enablePullDownRefresh": false
+      }
+    },
     {
       "path": "pages/scan/index",
       "style": {
@@ -21,6 +30,20 @@
         "enablePullDownRefresh": false
       }
     },
+    {
+      "path": "pages/scan/preview/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/scan/pdfViewer/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
     {
       "path": "pages/my/index",
       "style": {
@@ -48,6 +71,13 @@
         "navigationStyle": "custom",
         "enablePullDownRefresh": false
       }
+    },
+    {
+      "path": "pages/my/taskDocuments/index",
+      "style": {
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
     }
   ],
   "globalStyle": {

+ 112 - 0
pages/home/documentViewer/index.vue

@@ -0,0 +1,112 @@
+<template>
+  <view class="document-viewer-page">
+    <!-- 使用 web-view 全屏展示文档 -->
+    <web-view v-if="documentUrl" :src="documentUrl"></web-view>
+    
+    <!-- 错误状态 -->
+    <view v-else class="error-state">
+      <text class="error-text">{{ errorMessage || '文档地址无效' }}</text>
+      <button class="retry-btn" @click="handleBack">返回</button>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+
+// 文档信息
+const documentName = ref('')
+const documentUrl = ref('')
+const ossId = ref('')
+const errorMessage = ref('')
+
+onMounted(() => {
+  // 获取页面参数
+  const pages = getCurrentPages()
+  const currentPage = pages[pages.length - 1]
+  
+  documentName.value = decodeURIComponent(currentPage.options.name || '文档预览')
+  documentUrl.value = decodeURIComponent(currentPage.options.url || '')
+  ossId.value = currentPage.options.ossId || ''
+  
+  // 设置导航栏标题
+  if (documentName.value) {
+    uni.setNavigationBarTitle({
+      title: documentName.value
+    })
+  }
+  
+  // 如果没有URL但有ossId,可以通过ossId构建URL
+  if (!documentUrl.value && ossId.value) {
+    // TODO: 根据ossId构建文档访问URL
+    // documentUrl.value = `${BASE_URL}/file/preview/${ossId.value}`
+  }
+  
+  // 验证URL
+  if (!documentUrl.value) {
+    errorMessage.value = '文档地址无效'
+  }
+})
+
+// 返回
+const handleBack = () => {
+  uni.navigateBack({
+    fail: () => {
+      uni.reLaunch({
+        url: '/pages/home/index'
+      })
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.document-viewer-page {
+  width: 100%;
+  height: 100vh;
+  background-color: #ffffff;
+  
+  web-view {
+    width: 100%;
+    height: 100%;
+  }
+  
+  // 错误状态
+  .error-state {
+    width: 100%;
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 40rpx;
+    padding: 60rpx;
+    background-color: #f5f5f5;
+    
+    .error-text {
+      font-size: 28rpx;
+      color: #999999;
+      text-align: center;
+    }
+    
+    .retry-btn {
+      width: 200rpx;
+      height: 72rpx;
+      line-height: 72rpx;
+      background: #1ec9c9;
+      color: #ffffff;
+      border-radius: 36rpx;
+      font-size: 28rpx;
+      border: none;
+      
+      &:active {
+        opacity: 0.8;
+      }
+      
+      &::after {
+        border: none;
+      }
+    }
+  }
+}
+</style>

+ 55 - 11
pages/home/index.vue

@@ -72,7 +72,7 @@
             class="document-item"
             @click="handleDocumentClick(doc)"
           >
-            <image class="doc-thumbnail" :src="doc.thumbnail || '/static/pages/home/doc-thumb.png'" mode="aspectFill" />
+            <image class="doc-thumbnail" :src="getFileIcon(doc.url)" mode="aspectFit" />
             <view class="doc-info">
               <text class="doc-name">{{ doc.name }}</text>
               <text class="doc-meta">{{ doc.createTime }}</text>
@@ -130,6 +130,34 @@ const total = ref(0)
 const loading = ref(false)
 const hasMore = ref(true)
 
+// 根据文件URL获取文件类型图标
+const getFileIcon = (url) => {
+  if (!url) return '/static/icon/document.svg'
+  
+  // 提取文件扩展名
+  const extension = url.split('.').pop().toLowerCase()
+  
+  // 根据扩展名返回对应图标
+  const iconMap = {
+    // Word文档
+    'doc': '/static/icon/word.svg',
+    'docx': '/static/icon/word.svg',
+    
+    // Excel表格
+    'xls': '/static/icon/excel.svg',
+    'xlsx': '/static/icon/excel.svg',
+    
+    // PowerPoint演示
+    'ppt': '/static/icon/ppt.svg',
+    'pptx': '/static/icon/ppt.svg',
+    
+    // PDF文档
+    'pdf': '/static/icon/pdf.svg'
+  }
+  
+  return iconMap[extension] || '/static/icon/document.svg'
+}
+
 onMounted(() => {
   // 获取系统信息
   const windowInfo = uni.getWindowInfo()
@@ -157,8 +185,6 @@ const fetchCarouselList = async () => {
         image: item.ossidUrl,
         note: item.note
       }))
-      
-      console.log('轮播图加载成功:', bannerList.value.length, '张')
     }
   } catch (error) {
     console.error('获取轮播图失败:', error)
@@ -196,8 +222,6 @@ const fetchRecentDocuments = async (isLoadMore = false) => {
       params.name = searchKeyword.value.trim()
     }
     
-    console.log('请求参数:', params)
-    
     const response = await getRecentDocuments(params)
     
     if (response && response.code === 200) {
@@ -215,8 +239,6 @@ const fetchRecentDocuments = async (isLoadMore = false) => {
       
       // 判断是否还有更多数据
       hasMore.value = recentDocuments.value.length < total.value
-      
-      console.log('最近文档加载成功:', rows.length, '条')
     }
   } catch (error) {
     console.error('获取最近文档失败:', error)
@@ -273,11 +295,31 @@ const handleViewMore = () => {
 
 // 点击文档
 const handleDocumentClick = (doc) => {
-  uni.showToast({
-    title: `打开文档: ${doc.name}`,
-    icon: 'none'
+  if (!doc.url && !doc.ossId) {
+    uni.showToast({
+      title: '文档地址无效',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 手动构建URL参数
+  const params = []
+  if (doc.name) {
+    params.push(`name=${encodeURIComponent(doc.name)}`)
+  }
+  if (doc.url) {
+    params.push(`url=${encodeURIComponent(doc.url)}`)
+  }
+  if (doc.ossId) {
+    params.push(`ossId=${doc.ossId}`)
+  }
+  
+  const queryString = params.join('&')
+  
+  uni.navigateTo({
+    url: `/pages/home/documentViewer/index?${queryString}`
   })
-  // TODO: 跳转到文档详情页
 }
 </script>
 
@@ -468,6 +510,8 @@ const handleDocumentClick = (doc) => {
             border-radius: 8rpx;
             margin-right: 24rpx;
             background-color: #f0f0f0;
+            padding: 10rpx;
+            box-sizing: border-box;
           }
           
           .doc-info {

+ 0 - 4
pages/my/aggreement/detail.vue

@@ -57,8 +57,6 @@ onMounted(async () => {
   const currentPage = pages[pages.length - 1]
   agreementType.value = currentPage.options.type || 'user'
   
-  console.log('协议详情页面已加载, 类型:', agreementType.value)
-  
   // 获取协议内容
   await fetchAgreementContent()
 })
@@ -104,8 +102,6 @@ const fetchAgreementContent = async () => {
                                  contentObj['en_US'] || 
                                  Object.values(contentObj)[0] || 
                                  ''
-        
-        console.log('已加载协议内容,语言:', localeKey)
       } catch (parseError) {
         console.error('解析协议内容JSON失败:', parseError)
         // 如果解析失败,尝试直接使用原始内容

+ 19 - 6
pages/my/index.vue

@@ -38,7 +38,7 @@
       
       <!-- 我的任务 -->
       <view class="task-section">
-        <view class="section-header">
+        <view class="section-header" @click="handleTaskSectionClick">
           <text class="section-title">{{ t('my.myTasks') }}</text>
           <text class="arrow">›</text>
         </view>
@@ -216,8 +216,6 @@ const fetchTaskCount = async () => {
         pending: response.data.toSubmit || 0,
         reviewing: response.data.toAudit || 0
       }
-      
-      console.log('任务数量:', taskCounts.value)
     }
   } catch (error) {
     console.error('获取任务数量失败:', error)
@@ -247,9 +245,24 @@ const handleVipClick = () => {
 
 // 任务点击
 const handleTaskClick = (type) => {
-  uni.showToast({
-    title: `${type}任务`,
-    icon: 'none'
+  let status = null
+  
+  if (type === 'pending') {
+    status = 0 // 待递交
+  } else if (type === 'reviewing') {
+    status = 1 // 待审核
+  }
+  
+  uni.navigateTo({
+    url: `/pages/my/taskDocuments/index?status=${status}`
+  })
+}
+
+// 点击我的任务标题区域
+const handleTaskSectionClick = () => {
+  // 跳转到任务列表,不传status参数显示所有任务
+  uni.navigateTo({
+    url: '/pages/my/taskDocuments/index'
   })
 }
 

+ 555 - 0
pages/my/taskDocuments/index.vue

@@ -0,0 +1,555 @@
+<template>
+  <view class="task-documents-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">{{ pageTitle }}</text>
+        <view class="placeholder"></view>
+      </view>
+    </view>
+    
+    <!-- 页面内容 -->
+    <scroll-view 
+      scroll-y 
+      class="page-scroll"
+      :style="{ paddingTop: (statusBarHeight * 2 + 88) + 'rpx' }"
+      @scrolltolower="handleLoadMore"
+    >
+      <view class="page-content">
+        <!-- 搜索和筛选区域 -->
+        <view class="search-filter-row">
+          <!-- 搜索框 -->
+          <view class="search-box">
+            <image class="search-icon" src="/static/pages/home/search.png" mode="aspectFit" />
+            <input 
+              class="search-input" 
+              :placeholder="t('taskDocuments.searchPlaceholder')"
+              placeholder-class="search-placeholder"
+              v-model="searchKeyword"
+              @confirm="handleSearch"
+            />
+            <view class="search-btn" @click="handleSearch">
+              <text class="btn-text">{{ t('taskDocuments.search') }}</text>
+            </view>
+          </view>
+          
+          <!-- 状态选择器 -->
+          <view class="status-selector">
+            <picker 
+              mode="selector" 
+              :range="statusOptions" 
+              range-key="label"
+              :value="statusOptions.findIndex(opt => opt.value === taskStatus)"
+              @change="handleStatusChange"
+            >
+              <view class="selector-value">
+                <text class="value-text">{{ selectedStatusLabel }}</text>
+                <text class="arrow-icon">▼</text>
+              </view>
+            </picker>
+          </view>
+        </view>
+        
+        <!-- 文档列表 -->
+        <view class="document-list">
+          <view 
+            v-for="(doc, index) in documentList" 
+            :key="doc.id"
+            class="document-item"
+            @click="handleDocumentClick(doc)"
+          >
+            <image class="doc-thumbnail" :src="getFileIcon(doc.url)" mode="aspectFit" />
+            <view class="doc-info">
+              <text class="doc-name">{{ doc.name }}</text>
+              <view class="doc-meta">
+                <text class="meta-text">{{ t('taskDocuments.createTime') }}:{{ doc.createTime }}</text>
+                <text class="meta-text" v-if="doc.submitter">{{ t('taskDocuments.submitter') }}:{{ doc.submitter }}</text>
+              </view>
+            </view>
+          </view>
+          
+          <!-- 加载状态 -->
+          <view v-if="loading" class="loading-more">
+            <text class="loading-text">{{ t('taskDocuments.loading') }}</text>
+          </view>
+          
+          <!-- 没有更多数据 -->
+          <view v-if="!hasMore && documentList.length > 0" class="no-more">
+            <text class="no-more-text">{{ t('taskDocuments.noMore') }}</text>
+          </view>
+          
+          <!-- 空状态 -->
+          <view v-if="!loading && documentList.length === 0" class="empty-state">
+            <text class="empty-text">{{ t('taskDocuments.empty') }}</text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { getTaskDocuments } from '@/apis/auth'
+
+const { t } = useI18n()
+
+// 状态栏高度
+const statusBarHeight = ref(0)
+
+// 任务状态
+const taskStatus = ref(null)
+
+// 状态选项列表
+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 }
+])
+
+// 当前选中的状态标签
+const selectedStatusLabel = computed(() => {
+  const option = statusOptions.value.find(opt => opt.value === taskStatus.value)
+  return option ? option.label : t('taskDocuments.status.all')
+})
+
+// 搜索关键词
+const searchKeyword = ref('')
+
+// 文档列表
+const documentList = ref([])
+
+// 分页参数
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const loading = ref(false)
+const hasMore = ref(true)
+
+// 页面标题
+const pageTitle = computed(() => {
+  if (taskStatus.value === 0) return t('taskDocuments.toSubmit')
+  if (taskStatus.value === 1) return t('taskDocuments.toAudit')
+  return t('taskDocuments.myTasks')
+})
+
+// 根据文件URL获取文件类型图标
+const getFileIcon = (url) => {
+  if (!url) return '/static/icon/document.svg'
+  
+  // 提取文件扩展名
+  const extension = url.split('.').pop().toLowerCase()
+  
+  // 根据扩展名返回对应图标
+  const iconMap = {
+    // Word文档
+    'doc': '/static/icon/word.svg',
+    'docx': '/static/icon/word.svg',
+    
+    // Excel表格
+    'xls': '/static/icon/excel.svg',
+    'xlsx': '/static/icon/excel.svg',
+    
+    // PowerPoint演示
+    'ppt': '/static/icon/ppt.svg',
+    'pptx': '/static/icon/ppt.svg',
+    
+    // PDF文档
+    'pdf': '/static/icon/pdf.svg'
+  }
+  
+  return iconMap[extension] || '/static/icon/document.svg'
+}
+
+onMounted(() => {
+  // 获取系统信息
+  const windowInfo = uni.getWindowInfo()
+  statusBarHeight.value = windowInfo.statusBarHeight || 0
+  
+  // 获取页面参数
+  const pages = getCurrentPages()
+  const currentPage = pages[pages.length - 1]
+  const status = currentPage.options.status
+  
+  if (status !== undefined && status !== '') {
+    taskStatus.value = parseInt(status)
+  }
+  
+  // 加载文档列表
+  fetchDocuments()
+})
+
+// 获取文档列表
+const fetchDocuments = async (isLoadMore = false) => {
+  if (loading.value) return
+  
+  try {
+    loading.value = true
+    
+    // 构建请求参数
+    const params = {
+      pageNum: pageNum.value,
+      pageSize: pageSize.value
+    }
+    
+    // 只有当搜索关键词不为空时才添加 name 参数
+    if (searchKeyword.value && searchKeyword.value.trim()) {
+      params.name = searchKeyword.value.trim()
+    }
+    
+    // 只有当状态不为空时才添加 status 参数
+    if (taskStatus.value !== null && taskStatus.value !== undefined) {
+      params.status = taskStatus.value
+    }
+    
+    const response = await getTaskDocuments(params)
+    
+    if (response && response.code === 200) {
+      const { rows, total: totalCount } = response
+      
+      total.value = totalCount
+      
+      if (isLoadMore) {
+        // 加载更多,追加数据
+        documentList.value = [...documentList.value, ...rows]
+      } else {
+        // 首次加载或搜索,替换数据
+        documentList.value = rows
+      }
+      
+      // 判断是否还有更多数据
+      hasMore.value = documentList.value.length < total.value
+    }
+  } catch (error) {
+    console.error('获取文档列表失败:', error)
+    
+    if (!isLoadMore) {
+      documentList.value = []
+    }
+    
+    uni.showToast({
+      title: t('taskDocuments.loadFailed'),
+      icon: 'none'
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 加载更多
+const handleLoadMore = () => {
+  if (!hasMore.value || loading.value) return
+  
+  pageNum.value++
+  fetchDocuments(true)
+}
+
+// 搜索
+const handleSearch = () => {
+  // 重置分页
+  pageNum.value = 1
+  hasMore.value = true
+  
+  // 重新加载数据
+  fetchDocuments()
+}
+
+// 状态选择
+const handleStatusChange = (e) => {
+  const index = e.detail.value
+  taskStatus.value = statusOptions.value[index].value
+  
+  // 重置分页
+  pageNum.value = 1
+  hasMore.value = true
+  
+  // 重新加载数据
+  fetchDocuments()
+}
+
+// 点击文档
+const handleDocumentClick = (doc) => {
+  if (!doc.url) {
+    uni.showToast({
+      title: t('taskDocuments.invalidUrl'),
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 构建跳转URL,传递文档信息
+  const params = []
+  if (doc.name) {
+    params.push(`name=${encodeURIComponent(doc.name)}`)
+  }
+  if (doc.url) {
+    params.push(`url=${encodeURIComponent(doc.url)}`)
+  }
+  
+  const queryString = params.join('&')
+  
+  uni.navigateTo({
+    url: `/pages/home/documentViewer/index?${queryString}`
+  })
+}
+
+// 返回
+const handleBack = () => {
+  uni.navigateBack({
+    fail: () => {
+      uni.reLaunch({
+        url: '/pages/my/index'
+      })
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.task-documents-page {
+  width: 100%;
+  height: 100vh;
+  background-color: #f5f5f5;
+  position: relative;
+  overflow: hidden;
+  
+  // 顶部导航栏
+  .header-bg {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    background: linear-gradient(180deg, #1ec9c9 0%, #1eb8b8 100%);
+    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-scroll {
+    width: 100%;
+    height: 100%;
+    
+    .page-content {
+      padding: 24rpx;
+      padding-bottom: 40rpx;
+      
+      // 搜索和筛选行
+      .search-filter-row {
+        display: flex;
+        gap: 16rpx;
+        margin-bottom: 24rpx;
+      }
+      
+      // 搜索框
+      .search-box {
+        flex: 1;
+        background: #ffffff;
+        border-radius: 16rpx;
+        padding: 16rpx 20rpx;
+        display: flex;
+        align-items: center;
+        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+        
+        .search-icon {
+          width: 32rpx;
+          height: 32rpx;
+          margin-right: 12rpx;
+          flex-shrink: 0;
+        }
+        
+        .search-input {
+          flex: 1;
+          font-size: 26rpx;
+          color: #333333;
+          min-width: 0;
+        }
+        
+        .search-placeholder {
+          color: #999999;
+        }
+        
+        .search-btn {
+          margin-left: 12rpx;
+          padding: 8rpx 20rpx;
+          background: linear-gradient(135deg, #1ec9c9 0%, #17b3b3 100%);
+          border-radius: 8rpx;
+          flex-shrink: 0;
+          
+          &:active {
+            opacity: 0.8;
+          }
+          
+          .btn-text {
+            font-size: 24rpx;
+            color: #ffffff;
+            font-weight: 500;
+          }
+        }
+      }
+      
+      // 状态选择器
+      .status-selector {
+        background: #ffffff;
+        border-radius: 16rpx;
+        padding: 16rpx 20rpx;
+        display: flex;
+        align-items: center;
+        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+        flex-shrink: 0;
+        
+        picker {
+          display: flex;
+        }
+        
+        .selector-value {
+          display: flex;
+          align-items: center;
+          gap: 8rpx;
+          
+          .value-text {
+            font-size: 26rpx;
+            color: #1ec9c9;
+            font-weight: 500;
+            white-space: nowrap;
+          }
+          
+          .arrow-icon {
+            font-size: 20rpx;
+            color: #1ec9c9;
+          }
+        }
+      }
+      
+      // 文档列表
+      .document-list {
+        .document-item {
+          background: #ffffff;
+          border-radius: 16rpx;
+          padding: 24rpx;
+          display: flex;
+          align-items: center;
+          margin-bottom: 16rpx;
+          box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+          
+          &:active {
+            background-color: #f8f8f8;
+          }
+          
+          .doc-thumbnail {
+            width: 100rpx;
+            height: 120rpx;
+            border-radius: 8rpx;
+            margin-right: 24rpx;
+            background-color: #f0f0f0;
+            padding: 10rpx;
+            box-sizing: border-box;
+          }
+          
+          .doc-info {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            gap: 12rpx;
+            
+            .doc-name {
+              font-size: 28rpx;
+              font-weight: 500;
+              color: #333333;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+            
+            .doc-meta {
+              display: flex;
+              flex-direction: column;
+              gap: 8rpx;
+              
+              .meta-text {
+                font-size: 24rpx;
+                color: #999999;
+              }
+            }
+          }
+        }
+        
+        // 加载更多
+        .loading-more {
+          padding: 32rpx;
+          text-align: center;
+          
+          .loading-text {
+            font-size: 24rpx;
+            color: #999999;
+          }
+        }
+        
+        // 没有更多
+        .no-more {
+          padding: 32rpx;
+          text-align: center;
+          
+          .no-more-text {
+            font-size: 24rpx;
+            color: #999999;
+          }
+        }
+        
+        // 空状态
+        .empty-state {
+          padding: 120rpx 32rpx;
+          text-align: center;
+          
+          .empty-text {
+            font-size: 28rpx;
+            color: #999999;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 519 - 257
pages/scan/index.vue

@@ -19,13 +19,6 @@
         class="camera"
         @error="handleCameraError"
       >
-        <!-- 检测边框覆盖层 -->
-        <canvas 
-          v-if="detectedBorders.length > 0"
-          canvas-id="borderCanvas" 
-          class="border-canvas"
-          :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
-        ></canvas>
       </camera>
       
       <!-- 模式切换按钮 - 在底部操作栏上方 -->
@@ -48,6 +41,46 @@
         </view>
       </view>
       
+      <!-- 多页模式缩略图列表 -->
+      <view v-if="scanMode === 'multiple' && pdfDataList.length > 0" class="thumbnail-list">
+        <scroll-view scroll-x class="thumbnail-scroll">
+          <view class="thumbnail-wrapper">
+            <view 
+              v-for="(item, index) in pdfDataList" 
+              :key="index" 
+              class="thumbnail-item"
+              @click="handleViewAllPdf"
+            >
+              <view class="pdf-thumbnail-icon">
+                <image src="/static/icon/pdf.svg" mode="aspectFit" class="pdf-icon-img" />
+              </view>
+              <view class="thumbnail-delete" @click.stop="handleDeletePdf(index)">
+                <text class="delete-icon">×</text>
+              </view>
+              <view class="thumbnail-index">{{ index + 1 }}</view>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+      
+      <!-- 上传按钮(多页模式且有图片时显示) -->
+      <view 
+        v-if="scanMode === 'multiple' && imageBase64List.length > 0" 
+        class="upload-btn"
+        @click="handleMultipleUpload"
+      >
+        <text class="upload-text">上传</text>
+      </view>
+      
+      <!-- PDF缩略图预览(右下角) -->
+      <view v-if="showPdfThumbnail" class="pdf-thumbnail" @click="handleOpenPdf">
+        <view class="pdf-close" @click.stop="handleClosePdf">
+          <text class="close-icon">×</text>
+        </view>
+        <image class="pdf-icon" src="/static/icon/pdf.svg" mode="aspectFit" />
+        <text class="pdf-text">PDF</text>
+      </view>
+      
       <!-- 底部操作按钮 -->
       <view class="action-buttons">
         <view class="action-btn" @click="handleSelectImage">
@@ -71,6 +104,7 @@
 <script setup>
 import { ref, onMounted, onUnmounted } from 'vue'
 import { useI18n } from 'vue-i18n'
+import { scanUpload } from '@/apis/scan'
 
 const { t } = useI18n()
 
@@ -80,45 +114,38 @@ const statusBarHeight = ref(0)
 // 扫描模式:single-单页,multiple-多页
 const scanMode = ref('single')
 
-// 检测到的边框
-const detectedBorders = ref([])
+// 图片base64数组(用于多页模式)
+const imageBase64List = ref([])
 
-// Canvas尺寸
-const canvasWidth = ref(0)
-const canvasHeight = ref(0)
+// 图片临时路径数组(用于显示缩略图)
+const imageTempPaths = ref([])
 
-// 相机上下文
-let cameraContext = null
+// PDF数据数组(多页模式,包含路径和base64)
+const pdfDataList = ref([])
+
+// 缓存的ossId
+const cachedOssId = ref(null)
 
-// Canvas上下文
-let canvasContext = null
+// PDF文件路径(用于预览)
+const pdfFilePath = ref('')
 
-// 扫描定时器
-let scanTimer = null
+// 是否显示PDF缩略图
+const showPdfThumbnail = ref(false)
+
+// 相机上下文
+let cameraContext = null
 
 onMounted(() => {
   // 获取系统信息
   const windowInfo = uni.getWindowInfo()
   statusBarHeight.value = windowInfo.statusBarHeight || 0
   
-  // 获取屏幕尺寸
-  const systemInfo = uni.getSystemInfoSync()
-  canvasWidth.value = systemInfo.windowWidth
-  canvasHeight.value = systemInfo.windowHeight - statusBarHeight.value - 88 - 200 // 减去头部和底部按钮高度
-  
   // 初始化相机上下文
   cameraContext = uni.createCameraContext()
-  
-  // 初始化Canvas上下文
-  canvasContext = uni.createCanvasContext('borderCanvas')
-  
-  // 开始实物检测
-  startDetection()
 })
 
 onUnmounted(() => {
-  // 停止检测
-  stopDetection()
+  // 清理资源
 })
 
 // 返回上一页
@@ -172,199 +199,37 @@ const handleCameraError = (e) => {
   })
 }
 
-// 开始实物检测
-const startDetection = () => {
-  // 每隔一段时间进行一次实物检测
-  scanTimer = setInterval(() => {
-    detectObject()
-  }, 300) // 每300ms检测一次
-}
-
-// 停止检测
-const stopDetection = () => {
-  if (scanTimer) {
-    clearInterval(scanTimer)
-    scanTimer = null
-  }
-}
-
-// 检测实物
-const detectObject = () => {
-  if (!cameraContext) return
-  
-  // 拍摄临时照片用于检测
-  cameraContext.takePhoto({
-    quality: 'low', // 使用低质量以提高检测速度
-    success: (res) => {
-      // 分析图片检测实物
-      analyzeImage(res.tempImagePath)
-    },
-    fail: () => {
-      // 静默失败,继续下一次检测
-    }
-  })
-}
-
-// 分析图片中的实物
-const analyzeImage = (imagePath) => {
-  // TODO: 调用后端API或图像识别服务检测实物
-  // 这里需要接入实际的物体检测API,例如:
-  // - 百度AI文字识别OCR
-  // - 腾讯云文档识别
-  // - 阿里云文档检测
-  
-  // 模拟检测结果 - 检测到1-3个实物
-  const hasObject = Math.random() > 0.3 // 70%概率检测到实物
-  
-  if (hasObject) {
-    const objectCount = Math.floor(Math.random() * 3) + 1 // 1-3个实物
-    const borders = []
-    
-    for (let i = 0; i < objectCount; i++) {
-      // 生成随机位置的实物边框(模拟检测结果)
-      const x = Math.random() * (canvasWidth.value - 200) + 50
-      const y = Math.random() * (canvasHeight.value - 300) + 50
-      const width = Math.random() * 200 + 150
-      const height = Math.random() * 250 + 200
-      
-      borders.push({
-        x,
-        y,
-        width,
-        height,
-        // 四个角点坐标(用于绘制更精确的边框)
-        points: [
-          { x, y }, // 左上
-          { x: x + width, y }, // 右上
-          { x: x + width, y: y + height }, // 右下
-          { x, y: y + height } // 左下
-        ]
-      })
-    }
-    
-    detectedBorders.value = borders
-    
-    // 绘制边框
-    drawBorders(borders)
-    
-    // 自动对焦到第一个检测到的实物
-    if (borders.length > 0) {
-      autoFocus(borders[0])
-    }
-  } else {
-    // 没有检测到实物,清空边框
-    detectedBorders.value = []
-    clearCanvas()
-  }
-}
-
-// 绘制检测到的边框
-const drawBorders = (borders) => {
-  if (!canvasContext || !borders || borders.length === 0) return
-  
-  // 清空画布
-  canvasContext.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
-  
-  // 绘制每个检测到的边框
-  borders.forEach((border, index) => {
-    // 设置边框样式
-    canvasContext.setStrokeStyle('#1ec9c9')
-    canvasContext.setLineWidth(3)
-    canvasContext.setLineDash([10, 5], 0) // 虚线效果
-    
-    // 绘制四边形边框
-    canvasContext.beginPath()
-    canvasContext.moveTo(border.points[0].x, border.points[0].y)
-    border.points.forEach((point, i) => {
-      if (i > 0) {
-        canvasContext.lineTo(point.x, point.y)
-      }
-    })
-    canvasContext.closePath()
-    canvasContext.stroke()
-    
-    // 绘制四个角点
-    border.points.forEach(point => {
-      canvasContext.beginPath()
-      canvasContext.arc(point.x, point.y, 8, 0, 2 * Math.PI)
-      canvasContext.setFillStyle('#1ec9c9')
-      canvasContext.fill()
-      
-      // 角点外圈
-      canvasContext.beginPath()
-      canvasContext.arc(point.x, point.y, 12, 0, 2 * Math.PI)
-      canvasContext.setStrokeStyle('#ffffff')
-      canvasContext.setLineWidth(2)
-      canvasContext.stroke()
-    })
-    
-    // 添加半透明填充
-    canvasContext.setFillStyle('rgba(30, 201, 201, 0.1)')
-    canvasContext.beginPath()
-    canvasContext.moveTo(border.points[0].x, border.points[0].y)
-    border.points.forEach((point, i) => {
-      if (i > 0) {
-        canvasContext.lineTo(point.x, point.y)
-      }
-    })
-    canvasContext.closePath()
-    canvasContext.fill()
-  })
-  
-  // 绘制到屏幕
-  canvasContext.draw()
-}
-
-// 清空画布
-const clearCanvas = () => {
-  if (!canvasContext) return
-  canvasContext.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
-  canvasContext.draw()
-}
-
-// 自动对焦到实物
-const autoFocus = (position) => {
-  if (!cameraContext || !position) return
-  
-  // 计算实物中心点(相对于屏幕的比例)
-  const centerX = (position.x + position.width / 2) / 750
-  const centerY = (position.y + position.height / 2) / 1334
-  
-  // 设置对焦点
-  console.log('自动对焦到实物:', centerX, centerY)
-  
-  // 注意:实际对焦效果取决于设备和平台支持
-}
-
 // 拍照
 const handleCapture = () => {
   if (!cameraContext) return
   
-  // 暂停检测
-  stopDetection()
-  
   cameraContext.takePhoto({
     quality: 'high',
-    success: (res) => {
-      console.log('拍照成功:', res.tempImagePath)
-      
-      uni.showLoading({
-        title: '处理中...',
-        mask: true
-      })
-      
-      // TODO: 上传图片到服务器
-      setTimeout(() => {
-        uni.hideLoading()
+    success: async (res) => {
+      try {
+        uni.showLoading({
+          title: '处理中...',
+          mask: true
+        })
         
+        // 将图片转换为base64
+        const base64Data = await imageToBase64(res.tempImagePath)
+        
+        if (scanMode.value === 'single') {
+          // 单页模式:直接上传
+          await uploadSingleImage(base64Data)
+        } else {
+          // 多页模式:立即上传并扫描
+          await uploadAndScanImage(base64Data)
+        }
+      } catch (error) {
+        uni.hideLoading()
+        console.error('处理图片失败:', error)
         uni.showToast({
-          title: '拍照成功',
-          icon: 'success'
+          title: '处理失败',
+          icon: 'none'
         })
-        
-        // 恢复检测
-        startDetection()
-      }, 1000)
+      }
     },
     fail: (err) => {
       console.error('拍照失败:', err)
@@ -372,57 +237,51 @@ const handleCapture = () => {
         title: '拍照失败',
         icon: 'none'
       })
-      
-      // 恢复检测
-      startDetection()
     }
   })
 }
 
 // 导入图片
-const handleSelectImage = () => {
-  stopDetection()
-  
+const handleSelectImage = async () => {
   uni.chooseImage({
     count: 1,
     sizeType: ['original', 'compressed'],
     sourceType: ['album'],
-    success: (res) => {
-      console.log('选择图片成功:', res.tempFilePaths)
-      
-      uni.showLoading({
-        title: '处理中...',
-        mask: true
-      })
-      
-      // TODO: 上传图片到服务器
-      setTimeout(() => {
-        uni.hideLoading()
+    success: async (res) => {
+      try {
+        uni.showLoading({
+          title: '处理中...',
+          mask: true
+        })
+        
+        // 将图片转换为base64
+        const base64Data = await imageToBase64(res.tempFilePaths[0])
         
+        if (scanMode.value === 'single') {
+          // 单页模式:直接上传
+          await uploadSingleImage(base64Data)
+        } else {
+          // 多页模式:立即上传并扫描
+          await uploadAndScanImage(base64Data)
+        }
+      } catch (error) {
+        uni.hideLoading()
+        console.error('处理图片失败:', error)
         uni.showToast({
-          title: '图片导入成功',
-          icon: 'success'
+          title: '处理失败',
+          icon: 'none'
         })
-        
-        startDetection()
-      }, 1000)
-    },
-    fail: () => {
-      startDetection()
+      }
     }
   })
 }
 
 // 导入文档
 const handleSelectFile = () => {
-  stopDetection()
-  
   uni.chooseMessageFile({
     count: 1,
     type: 'file',
     success: (res) => {
-      console.log('选择文件成功:', res.tempFiles)
-      
       uni.showLoading({
         title: '处理中...',
         mask: true
@@ -436,15 +295,266 @@ const handleSelectFile = () => {
           title: '文档导入成功',
           icon: 'success'
         })
-        
-        startDetection()
       }, 1000)
+    }
+  })
+}
+
+// 将图片转换为base64
+const imageToBase64 = (filePath) => {
+  return new Promise((resolve, reject) => {
+    uni.getFileSystemManager().readFile({
+      filePath: filePath,
+      encoding: 'base64',
+      success: (res) => {
+        resolve(res.data)
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}
+
+// 单页模式上传
+const uploadSingleImage = async (base64Data) => {
+  try {
+    // 调用上传接口(单个文件)
+    const response = await scanUpload({
+      file: base64Data
+    })
+    
+    uni.hideLoading()
+    
+    if (response.code === 200 && response.data) {
+      // 缓存ossId
+      cachedOssId.value = response.data.ossId
+      
+      // 处理PDF预览
+      if (response.data.fileBase64) {
+        await handlePdfPreview(response.data.fileBase64)
+      } else {
+        uni.showToast({
+          title: '上传成功',
+          icon: 'success',
+          duration: 2000
+        })
+      }
+    } else {
+      throw new Error(response.msg || '上传失败')
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('上传失败:', error)
+    uni.showToast({
+      title: error.message || '上传失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 多页模式批量上传
+const handleMultipleUpload = async () => {
+  if (imageBase64List.value.length === 0) {
+    uni.showToast({
+      title: '请先添加图片',
+      icon: 'none'
+    })
+    return
+  }
+  
+  try {
+    uni.showLoading({
+      title: '上传中...',
+      mask: true
+    })
+    
+    // 调用上传接口
+    const response = await scanUpload({
+      files: imageBase64List.value
+    })
+    
+    uni.hideLoading()
+    
+    if (response.code === 200 && response.data) {
+      // 缓存ossId
+      cachedOssId.value = response.data.ossId
+      
+      // 清空数组
+      imageBase64List.value = []
+      imageTempPaths.value = []
+      
+      // 处理PDF预览
+      if (response.data.fileBase64) {
+        await handlePdfPreview(response.data.fileBase64)
+      } else {
+        uni.showToast({
+          title: '上传成功',
+          icon: 'success',
+          duration: 2000
+        })
+      }
+    } else {
+      throw new Error(response.msg || '上传失败')
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('上传失败:', error)
+    uni.showToast({
+      title: error.message || '上传失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 多页模式:上传并扫描单张图片
+const uploadAndScanImage = async (base64Data) => {
+  try {
+    // 调用上传接口(单个文件)
+    const response = await scanUpload({
+      file: base64Data
+    })
+    
+    uni.hideLoading()
+    
+    if (response.code === 200 && response.data && response.data.fileBase64) {
+      // 将PDF转换为临时文件
+      const pdfPath = await base64ToTempFile(response.data.fileBase64)
+      
+      // 添加到PDF数据列表(包含路径和base64)
+      pdfDataList.value.push({
+        path: pdfPath,
+        base64: response.data.fileBase64
+      })
+      
+      uni.showToast({
+        title: `已扫描第${pdfDataList.value.length}页`,
+        icon: 'success',
+        duration: 1500
+      })
+    } else {
+      throw new Error(response.msg || '扫描失败')
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('扫描失败:', error)
+    uni.showToast({
+      title: error.message || '扫描失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 删除指定图片
+const handleDeleteImage = (index) => {
+  imageBase64List.value.splice(index, 1)
+  imageTempPaths.value.splice(index, 1)
+  
+  uni.showToast({
+    title: '已删除',
+    icon: 'success',
+    duration: 1000
+  })
+}
+
+// 删除指定PDF
+const handleDeletePdf = (index) => {
+  pdfDataList.value.splice(index, 1)
+  
+  uni.showToast({
+    title: '已删除',
+    icon: 'success',
+    duration: 1000
+  })
+}
+
+// 查看所有PDF
+const handleViewAllPdf = () => {
+  if (pdfDataList.value.length === 0) {
+    uni.showToast({
+      title: '暂无PDF',
+      icon: 'none'
+    })
+    return
+  }
+  
+  // 使用全局数据存储PDF数据(包含路径和base64)
+  getApp().globalData.pdfData = pdfDataList.value
+  
+  uni.navigateTo({
+    url: '/pages/scan/pdfViewer/index'
+  })
+}
+
+// 处理PDF预览
+const handlePdfPreview = async (base64Data) => {
+  try {
+    // 将base64转换为临时文件
+    const filePath = await base64ToTempFile(base64Data)
+    pdfFilePath.value = filePath
+    showPdfThumbnail.value = true
+    
+    uni.showToast({
+      title: '上传成功',
+      icon: 'success',
+      duration: 2000
+    })
+  } catch (error) {
+    console.error('处理PDF失败:', error)
+    uni.showToast({
+      title: '处理PDF失败',
+      icon: 'none'
+    })
+  }
+}
+
+// 将base64转换为临时文件
+const base64ToTempFile = (base64Data) => {
+  return new Promise((resolve, reject) => {
+    const fs = uni.getFileSystemManager()
+    const fileName = `scan_${Date.now()}.pdf`
+    const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`
+    
+    fs.writeFile({
+      filePath: filePath,
+      data: base64Data,
+      encoding: 'base64',
+      success: () => {
+        resolve(filePath)
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}
+
+// 打开PDF预览
+const handleOpenPdf = () => {
+  if (!pdfFilePath.value) return
+  
+  uni.openDocument({
+    filePath: pdfFilePath.value,
+    fileType: 'pdf',
+    showMenu: true,
+    success: () => {
+      // 预览成功
     },
-    fail: () => {
-      startDetection()
+    fail: (err) => {
+      console.error('打开文档失败:', err)
+      uni.showToast({
+        title: '打开文档失败',
+        icon: 'none'
+      })
     }
   })
 }
+
+// 关闭PDF缩略图
+const handleClosePdf = () => {
+  showPdfThumbnail.value = false
+  pdfFilePath.value = ''
+}
 </script>
 
 <style lang="scss" scoped>
@@ -507,15 +617,6 @@ const handleSelectFile = () => {
       flex: 1;
       width: 100%;
       position: relative;
-      
-      // 边框检测画布
-      .border-canvas {
-        position: absolute;
-        top: 0;
-        left: 0;
-        z-index: 10;
-        pointer-events: none;
-      }
     }
     
     // 模式切换按钮 - 在底部操作栏上方
@@ -561,6 +662,167 @@ const handleSelectFile = () => {
       }
     }
     
+    // 多页模式缩略图列表
+    .thumbnail-list {
+      position: absolute;
+      bottom: 220rpx;
+      left: 0;
+      right: 0;
+      height: 160rpx;
+      background: rgba(0, 0, 0, 0.6);
+      backdrop-filter: blur(10rpx);
+      
+      .thumbnail-scroll {
+        height: 100%;
+        white-space: nowrap;
+        
+        .thumbnail-wrapper {
+          display: inline-flex;
+          padding: 20rpx;
+          gap: 16rpx;
+          
+          .thumbnail-item {
+            position: relative;
+            width: 120rpx;
+            height: 120rpx;
+            border-radius: 12rpx;
+            overflow: hidden;
+            background: #ffffff;
+            border: 2rpx solid #e0e0e0;
+            
+            .thumbnail-image {
+              width: 100%;
+              height: 100%;
+            }
+            
+            .pdf-thumbnail-icon {
+              width: 100%;
+              height: 100%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              background: #f5f5f5;
+              
+              .pdf-icon-img {
+                width: 60rpx;
+                height: 60rpx;
+              }
+            }
+            
+            .thumbnail-delete {
+              position: absolute;
+              top: 4rpx;
+              right: 4rpx;
+              width: 36rpx;
+              height: 36rpx;
+              border-radius: 50%;
+              background: rgba(0, 0, 0, 0.7);
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              
+              .delete-icon {
+                font-size: 32rpx;
+                color: #ffffff;
+                line-height: 1;
+              }
+            }
+            
+            .thumbnail-index {
+              position: absolute;
+              bottom: 4rpx;
+              left: 4rpx;
+              padding: 2rpx 8rpx;
+              background: rgba(0, 0, 0, 0.7);
+              border-radius: 8rpx;
+              font-size: 20rpx;
+              color: #ffffff;
+            }
+          }
+        }
+      }
+    }
+    
+    // 上传按钮
+    .upload-btn {
+      position: absolute;
+      bottom: 240rpx;
+      right: 32rpx;
+      width: 120rpx;
+      height: 80rpx;
+      background: #1ec9c9;
+      border-radius: 40rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-shadow: 0 4rpx 16rpx rgba(30, 201, 201, 0.4);
+      z-index: 20;
+      
+      &:active {
+        transform: scale(0.95);
+      }
+      
+      .upload-text {
+        font-size: 28rpx;
+        font-weight: 600;
+        color: #ffffff;
+      }
+    }
+    
+    // PDF缩略图
+    .pdf-thumbnail {
+      position: absolute;
+      bottom: 240rpx;
+      right: 32rpx;
+      width: 140rpx;
+      height: 140rpx;
+      background: #ffffff;
+      border-radius: 16rpx;
+      box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      z-index: 20;
+      
+      &:active {
+        transform: scale(0.95);
+      }
+      
+      .pdf-close {
+        position: absolute;
+        top: -8rpx;
+        right: -8rpx;
+        width: 40rpx;
+        height: 40rpx;
+        border-radius: 50%;
+        background: #ff4444;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-shadow: 0 2rpx 8rpx rgba(255, 68, 68, 0.3);
+        
+        .close-icon {
+          font-size: 36rpx;
+          color: #ffffff;
+          line-height: 1;
+          font-weight: 300;
+        }
+      }
+      
+      .pdf-icon {
+        width: 60rpx;
+        height: 60rpx;
+        margin-bottom: 8rpx;
+      }
+      
+      .pdf-text {
+        font-size: 24rpx;
+        color: #666666;
+        font-weight: 500;
+      }
+    }
+    
     // 底部操作按钮
     .action-buttons {
       position: absolute;

+ 405 - 0
pages/scan/pdfViewer/index.vue

@@ -0,0 +1,405 @@
+<template>
+  <view class="pdf-viewer-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">PDF管理 (共{{ pdfList.length }}页)</text>
+        <view class="action-btn" @click="handleUploadAll">
+          <text class="action-text">上传</text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- PDF列表 -->
+    <scroll-view scroll-y class="pdf-list">
+      <view 
+        v-for="(item, index) in pdfList" 
+        :key="item.id" 
+        class="pdf-item"
+      >
+        <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>
+        
+        <!-- 操作按钮 -->
+        <view class="pdf-actions">
+          <view 
+            v-if="index > 0" 
+            class="action-icon" 
+            @click="handleMoveUp(index)"
+          >
+            <text class="icon-text">↑</text>
+          </view>
+          <view 
+            v-if="index < pdfList.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 v-if="pdfList.length === 0" class="empty-tip">
+        <text class="empty-text">暂无PDF文档</text>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { scanUploadBatch } from '@/apis/scan'
+
+// 状态栏高度
+const statusBarHeight = ref(0)
+
+// PDF列表(包含路径和base64)
+const pdfList = ref([])
+
+// 生成唯一ID
+let idCounter = 0
+const generateId = () => {
+  return `pdf_${Date.now()}_${idCounter++}`
+}
+
+onMounted(() => {
+  // 获取系统信息
+  const windowInfo = uni.getWindowInfo()
+  statusBarHeight.value = windowInfo.statusBarHeight || 0
+  
+  // 从全局数据获取PDF路径和base64
+  const app = getApp()
+  if (app.globalData && app.globalData.pdfData) {
+    pdfList.value = app.globalData.pdfData.map(item => ({
+      id: generateId(),
+      path: item.path,
+      base64: item.base64
+    }))
+  }
+})
+
+// 返回上一页
+const handleBack = () => {
+  uni.navigateBack()
+}
+
+// 预览PDF
+const handlePreviewPdf = (path, index) => {
+  uni.openDocument({
+    filePath: path,
+    fileType: 'pdf',
+    showMenu: true,
+    success: () => {
+      // 预览成功
+    },
+    fail: (err) => {
+      console.error('打开文档失败:', err)
+      uni.showToast({
+        title: '打开文档失败',
+        icon: 'none'
+      })
+    }
+  })
+}
+
+// 上移
+const handleMoveUp = (index) => {
+  if (index === 0) return
+  
+  const temp = pdfList.value[index]
+  pdfList.value[index] = pdfList.value[index - 1]
+  pdfList.value[index - 1] = temp
+  
+  // 触发响应式更新
+  pdfList.value = [...pdfList.value]
+  
+  uni.showToast({
+    title: '已上移',
+    icon: 'success',
+    duration: 1000
+  })
+}
+
+// 下移
+const handleMoveDown = (index) => {
+  if (index === pdfList.value.length - 1) return
+  
+  const temp = pdfList.value[index]
+  pdfList.value[index] = pdfList.value[index + 1]
+  pdfList.value[index + 1] = temp
+  
+  // 触发响应式更新
+  pdfList.value = [...pdfList.value]
+  
+  uni.showToast({
+    title: '已下移',
+    icon: 'success',
+    duration: 1000
+  })
+}
+
+// 删除
+const handleDelete = (index) => {
+  uni.showModal({
+    title: '确认删除',
+    content: `确定要删除第${index + 1}页吗?`,
+    confirmText: '删除',
+    cancelText: '取消',
+    success: (res) => {
+      if (res.confirm) {
+        pdfList.value.splice(index, 1)
+        
+        uni.showToast({
+          title: '已删除',
+          icon: 'success',
+          duration: 1000
+        })
+      }
+    }
+  })
+}
+
+// 上传所有PDF
+const handleUploadAll = async () => {
+  if (pdfList.value.length === 0) {
+    uni.showToast({
+      title: '请先添加PDF',
+      icon: 'none'
+    })
+    return
+  }
+  
+  uni.showModal({
+    title: '确认上传',
+    content: `将按当前顺序上传${pdfList.value.length}个PDF文档`,
+    confirmText: '上传',
+    cancelText: '取消',
+    success: async (res) => {
+      if (res.confirm) {
+        try {
+          uni.showLoading({
+            title: '上传中...',
+            mask: true
+          })
+          
+          // 按顺序提取base64数组
+          const base64Array = pdfList.value.map(item => item.base64)
+          
+          // 调用批量上传接口
+          const response = await scanUploadBatch({
+            files: base64Array
+          })
+          
+          uni.hideLoading()
+          
+          if (response.code === 200 && response.data) {
+            uni.showToast({
+              title: '上传成功',
+              icon: 'success',
+              duration: 2000
+            })
+            
+            // 延迟返回上一页
+            setTimeout(() => {
+              uni.navigateBack()
+            }, 2000)
+          } else {
+            throw new Error(response.msg || '上传失败')
+          }
+        } catch (error) {
+          uni.hideLoading()
+          console.error('上传失败:', error)
+          uni.showToast({
+            title: error.message || '上传失败',
+            icon: 'none'
+          })
+        }
+      }
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.pdf-viewer-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;
+      }
+      
+      .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;
+        }
+      }
+    }
+  }
+  
+  // PDF列表
+  .pdf-list {
+    flex: 1;
+    padding: 24rpx;
+    
+    .pdf-item {
+      background: #ffffff;
+      border-radius: 16rpx;
+      padding: 24rpx;
+      margin-bottom: 16rpx;
+      display: flex;
+      align-items: center;
+      box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+      
+      &:active {
+        background: #f8f8f8;
+      }
+      
+      .pdf-icon-wrapper {
+        width: 80rpx;
+        height: 80rpx;
+        background: #f5f5f5;
+        border-radius: 12rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 24rpx;
+        
+        .pdf-icon {
+          width: 48rpx;
+          height: 48rpx;
+        }
+      }
+      
+      .pdf-info {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        gap: 8rpx;
+        
+        .pdf-name {
+          font-size: 32rpx;
+          font-weight: 600;
+          color: #333333;
+        }
+        
+        .pdf-tip {
+          font-size: 24rpx;
+          color: #999999;
+        }
+      }
+      
+      .pdf-actions {
+        display: flex;
+        align-items: center;
+        gap: 12rpx;
+        
+        .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;
+          }
+        }
+      }
+      
+      .arrow-icon {
+        font-size: 48rpx;
+        color: #cccccc;
+        font-weight: 300;
+      }
+    }
+    
+    .empty-tip {
+      padding: 200rpx 0;
+      text-align: center;
+      
+      .empty-text {
+        font-size: 28rpx;
+        color: #999999;
+      }
+    }
+  }
+}
+</style>

+ 116 - 0
pages/scan/preview/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <view class="preview-page">
+    <view v-if="isLoading" class="loading-container">
+      <text class="loading-text">正在加载PDF...</text>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+
+// 加载状态
+const isLoading = ref(true)
+
+onMounted(async () => {
+  // 获取页面参数
+  const pages = getCurrentPages()
+  const currentPage = pages[pages.length - 1]
+  const options = currentPage.options || {}
+  
+  if (options.base64) {
+    try {
+      // 解码base64
+      const base64Data = decodeURIComponent(options.base64)
+      
+      // 将base64转换为临时文件
+      const filePath = await base64ToTempFile(base64Data)
+      
+      // 打开文档预览
+      uni.openDocument({
+        filePath: filePath,
+        fileType: 'pdf',
+        showMenu: true,
+        success: () => {
+          isLoading.value = false
+          // 预览打开后返回上一页
+          setTimeout(() => {
+            uni.navigateBack()
+          }, 500)
+        },
+        fail: (err) => {
+          console.error('打开文档失败:', err)
+          isLoading.value = false
+          uni.showToast({
+            title: '打开文档失败',
+            icon: 'none'
+          })
+          setTimeout(() => {
+            uni.navigateBack()
+          }, 1500)
+        }
+      })
+    } catch (error) {
+      console.error('处理PDF失败:', error)
+      isLoading.value = false
+      uni.showToast({
+        title: '处理PDF失败',
+        icon: 'none'
+      })
+      setTimeout(() => {
+        uni.navigateBack()
+      }, 1500)
+    }
+  } else {
+    isLoading.value = false
+    uni.showToast({
+      title: '缺少PDF数据',
+      icon: 'none'
+    })
+    setTimeout(() => {
+      uni.navigateBack()
+    }, 1500)
+  }
+})
+
+// 将base64转换为临时文件
+const base64ToTempFile = (base64Data) => {
+  return new Promise((resolve, reject) => {
+    const fs = uni.getFileSystemManager()
+    const fileName = `${Date.now()}.pdf`
+    const filePath = `${wx.env.USER_DATA_PATH}/${fileName}`
+    
+    fs.writeFile({
+      filePath: filePath,
+      data: base64Data,
+      encoding: 'base64',
+      success: () => {
+        resolve(filePath)
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.preview-page {
+  width: 100%;
+  height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f5f5;
+  
+  .loading-container {
+    text-align: center;
+    
+    .loading-text {
+      font-size: 28rpx;
+      color: #666666;
+    }
+  }
+}
+</style>

BIN
scan.png


+ 1 - 0
static/icon/document.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="1765935444141" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9771" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M224 831.936V192.096L223.808 192H576v159.936c0 35.328 28.736 64.064 64.064 64.064h159.712c0.032 0.512 0.224 1.184 0.224 1.664L800.256 832 224 831.936zM757.664 352L640 351.936V224.128L757.664 352z m76.064-11.872l-163.872-178.08C651.712 142.336 619.264 128 592.672 128H223.808A64.032 64.032 0 0 0 160 192.096v639.84A64 64 0 0 0 223.744 896h576.512A64 64 0 0 0 864 831.872V417.664c0-25.856-12.736-58.464-30.272-77.536z" fill="#3E3A39" p-id="9772"></path><path d="M640 512h-256a32 32 0 0 0 0 64h256a32 32 0 0 0 0-64M640 672h-256a32 32 0 0 0 0 64h256a32 32 0 0 0 0-64" fill="#3E3A39" p-id="9773"></path></svg>

+ 1 - 0
static/icon/excel.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="1765935395549" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7714" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z" fill="#45B058" p-id="7715"></path><path d="M374.4 862.4c-3.2 0-6.4-1.6-8-3.2l-59.2-80-60.8 80c-1.6 1.6-4.8 3.2-8 3.2-6.4 0-11.2-4.8-11.2-11.2 0-1.6 0-4.8 1.6-6.4l62.4-81.6-57.6-78.4c-1.6-1.6-3.2-3.2-3.2-6.4 0-4.8 4.8-11.2 11.2-11.2 4.8 0 8 1.6 9.6 4.8l56 73.6 54.4-73.6c1.6-3.2 4.8-4.8 8-4.8 6.4 0 12.8 4.8 12.8 11.2 0 3.2-1.6 4.8-1.6 6.4l-59.2 76.8 62.4 83.2c1.6 1.6 3.2 4.8 3.2 6.4 0 6.4-6.4 11.2-12.8 11.2z m160-1.6H448c-9.6 0-17.6-8-17.6-17.6V678.4c0-6.4 4.8-11.2 12.8-11.2 6.4 0 11.2 4.8 11.2 11.2v161.6h80c6.4 0 11.2 4.8 11.2 9.6 0 6.4-4.8 11.2-11.2 11.2z m112 3.2c-28.8 0-51.2-9.6-67.2-24-3.2-1.6-3.2-4.8-3.2-8 0-6.4 3.2-12.8 11.2-12.8 1.6 0 4.8 1.6 6.4 3.2 12.8 11.2 32 20.8 54.4 20.8 33.6 0 44.8-19.2 44.8-33.6 0-49.6-113.6-22.4-113.6-89.6 0-32 27.2-54.4 65.6-54.4 24 0 46.4 8 60.8 20.8 3.2 1.6 4.8 4.8 4.8 8 0 6.4-4.8 12.8-11.2 12.8-1.6 0-4.8-1.6-6.4-3.2-14.4-11.2-32-16-49.6-16-24 0-40 11.2-40 30.4 0 43.2 113.6 17.6 113.6 89.6 0 27.2-19.2 56-70.4 56z" fill="#FFFFFF" p-id="7716"></path><path d="M960 326.4v16H755.2s-102.4-20.8-99.2-108.8c0 0 3.2 92.8 96 92.8h208z" fill="#349C42" p-id="7717"></path><path d="M656 0v233.6c0 25.6 19.2 92.8 99.2 92.8H960L656 0z" fill="#FFFFFF" opacity=".5" p-id="7718"></path></svg>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/icon/pdf.svg


+ 1 - 0
static/icon/ppt.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="1765935337019" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5597" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.6 0C100.8 0 64 36.8 64 81.6v860.8C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z" fill="#E34221" p-id="5598"></path><path d="M960 326.4v16H755.2s-100.8-20.8-99.2-108.8c0 0 4.8 92.8 97.6 92.8H960z" fill="#DC3119" p-id="5599"></path><path d="M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z" fill="#FFFFFF" p-id="5600"></path><path d="M304 784h-54.4v67.2c0 6.4-4.8 11.2-11.2 11.2-6.4 0-12.8-4.8-12.8-11.2V686.4c0-9.6 8-17.6 17.6-17.6H304c38.4 0 59.2 25.6 59.2 57.6S340.8 784 304 784z m-3.2-94.4h-51.2v73.6h51.2c22.4 0 38.4-16 38.4-36.8 0-22.4-16-36.8-38.4-36.8zM480 784h-54.4v67.2c0 6.4-4.8 11.2-11.2 11.2-6.4 0-11.2-4.8-11.2-11.2V686.4c0-9.6 6.4-17.6 16-17.6H480c38.4 0 59.2 25.6 59.2 57.6S518.4 784 480 784z m-3.2-94.4h-49.6v73.6h49.6c22.4 0 38.4-16 38.4-36.8 0-22.4-16-36.8-38.4-36.8z m225.6 0h-52.8v161.6c0 6.4-4.8 11.2-11.2 11.2-6.4 0-12.8-4.8-12.8-11.2V689.6h-51.2c-6.4 0-11.2-4.8-11.2-11.2 0-4.8 4.8-9.6 11.2-9.6h128c6.4 0 11.2 4.8 11.2 11.2 0 4.8-4.8 9.6-11.2 9.6z" fill="#FFFFFF" p-id="5601"></path></svg>

+ 1 - 0
static/icon/word.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="1765935423741" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8763" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M791.94 155.36H332.48c-42.23 0-76.54 34.31-76.54 76.54v76.55h-51.03c-28.16 0-51.04 22.87-51.04 51.03v306.37c0 28.15 22.88 51.02 51.04 51.02h51.03v76.54c0 42.24 34.32 76.55 76.54 76.55h459.46c42.23 0 76.54-34.32 76.54-76.54V231.9c0.1-42.24-34.31-76.54-76.54-76.54z m-547.68 298.6h40.28l26.04 93.58h5.7l29.16-93.58h34.45l29.02 93.71h6.24l26.18-93.71h40.41l-40.41 133.72H384.1l-19.53-70.25h-5.43l-19.26 69.98h-55.74l-39.88-133.45zM817.45 793.5c-0.01 14.09-11.42 25.5-25.51 25.51H332.48c-14.09-0.01-25.5-11.43-25.51-25.51v-76.54h204.21c28.15 0 51.03-22.88 51.03-51.03V614.9H740.9c14.09-0.01 25.51-11.43 25.52-25.52-0.02-14.09-11.43-25.5-25.52-25.51H562.21v-51.03H740.9c14.09-0.01 25.51-11.43 25.52-25.52-0.01-14.09-11.43-25.5-25.52-25.52H562.21v-51.03H740.9c14.09-0.01 25.51-11.43 25.52-25.51-0.01-14.09-11.43-25.51-25.52-25.52H562.21c0-28.16-22.89-51.03-51.03-51.03H306.97v-76.54c0.01-14.09 11.43-25.5 25.51-25.52h459.46c14.08 0.01 25.5 11.43 25.51 25.52V793.5z m0 0" fill="#54A0FF" p-id="8764"></path></svg>

+ 1 - 2
utils/request.js

@@ -7,7 +7,7 @@ import { t } from './i18n.js'
 const CLIENT_ID = '2f847927afb2b3ebeefc870c13d623f2'
 
 // 基础 URL(根据实际情况修改)
-const BASE_URL = 'http://127.0.0.1:8080'
+const BASE_URL = 'http://192.168.1.118:8080'
 
 /**
  * 获取当前语言环境
@@ -50,7 +50,6 @@ const request = (options) => {
           cleanData[key] = value
         }
       })
-      console.log('清理后的请求参数:', cleanData)
     }
     
     uni.request({

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است