From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/ai/knowledge/document/form/UploadStep.vue |  273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 273 insertions(+), 0 deletions(-)

diff --git a/src/views/ai/knowledge/document/form/UploadStep.vue b/src/views/ai/knowledge/document/form/UploadStep.vue
new file mode 100644
index 0000000..5a4d700
--- /dev/null
+++ b/src/views/ai/knowledge/document/form/UploadStep.vue
@@ -0,0 +1,273 @@
+<template>
+  <el-form ref="formRef" :model="modelData" label-width="0" class="mt-20px">
+    <el-form-item class="mb-20px">
+      <div class="w-full">
+        <div
+          class="w-full border-2 border-dashed border-[#dcdfe6] rounded-md p-20px text-center hover:border-[#409eff]"
+        >
+          <el-upload
+            ref="uploadRef"
+            class="upload-demo"
+            drag
+            :action="uploadUrl"
+            :auto-upload="true"
+            :on-success="handleUploadSuccess"
+            :on-error="handleUploadError"
+            :on-change="handleFileChange"
+            :on-remove="handleFileRemove"
+            :before-upload="beforeUpload"
+            :http-request="httpRequest"
+            :file-list="fileList"
+            :multiple="true"
+            :show-file-list="false"
+            :accept="acceptedFileTypes"
+          >
+            <div class="flex flex-col items-center justify-center py-20px">
+              <Icon icon="ep:upload-filled" class="text-[48px] text-[#c0c4cc] mb-10px" />
+              <div class="el-upload__text text-[16px] text-[#606266]">
+                鎷栨嫿鏂囦欢鑷虫锛屾垨鑰�
+                <em class="text-[#409eff] not-italic cursor-pointer">閫夋嫨鏂囦欢</em>
+              </div>
+              <div class="el-upload__tip mt-10px text-[#909399] text-[12px]">
+                宸叉敮鎸� {{ supportedFileTypes.join('銆�') }}锛屾瘡涓枃浠朵笉瓒呰繃 {{ maxFileSize }} MB銆�
+              </div>
+            </div>
+          </el-upload>
+        </div>
+
+        <div
+          v-if="modelData.list && modelData.list.length > 0"
+          class="mt-15px grid grid-cols-1 gap-2"
+        >
+          <div
+            v-for="(file, index) in modelData.list"
+            :key="index"
+            class="flex justify-between items-center py-4px px-12px border-l-4 border-l-[#409eff] rounded-sm shadow-sm hover:bg-[#ecf5ff] transition-all duration-300"
+          >
+            <div class="flex items-center">
+              <Icon icon="ep:document" class="mr-8px text-[#409eff]" />
+              <span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
+            </div>
+            <el-button type="danger" link @click="removeFile(index)" class="ml-2">
+              <Icon icon="ep:delete" />
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </el-form-item>
+
+    <!-- 娣诲姞涓嬩竴姝ユ寜閽� -->
+    <el-form-item>
+      <div class="flex justify-end w-full">
+        <el-button type="primary" @click="handleNextStep" :disabled="!isAllUploaded">
+          涓嬩竴姝�
+        </el-button>
+      </div>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+import { PropType, ref, computed, inject, getCurrentInstance, onMounted } from 'vue'
+import { useMessage } from '@/hooks/web/useMessage'
+import { useUpload } from '@/components/UploadFile/src/useUpload'
+import { generateAcceptedFileTypes } from '@/utils'
+import { Icon } from '@/components/Icon'
+
+const props = defineProps({
+  modelValue: {
+    type: Object as PropType<any>,
+    required: true
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const formRef = ref() // 琛ㄥ崟寮曠敤
+const uploadRef = ref() // 涓婁紶缁勪欢寮曠敤
+const parent = inject('parent', null) // 鑾峰彇鐖剁粍浠跺疄渚�
+const { uploadUrl, httpRequest } = useUpload() // 浣跨敤涓婁紶缁勪欢鐨勯挬瀛�
+const message = useMessage() // 娑堟伅寮圭獥
+const fileList = ref([]) // 鏂囦欢鍒楄〃
+const uploadingCount = ref(0) // 涓婁紶涓殑鏂囦欢鏁伴噺
+
+// 鏀寔鐨勬枃浠剁被鍨嬪拰澶у皬闄愬埗
+const supportedFileTypes = [
+  'TXT',
+  'MARKDOWN',
+  'MDX',
+  'PDF',
+  'HTML',
+  'XLSX',
+  'XLS',
+  'DOC',
+  'DOCX',
+  'CSV',
+  'EML',
+  'MSG',
+  'PPTX',
+  'XML',
+  'EPUB',
+  'PPT',
+  'MD',
+  'HTM'
+]
+const allowedExtensions = supportedFileTypes.map((ext) => ext.toLowerCase()) // 灏忓啓鐨勬墿灞曞悕鍒楄〃
+const maxFileSize = 15 // 鏈�澶ф枃浠跺ぇ灏�(MB)
+
+// 鏋勫缓 accept 灞炴�у�硷紝鐢ㄤ簬闄愬埗鏂囦欢閫夋嫨瀵硅瘽妗嗕腑鍙鐨勬枃浠剁被鍨�
+const acceptedFileTypes = computed(() => generateAcceptedFileTypes(supportedFileTypes))
+
+/** 琛ㄥ崟鏁版嵁 */
+const modelData = computed({
+  get: () => {
+    return props.modelValue
+  },
+  set: (val) => emit('update:modelValue', val)
+})
+
+/** 纭繚 list 灞炴�у瓨鍦� */
+const ensureListExists = () => {
+  if (!props.modelValue.list) {
+    emit('update:modelValue', {
+      ...props.modelValue,
+      list: []
+    })
+  }
+}
+
+/** 鏄惁鎵�鏈夋枃浠堕兘宸蹭笂浼犲畬鎴� */
+const isAllUploaded = computed(() => {
+  return modelData.value.list && modelData.value.list.length > 0 && uploadingCount.value === 0
+})
+
+/**
+ * 涓婁紶鍓嶆鏌ユ枃浠剁被鍨嬪拰澶у皬
+ *
+ * @param file 寰呬笂浼犵殑鏂囦欢
+ * @returns 鏄惁鍏佽涓婁紶
+ */
+const beforeUpload = (file) => {
+  // 1.1 妫�鏌ユ枃浠舵墿灞曞悕
+  const fileName = file.name.toLowerCase()
+  const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1)
+  if (!allowedExtensions.includes(fileExtension)) {
+    message.error('涓嶆敮鎸佺殑鏂囦欢绫诲瀷锛�')
+    return false
+  }
+  // 1.2 妫�鏌ユ枃浠跺ぇ灏�
+  if (!(file.size / 1024 / 1024 < maxFileSize)) {
+    message.error(`鏂囦欢澶у皬涓嶈兘瓒呰繃 ${maxFileSize} MB锛乣)
+    return false
+  }
+
+  // 2. 澧炲姞涓婁紶涓殑鏂囦欢璁℃暟
+  uploadingCount.value++
+  return true
+}
+
+/**
+ * 鏂囦欢涓婁紶鎴愬姛澶勭悊
+ *
+ * @param response 涓婁紶鍝嶅簲
+ * @param file 涓婁紶鐨勬枃浠�
+ */
+const handleUploadSuccess = (response, file) => {
+  // 娣诲姞鍒版枃浠跺垪琛�
+  if (response && response.data) {
+    ensureListExists()
+    emit('update:modelValue', {
+      ...props.modelValue,
+      list: [
+        ...props.modelValue.list,
+        {
+          name: file.name,
+          url: response.data
+        }
+      ]
+    })
+  } else {
+    message.error(`鏂囦欢 ${file.name} 涓婁紶澶辫触`)
+  }
+
+  // 鍑忓皯涓婁紶涓殑鏂囦欢璁℃暟
+  uploadingCount.value = Math.max(0, uploadingCount.value - 1)
+}
+
+/**
+ * 鏂囦欢涓婁紶澶辫触澶勭悊
+ *
+ * @param error 閿欒淇℃伅
+ * @param file 涓婁紶鐨勬枃浠�
+ */
+const handleUploadError = (error, file) => {
+  message.error(`鏂囦欢 ${file.name} 涓婁紶澶辫触: ${error}`)
+  // 鍑忓皯涓婁紶涓殑鏂囦欢璁℃暟
+  uploadingCount.value = Math.max(0, uploadingCount.value - 1)
+}
+
+/**
+ * 鏂囦欢鍙樻洿澶勭悊
+ *
+ * @param file 鍙樻洿鐨勬枃浠�
+ */
+const handleFileChange = (file) => {
+  if (file.status === 'success' || file.status === 'fail') {
+    uploadingCount.value = Math.max(0, uploadingCount.value - 1)
+  }
+}
+
+/**
+ * 鏂囦欢绉婚櫎澶勭悊
+ *
+ * @param file 琚Щ闄ょ殑鏂囦欢
+ */
+const handleFileRemove = (file) => {
+  if (file.status === 'uploading') {
+    uploadingCount.value = Math.max(0, uploadingCount.value - 1)
+  }
+}
+
+/**
+ * 浠庡垪琛ㄤ腑绉婚櫎鏂囦欢
+ *
+ * @param index 瑕佺Щ闄ょ殑鏂囦欢绱㈠紩
+ */
+const removeFile = (index: number) => {
+  // 浠庡垪琛ㄤ腑绉婚櫎鏂囦欢
+  const newList = [...props.modelValue.list]
+  newList.splice(index, 1)
+  // 鏇存柊琛ㄥ崟鏁版嵁
+  emit('update:modelValue', {
+    ...props.modelValue,
+    list: newList
+  })
+}
+
+/** 涓嬩竴姝ユ寜閽鐞� */
+const handleNextStep = () => {
+  // 1.1 妫�鏌ユ槸鍚︽湁鏂囦欢涓婁紶
+  if (!modelData.value.list || modelData.value.list.length === 0) {
+    message.warning('璇蜂笂浼犺嚦灏戜竴涓枃浠�')
+    return
+  }
+  // 1.2 妫�鏌ユ槸鍚︽湁鏂囦欢姝e湪涓婁紶
+  if (uploadingCount.value > 0) {
+    message.warning('璇风瓑寰呮墍鏈夋枃浠朵笂浼犲畬鎴�')
+    return
+  }
+
+  // 2. 鑾峰彇鐖剁粍浠剁殑goToNextStep鏂规硶
+  const parentEl = parent || getCurrentInstance()?.parent
+  if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
+    parentEl.exposed.goToNextStep()
+  }
+}
+
+/** 鍒濆鍖� */
+onMounted(() => {
+  ensureListExists()
+})
+</script>
+
+<style lang="scss" scoped></style>

--
Gitblit v1.8.0