rkljw 1 일 전
부모
커밋
2dd2582380
6개의 변경된 파일976개의 추가작업 그리고 9개의 파일을 삭제
  1. 5 1
      src/permission.js
  2. 5 0
      src/router/index.js
  3. 8 8
      src/views/complaint/complaintList.vue
  4. 130 0
      src/views/file/README.md
  5. 443 0
      src/views/file/index.vue
  6. 385 0
      src/views/file/test.vue

+ 5 - 1
src/permission.js

@@ -155,7 +155,11 @@ router.beforeEach(async (to, from, next) => {
             }
             next()
         } else {
-            next(`/login`)
+            if(to.path === '/file'){
+                next()
+            }else{
+                next(`/login`)
+            }
             NProgress.done()
         }
     }

+ 5 - 0
src/router/index.js

@@ -50,6 +50,11 @@ export const constantRoutes = [
       }
     ]
   },
+  {
+    path: '/file',
+    component: () => import('@/views/file/index'),
+    hidden: true
+  },
   {
     path: '/login',
     component: () => import('@/views/login/index'),

+ 8 - 8
src/views/complaint/complaintList.vue

@@ -179,28 +179,28 @@
             <el-input type="textarea" v-model="form.describe" rows="8" class="custom-textarea" disabled placeholder="请输入详细说明"></el-input>
           </el-form-item>
           <el-form-item label="判决书:" :label-width="formLabelWidth" prop="" class="custom-align-right">
-            <div v-if="form.judgment" @click="downloadFile(form.judgment)" class="complaintListdownloadBtn">
+            <div v-if="form.judgment" @click="downloadFile(form.judgment,'判决书')" class="complaintListdownloadBtn">
               <i class="el-icon-download"></i>
               下载
             </div>
             <div v-else>暂无数据</div>
           </el-form-item>
           <el-form-item label="录音录像:" :label-width="formLabelWidth" prop="" class="custom-align-right">
-            <div v-if="form.audio_and_video" @click="downloadFile(form.audio_and_video)" class="complaintListdownloadBtn">
+            <div v-if="form.audio_and_video" @click="downloadFile(form.audio_and_video,'录音录像')" class="complaintListdownloadBtn">
               <i class="el-icon-download"></i>
               下载
             </div>
             <div v-else>暂无数据</div>
           </el-form-item>
           <el-form-item label="合同:" :label-width="formLabelWidth" prop="" class="custom-align-right">
-            <div v-if="form.contract" @click="downloadFile(form.contract)" class="complaintListdownloadBtn">
+            <div v-if="form.contract" @click="downloadFile(form.contract,'合同')" class="complaintListdownloadBtn">
               <i class="el-icon-download"></i>
               下载
             </div>
             <div v-else>暂无数据</div>
           </el-form-item>
           <el-form-item label="资质:" :label-width="formLabelWidth" prop="" class="custom-align-right">
-            <div v-if="form.qualifications" @click="downloadFile(form.qualifications)" class="complaintListdownloadBtn">
+            <div v-if="form.qualifications" @click="downloadFile(form.qualifications,'资质')" class="complaintListdownloadBtn">
               <i class="el-icon-download"></i>
               下载
             </div>
@@ -622,16 +622,16 @@ export default {
 
 
     //4.编辑 start ------------------------------------------------------------>
-    downloadFile(data){
+    downloadFile(data,name){  
       let files = [];
-      let fileName = "download";
+      let fileName = name;
       if (!Array.isArray(data)) {
         data = [data];
       }
       console.log(data)
       for(let item of data){
-        if(item.fileSrc){
-          files.push(item.fileSrc);
+        if(item.imgUrl){
+          files.push(item.imgUrl);
         }else{
           files.push(item)
         }

+ 130 - 0
src/views/file/README.md

@@ -0,0 +1,130 @@
+# 文件上传页面使用说明
+
+## 功能描述
+
+这个H5页面是为小程序提供的web-view页面,主要功能包括:
+
+1. **文件选择**:支持选择多种格式的文件(图片、文档、视频等)
+2. **文件预览**:显示已选择的文件列表,包含文件名、大小等信息
+3. **文件管理**:可以删除不需要的文件
+4. **提交给小程序**:将选择的文件数据提交给小程序
+
+## 支持的文件格式
+
+- 图片:jpg, jpeg, png, gif, bmp, webp
+- 文档:pdf, doc, docx, xls, xlsx, txt
+- 视频:mp4, avi, mov
+- 音频:mp3, wav, aac
+
+## 文件大小限制
+
+- 单个文件最大50MB
+- 建议文件总大小不超过100MB
+
+## 使用方法
+
+### 1. 在小程序中嵌入
+
+在小程序的web-view组件中加载此页面:
+
+```javascript
+// 小程序页面
+<web-view src="https://your-domain.com/file-upload"></web-view>
+```
+
+### 2. 接收文件数据
+
+在小程序中监听web-view发送的消息:
+
+```javascript
+// 小程序页面
+Page({
+  onLoad() {
+    // 监听web-view发送的消息
+    wx.onWebviewMessage((message) => {
+      if (message.data.type === 'file_upload') {
+        const files = message.data.files
+        console.log('接收到文件数据:', files)
+        
+        // 处理文件数据
+        this.handleFiles(files)
+      }
+    })
+  },
+  
+  handleFiles(files) {
+    files.forEach(file => {
+      console.log('文件名:', file.name)
+      console.log('文件类型:', file.type)
+      console.log('文件大小:', file.size)
+      console.log('文件数据:', file.data) // Base64格式
+    })
+  }
+})
+```
+
+### 3. 文件数据结构
+
+每个文件对象包含以下属性:
+
+```javascript
+{
+  name: "文件名.jpg",
+  type: "image/jpeg",
+  size: 1024000, // 字节
+  url: "https://example.com/uploads/file.jpg" // 上传后的文件URL
+}
+```
+
+## 技术实现
+
+### 1. 微信环境检测
+
+页面会自动检测是否在微信环境中运行,如果不是会显示提示信息。
+
+### 2. 文件处理
+
+- 使用HTML5 File API进行文件选择
+- 选择文件后自动上传到服务器
+- 获取文件URL后提交给小程序
+- 支持多文件同时选择和上传
+
+### 3. 与小程序通信
+
+使用微信提供的API与小程序进行通信:
+
+- `window.wx.miniProgram.postMessage()` - 在web-view中向小程序发送消息
+- 这是唯一正确的web-view与小程序通信方式
+
+## 注意事项
+
+1. **必须在微信环境中使用**:此页面依赖微信的API,只能在微信中正常使用
+2. **文件大小限制**:大文件会影响传输性能,建议控制文件大小
+3. **网络环境**:需要稳定的网络连接以确保文件传输成功
+4. **浏览器兼容性**:主要支持现代浏览器,特别是微信内置浏览器
+
+## 样式定制
+
+页面使用了Element UI组件库,可以通过修改CSS来自定义样式:
+
+- 主容器:`.file-upload-container`
+- 上传区域:`.upload-area`
+- 文件列表:`.file-list`
+- 文件项:`.file-item`
+
+## 错误处理
+
+页面包含以下错误处理机制:
+
+- 文件大小超限提示
+- 重复文件检测
+- 网络错误处理
+- 微信环境检测
+
+## 开发调试
+
+在开发过程中,可以通过以下方式调试:
+
+1. 在浏览器控制台查看日志
+2. 使用微信开发者工具进行调试
+3. 检查网络请求和响应 

+ 443 - 0
src/views/file/index.vue

@@ -0,0 +1,443 @@
+<template>
+  <div class="file-upload-container">
+    <div class="header">
+      <h2>文件上传</h2>
+      <p>选择文件后提交给小程序</p>
+    </div>
+
+    <div class="upload-area" @click="triggerFileInput">
+      <div class="upload-icon">
+        <i class="el-icon-upload"></i>
+      </div>
+      <div class="upload-text">
+        <p>点击选择文件</p>
+        <p class="upload-hint">支持图片、文档、视频等格式</p>
+      </div>
+      <input
+        ref="fileInput"
+        type="file"
+        multiple
+        accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.mp4,.mp3"
+        @change="handleFileSelect"
+        style="display: none"
+      />
+    </div>
+
+    <div v-if="selectedFiles.length > 0" class="file-list">
+      <h3>已选择的文件</h3>
+             <div class="file-item" v-for="(file, index) in selectedFiles" :key="index">
+         <div class="file-info">
+           <div class="file-icon">
+             <i :class="getFileIcon(file.type)"></i>
+           </div>
+           <div class="file-details">
+             <div class="file-name">{{ file.name }}</div>
+             <div class="file-size">{{ formatFileSize(file.size) }}</div>
+             <div class="file-status">
+               <span v-if="uploadedFiles[index]" class="status-success">
+                 <i class="el-icon-check"></i> 已上传
+               </span>
+               <span v-else class="status-uploading">
+                 <i class="el-icon-loading"></i> 上传中...
+               </span>
+             </div>
+           </div>
+         </div>
+         <div class="file-actions">
+           <el-button 
+             type="danger" 
+             size="mini" 
+             @click="removeFile(index)"
+             icon="el-icon-delete"
+           >
+             删除
+           </el-button>
+         </div>
+       </div>
+    </div>
+
+    <div class="submit-area">
+      <el-button 
+        type="primary" 
+        size="large" 
+        @click="submitToMiniProgram"
+        :disabled="uploadedFiles.length === 0"
+        :loading="submitting"
+      >
+        {{ submitting ? '提交中...' : '提交给小程序' }} ({{ uploadedFiles.length }}个文件)
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FileUpload',
+  data() {
+    return {
+      selectedFiles: [],
+      uploadedFiles: [], // 存储上传后的文件URL
+      submitting: false
+    }
+  },
+  mounted() {
+    this.checkWechatEnvironment()
+  },
+  methods: {
+    // 检查微信环境
+    checkWechatEnvironment() {
+      const isWechat = /micromessenger/i.test(navigator.userAgent)
+      if (!isWechat) {
+        this.$message.warning('请在微信中打开此页面')
+      }
+    },
+
+    // 触发文件选择
+    triggerFileInput() {
+      this.$refs.fileInput.click()
+    },
+
+    // 处理文件选择
+    async handleFileSelect(event) {
+      const files = Array.from(event.target.files)
+      
+      for (const file of files) {
+        // 检查文件大小限制 (50MB)
+        if (file.size > 50 * 1024 * 1024) {
+          this.$message.error(`文件 ${file.name} 超过50MB限制`)
+          continue
+        }
+        
+        // 检查是否已存在同名文件
+        const exists = this.selectedFiles.find(f => f.name === file.name)
+        if (exists) {
+          this.$message.warning(`文件 ${file.name} 已存在`)
+          continue
+        }
+        
+        // 添加到选中文件列表
+        this.selectedFiles.push(file)
+        
+        // 上传文件并获取URL
+        try {
+          await this.uploadFile(file)
+        } catch (error) {
+          console.error(`上传文件 ${file.name} 失败:`, error)
+          this.$message.error(`上传文件 ${file.name} 失败`)
+        }
+      }
+      
+      // 清空input值,允许重复选择同一文件
+      event.target.value = ''
+    },
+
+    // 删除文件
+    removeFile(index) {
+      this.selectedFiles.splice(index, 1)
+      // 同时删除对应的上传文件信息
+      if (this.uploadedFiles[index]) {
+        this.uploadedFiles.splice(index, 1)
+      }
+    },
+
+    // 获取文件图标
+    getFileIcon(type) {
+      if (type.startsWith('image/')) {
+        return 'el-icon-picture'
+      } else if (type.includes('pdf')) {
+        return 'el-icon-document'
+      } else if (type.includes('word') || type.includes('document')) {
+        return 'el-icon-document'
+      } else if (type.includes('excel') || type.includes('spreadsheet')) {
+        return 'el-icon-document'
+      } else if (type.includes('video')) {
+        return 'el-icon-video-camera'
+      } else if (type.includes('audio')) {
+        return 'el-icon-headset'
+      } else {
+        return 'el-icon-document'
+      }
+    },
+
+    // 格式化文件大小
+    formatFileSize(bytes) {
+      if (bytes === 0) return '0 B'
+      const k = 1024
+      const sizes = ['B', 'KB', 'MB', 'GB']
+      const i = Math.floor(Math.log(bytes) / Math.log(k))
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+    },
+
+    // 提交给小程序
+    async submitToMiniProgram() {
+      if (this.uploadedFiles.length === 0) {
+        this.$message.warning('请先选择并上传文件')
+        return
+      }
+
+      this.submitting = true
+
+      try {
+        // 检查微信环境
+        if (typeof WeixinJSBridge === 'undefined') {
+          this.$message.error('请在微信中打开此页面')
+          return
+        }
+
+        // 通过微信JS-SDK发送消息给小程序
+        if (window.wx && window.wx.miniProgram) {
+          // 在微信小程序web-view中
+          window.wx.miniProgram.postMessage({
+            data: {
+              type: 'file_upload',
+              files: this.uploadedFiles
+            }
+          })
+          
+          this.$message.success('文件已提交给小程序')
+          this.selectedFiles = []
+          this.uploadedFiles = []
+        } else {
+          // 如果不在小程序web-view中,提示用户
+          this.$message.error('请在微信小程序中打开此页面')
+        }
+
+      } catch (error) {
+        console.error('提交文件失败:', error)
+        this.$message.error('提交失败,请重试')
+      } finally {
+        this.submitting = false
+      }
+    },
+
+    // 准备文件数据
+    async prepareFilesData() {
+      const filesData = []
+      
+      for (const file of this.selectedFiles) {
+        try {
+          const base64 = await this.fileToBase64(file)
+          filesData.push({
+            name: file.name,
+            type: file.type,
+            size: file.size,
+            data: base64
+          })
+        } catch (error) {
+          console.error('转换文件失败:', error)
+        }
+      }
+      
+      return filesData
+    },
+    uploadFile(file) {
+        return new Promise((resolve, reject) => {
+            const formData = new FormData();
+            formData.append('file', file);
+            
+            this.$store.dispatch('pool/uploadFile', formData).then(res => {
+                const fileInfo = {
+                    name: file.name,
+                    type: file.type,
+                    size: file.size,
+                    url: res.data.imgUrl
+                };
+                
+                // 添加到已上传文件列表
+                this.uploadedFiles.push(fileInfo);
+                console.log('文件上传成功:', fileInfo);
+                resolve(fileInfo);
+            }).catch((error) => {
+                console.error('文件上传失败:', error);
+                this.$message({
+                    type: 'error',
+                    message: `上传文件 ${file.name} 失败,请重试!`
+                });
+                reject(error);
+            });
+        });
+    },
+    // 文件转Base64
+    fileToBase64(file) {
+      return new Promise((resolve, reject) => {
+        const reader = new FileReader()
+        reader.onload = () => resolve(reader.result)
+        reader.onerror = reject
+        reader.readAsDataURL(file)
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.file-upload-container {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 20px;
+  background: #fff;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  margin-bottom: 30px;
+}
+
+.header h2 {
+  color: #303133;
+  margin-bottom: 10px;
+}
+
+.header p {
+  color: #909399;
+  font-size: 14px;
+}
+
+.upload-area {
+  border: 2px dashed #d9d9d9;
+  border-radius: 8px;
+  padding: 40px;
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.3s;
+  background: #fafafa;
+}
+
+.upload-area:hover {
+  border-color: #409eff;
+  background: #f0f9ff;
+}
+
+.upload-icon {
+  font-size: 48px;
+  color: #c0c4cc;
+  margin-bottom: 20px;
+}
+
+.upload-text p {
+  margin: 5px 0;
+  color: #606266;
+}
+
+.upload-hint {
+  font-size: 12px;
+  color: #c0c4cc;
+}
+
+.file-list {
+  margin-top: 30px;
+}
+
+.file-list h3 {
+  margin-bottom: 15px;
+  color: #303133;
+}
+
+.file-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 15px;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  margin-bottom: 10px;
+  background: #fff;
+  transition: all 0.3s;
+}
+
+.file-item:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.file-info {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+
+.file-icon {
+  font-size: 24px;
+  color: #409eff;
+  margin-right: 15px;
+}
+
+.file-details {
+  flex: 1;
+}
+
+.file-name {
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 5px;
+  word-break: break-all;
+}
+
+.file-size {
+  font-size: 12px;
+  color: #909399;
+}
+
+.file-status {
+  margin-top: 5px;
+}
+
+.status-success {
+  color: #67c23a;
+  font-size: 12px;
+}
+
+.status-success i {
+  margin-right: 3px;
+}
+
+.status-uploading {
+  color: #e6a23c;
+  font-size: 12px;
+}
+
+.status-uploading i {
+  margin-right: 3px;
+  animation: rotating 2s linear infinite;
+}
+
+@keyframes rotating {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.file-actions {
+  margin-left: 15px;
+}
+
+.submit-area {
+  margin-top: 30px;
+  text-align: center;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .file-upload-container {
+    padding: 15px;
+  }
+  
+  .upload-area {
+    padding: 30px 20px;
+  }
+  
+  .file-item {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  .file-actions {
+    margin-left: 0;
+    margin-top: 10px;
+    align-self: flex-end;
+  }
+}
+</style>

+ 385 - 0
src/views/file/test.vue

@@ -0,0 +1,385 @@
+<template>
+  <div class="test-container">
+    <h2>文件上传测试页面</h2>
+    <p>这个页面用于测试文件上传功能,可以在浏览器中直接访问</p>
+    
+    <div class="test-info">
+      <h3>测试说明:</h3>
+      <ul>
+        <li>此页面可以在任何浏览器中访问</li>
+        <li>文件选择功能会正常工作</li>
+        <li>提交功能会模拟发送数据</li>
+        <li>实际使用时需要在微信小程序中打开</li>
+      </ul>
+    </div>
+
+    <div class="upload-area" @click="triggerFileInput">
+      <div class="upload-icon">
+        <i class="el-icon-upload"></i>
+      </div>
+      <div class="upload-text">
+        <p>点击选择文件</p>
+        <p class="upload-hint">支持图片、文档、视频等格式</p>
+      </div>
+      <input
+        ref="fileInput"
+        type="file"
+        multiple
+        accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.mp4,.mp3"
+        @change="handleFileSelect"
+        style="display: none"
+      />
+    </div>
+
+    <div v-if="selectedFiles.length > 0" class="file-list">
+      <h3>已选择的文件</h3>
+      <div class="file-item" v-for="(file, index) in selectedFiles" :key="index">
+        <div class="file-info">
+          <div class="file-icon">
+            <i :class="getFileIcon(file.type)"></i>
+          </div>
+          <div class="file-details">
+            <div class="file-name">{{ file.name }}</div>
+            <div class="file-size">{{ formatFileSize(file.size) }}</div>
+          </div>
+        </div>
+        <div class="file-actions">
+          <el-button 
+            type="danger" 
+            size="mini" 
+            @click="removeFile(index)"
+            icon="el-icon-delete"
+          >
+            删除
+          </el-button>
+        </div>
+      </div>
+    </div>
+
+    <div class="submit-area">
+      <el-button 
+        type="primary" 
+        size="large" 
+        @click="testSubmit"
+        :disabled="selectedFiles.length === 0"
+        :loading="submitting"
+      >
+        {{ submitting ? '提交中...' : '测试提交' }}
+      </el-button>
+    </div>
+
+    <div v-if="testResult" class="test-result">
+      <h3>测试结果:</h3>
+      <pre>{{ testResult }}</pre>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FileUploadTest',
+  data() {
+    return {
+      selectedFiles: [],
+      submitting: false,
+      testResult: ''
+    }
+  },
+  methods: {
+    triggerFileInput() {
+      this.$refs.fileInput.click()
+    },
+
+    handleFileSelect(event) {
+      const files = Array.from(event.target.files)
+      
+      files.forEach(file => {
+        if (file.size > 50 * 1024 * 1024) {
+          this.$message.error(`文件 ${file.name} 超过50MB限制`)
+          return
+        }
+        
+        const exists = this.selectedFiles.find(f => f.name === file.name)
+        if (exists) {
+          this.$message.warning(`文件 ${file.name} 已存在`)
+          return
+        }
+        
+        this.selectedFiles.push(file)
+      })
+      
+      event.target.value = ''
+    },
+
+    removeFile(index) {
+      this.selectedFiles.splice(index, 1)
+    },
+
+    getFileIcon(type) {
+      if (type.startsWith('image/')) {
+        return 'el-icon-picture'
+      } else if (type.includes('pdf')) {
+        return 'el-icon-document'
+      } else if (type.includes('word') || type.includes('document')) {
+        return 'el-icon-document'
+      } else if (type.includes('excel') || type.includes('spreadsheet')) {
+        return 'el-icon-document'
+      } else if (type.includes('video')) {
+        return 'el-icon-video-camera'
+      } else if (type.includes('audio')) {
+        return 'el-icon-headset'
+      } else {
+        return 'el-icon-document'
+      }
+    },
+
+    formatFileSize(bytes) {
+      if (bytes === 0) return '0 B'
+      const k = 1024
+      const sizes = ['B', 'KB', 'MB', 'GB']
+      const i = Math.floor(Math.log(bytes) / Math.log(k))
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+    },
+
+    async testSubmit() {
+      if (this.selectedFiles.length === 0) {
+        this.$message.warning('请先选择文件')
+        return
+      }
+
+      this.submitting = true
+      this.testResult = ''
+
+      try {
+        const filesData = await this.prepareFilesData()
+        
+        // 模拟提交数据
+        const mockResult = {
+          success: true,
+          message: '测试提交成功',
+          timestamp: new Date().toISOString(),
+          filesCount: filesData.length,
+          files: filesData.map(file => ({
+            name: file.name,
+            type: file.type,
+            size: file.size,
+            dataLength: file.data.length
+          }))
+        }
+
+        this.testResult = JSON.stringify(mockResult, null, 2)
+        this.$message.success('测试提交成功')
+        
+        // 清空文件列表
+        this.selectedFiles = []
+
+      } catch (error) {
+        console.error('测试提交失败:', error)
+        this.$message.error('测试提交失败')
+        this.testResult = JSON.stringify({ error: error.message }, null, 2)
+      } finally {
+        this.submitting = false
+      }
+    },
+
+    async prepareFilesData() {
+      const filesData = []
+      
+      for (const file of this.selectedFiles) {
+        try {
+          const base64 = await this.fileToBase64(file)
+          filesData.push({
+            name: file.name,
+            type: file.type,
+            size: file.size,
+            data: base64
+          })
+        } catch (error) {
+          console.error('转换文件失败:', error)
+        }
+      }
+      
+      return filesData
+    },
+
+    fileToBase64(file) {
+      return new Promise((resolve, reject) => {
+        const reader = new FileReader()
+        reader.onload = () => resolve(reader.result)
+        reader.onerror = reject
+        reader.readAsDataURL(file)
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.test-container {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 20px;
+  background: #fff;
+  min-height: 100vh;
+}
+
+.test-info {
+  background: #f0f9ff;
+  border: 1px solid #b3d8ff;
+  border-radius: 6px;
+  padding: 15px;
+  margin-bottom: 20px;
+}
+
+.test-info h3 {
+  margin-top: 0;
+  color: #409eff;
+}
+
+.test-info ul {
+  margin: 10px 0;
+  padding-left: 20px;
+}
+
+.test-info li {
+  margin: 5px 0;
+  color: #606266;
+}
+
+.upload-area {
+  border: 2px dashed #d9d9d9;
+  border-radius: 8px;
+  padding: 40px;
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.3s;
+  background: #fafafa;
+  margin-bottom: 20px;
+}
+
+.upload-area:hover {
+  border-color: #409eff;
+  background: #f0f9ff;
+}
+
+.upload-icon {
+  font-size: 48px;
+  color: #c0c4cc;
+  margin-bottom: 20px;
+}
+
+.upload-text p {
+  margin: 5px 0;
+  color: #606266;
+}
+
+.upload-hint {
+  font-size: 12px;
+  color: #c0c4cc;
+}
+
+.file-list {
+  margin: 20px 0;
+}
+
+.file-list h3 {
+  margin-bottom: 15px;
+  color: #303133;
+}
+
+.file-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 15px;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  margin-bottom: 10px;
+  background: #fff;
+  transition: all 0.3s;
+}
+
+.file-item:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.file-info {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+
+.file-icon {
+  font-size: 24px;
+  color: #409eff;
+  margin-right: 15px;
+}
+
+.file-details {
+  flex: 1;
+}
+
+.file-name {
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 5px;
+  word-break: break-all;
+}
+
+.file-size {
+  font-size: 12px;
+  color: #909399;
+}
+
+.file-actions {
+  margin-left: 15px;
+}
+
+.submit-area {
+  margin: 20px 0;
+  text-align: center;
+}
+
+.test-result {
+  margin-top: 20px;
+  background: #f5f5f5;
+  border-radius: 6px;
+  padding: 15px;
+}
+
+.test-result h3 {
+  margin-top: 0;
+  color: #303133;
+}
+
+.test-result pre {
+  background: #fff;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  padding: 10px;
+  overflow-x: auto;
+  font-size: 12px;
+  line-height: 1.4;
+}
+
+@media (max-width: 768px) {
+  .test-container {
+    padding: 15px;
+  }
+  
+  .upload-area {
+    padding: 30px 20px;
+  }
+  
+  .file-item {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  .file-actions {
+    margin-left: 0;
+    margin-top: 10px;
+    align-self: flex-end;
+  }
+}
+</style>