Pārlūkot izejas kodu

文件上传进度

CzRger 1 mēnesi atpakaļ
vecāks
revīzija
4c859162af

+ 2 - 2
src/api/index.ts

@@ -1,9 +1,9 @@
 import HttpService from "./request";
 
-export const handle = ({url, params, method,config,filName}: any) => {
+export const handle = ({url, params, method, config, filName, onProcess}: any) => {
   const httpService = new HttpService()
   // @ts-ignore
-  return httpService[method.toLowerCase()](url, params,config,filName)
+  return httpService[method.toLowerCase()]({url, params, config, filName, onProcess})
 }
 
 // @ts-ignore

+ 0 - 11
src/api/modules/cms/chat.ts

@@ -1,11 +0,0 @@
-import { handle } from '../../index'
-
-const suffix = 'cms-api'
-
-// 查询字典项
-export const cmsAiQueryHotThemelist = (params) => handle({
-  url: `/${suffix}/szface/cmsAi/queryHotThemelist`,
-  method: 'post',
-  params
-})
-

+ 12 - 0
src/api/modules/global/upload.ts

@@ -0,0 +1,12 @@
+import { handle } from '../../index'
+
+const suffix = 'bm-api'
+
+// 查询字典项
+export const axUpload = (params, onProcess) => handle({
+  url: `http://8.130.72.63:18099/api/common/upload`,
+  method: 'upload',
+  params,
+  onProcess
+})
+

+ 22 - 4
src/api/request.ts

@@ -5,7 +5,7 @@ export class HttpRequest {
     // 获取axios实例
     this.axios = new Interceptors().getInterceptors();
   }
-  public get(url: string, params: String, config: Object = {}) {
+  public get({url = '', params, config = {}}) {
     return new Promise((resolve, reject) => {
       let paramUrl = url
       if (params) {
@@ -22,7 +22,7 @@ export class HttpRequest {
     })
   }
 
-  public post(url: string, params: Object, config: Object = {}) {
+  public post({url = '', params, config = {}}) {
     return new Promise((resolve, reject) => {
       this.axios.post(url, params, {
         ...config   //  导出添加的下载类型
@@ -34,7 +34,7 @@ export class HttpRequest {
     })
   }
 
-  public delete(url: string, params: String, config: Object = {}) {
+  public delete({url = '', params, config = {}}) {
     return new Promise((resolve, reject) => {
       let paramUrl = url
       if (params) {
@@ -50,7 +50,7 @@ export class HttpRequest {
       })
     })
   }
-  public put(url: string, params: Object, config: Object = {}) {
+  public put({url = '', params, config = {}}) {
     return new Promise((resolve, reject) => {
       this.axios.put(url, params, {
         ...config   //  导出添加的下载类型
@@ -61,6 +61,24 @@ export class HttpRequest {
       })
     })
   }
+  public upload({url, params, onProcess = (p) => {}}) {
+    return new Promise(async (resolve, reject) => {
+      this.axios.post(url, params,  {
+        headers: {
+          'authorization': 'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6Ijg4NGJjMjQ5LTQ2ZTktNDhhYi1hNjAyLWIzMGYxZTUwNDMxMSJ9.KoDqMUYjxGk4obbaKYcGjYCbS891t9PCriBGbsBAlOdGfySdKvDeF3H8WrIO0DWG09Ew7sKStUUnXr4gqdlAaw'
+        },
+        onUploadProgress: (progressEvent) => {
+          const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
+          // console.log(`上传进度: ${percent}%`);
+          onProcess(percent)
+        },
+      }).then((res: any) => {
+        this.resultHandle(res, resolve, reject, url);
+      }).catch((err: { message: any; }) => {
+        reject(err.message);
+      })
+    })
+  }
 
 
   public resultHandle(res: any, resolve: { (value: unknown): void; (value: unknown): void; (arg0: any): void; },reject: { (value: unknown): void; (value: unknown): void; (arg0: any): void; }, url: string) {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
src/assets/svg/success.svg


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 11 - 0
src/assets/svg/upload.svg


+ 20 - 1
src/components/czr-ui/CzrButton.vue

@@ -21,6 +21,12 @@
         <SvgIcon :name="icon || 'czr_del'" color="#FF5454"/>{{title || '删除'}}
       </div>
     </template>
+    <template v-else-if="type == 'primary'">
+      <div class="czr-button __hover primary">{{title}}</div>
+    </template>
+    <template v-else-if="type == 'normal'">
+      <div class="czr-button __hover normal">{{title}}</div>
+    </template>
     <template v-else>
       <div class="czr-button __hover">
         <SvgIcon v-if="icon" :name="icon"/>{{ title }}
@@ -65,7 +71,20 @@ const props = defineProps({
   }
   &._add {
     color: #ffffff;
-    background-color: var(--czr-main-color)
+    background-color: var(--czr-main-color);
+    border-color: var(--czr-main-color);
+  }
+  &.primary {
+    height: 2rem;
+    background: var(--czr-main-color);
+    border-color:  var(--czr-main-color);
+    color: #ffffff;
+  }
+  &.normal {
+    height: 2rem;
+    background: #ffffff;
+    border-color: var(--czr-main-color);
+    color: var(--czr-main-color);
   }
 }
 .czr-button-table {

+ 2 - 18
src/style/czr.scss

@@ -191,7 +191,7 @@
         ._czr-dialog-head {
           width: 100%;
           height: 3.13rem;
-          background-color: var(--czr-dialog-bg);
+          //background-color: var(--czr-dialog-bg);
           display: flex;
           align-items: center;
           padding: 0 1rem;
@@ -337,7 +337,7 @@
       .__czr-confirm-head {
         width: 100%;
         height: 3.13rem;
-        background-color: var(--czr-dialog-bg);
+        //background-color: var(--czr-dialog-bg);
         display: flex;
         align-items: center;
         padding: 0 1rem;
@@ -406,20 +406,4 @@
   justify-content: center;
   box-sizing: border-box;
   line-height: 1rem;
-}
-.__czr-button {
-  height: 2rem;
-  background: var(--czr-main-color);
-  border-radius: 0.25rem;
-  padding: 0 1rem;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-weight: 400;
-  font-size: 0.88rem;
-  color: #FFFFFF;
-  &:hover {
-    opacity: 0.75;
-    cursor: pointer;
-  }
 }

+ 1 - 0
src/style/manage.scss

@@ -7,6 +7,7 @@
   display: flex;
   flex-direction: column;
   padding: 1rem;
+  overflow: hidden;
 }
 .bm-main-box-title {
   font-weight: bold;

+ 8 - 2
src/views/manage/knowledge/documents/document/index.vue

@@ -11,7 +11,12 @@
     >
       <template #tableTitle>
         <div class="flex gap-[var(--czr-gap)]">
-          <CzrButton type="add" title="添加文件"/>
+          <CzrButton type="add" title="添加文件" @click="$router.push({
+            name: '18e6009c-a72c-4359-864b-e7725fccca69',
+            params: {
+              id: ID
+            }
+          })"/>
           <CzrButton title="迁移" icon="move"/>
           <CzrButton title="归档" icon="cloud"/>
           <CzrButton type="del" title="删除" icon="czr_del" @click="onDel()"/>
@@ -73,7 +78,7 @@
 </template>
 
 <script setup lang="ts">
-import {getCurrentInstance, onMounted, reactive, ref, watch} from "vue";
+import {getCurrentInstance, inject, onMounted, reactive, ref, watch} from "vue";
 import { Search } from '@element-plus/icons-vue'
 import {debounce} from "lodash";
 import {useDialogStore, useDictionaryStore} from "@/stores";
@@ -87,6 +92,7 @@ const props = defineProps({
   knowledge: <any>{}
 })
 const {proxy}: any = getCurrentInstance()
+const ID = inject('ID')
 const state: any = reactive({
   text: '',
   query: {

+ 2 - 1
src/views/manage/knowledge/documents/index.vue

@@ -17,7 +17,7 @@
 </template>
 
 <script setup lang="ts">
-import {defineAsyncComponent, getCurrentInstance, onMounted, reactive, ref} from "vue";
+import {defineAsyncComponent, getCurrentInstance, onMounted, provide, reactive, ref} from "vue";
 import {useRoute, useRouter} from "vue-router";
 
 const route = useRoute();
@@ -35,6 +35,7 @@ const state: any = reactive({
   knowledge: {},
   menu: null
 })
+provide('ID', state.ID)
 const initDetail = () => {
   if (state.ID) {
     state.knowledge = {

+ 2 - 2
src/views/manage/knowledge/documents/test/index.vue

@@ -20,7 +20,7 @@
               :transparent="true"
               :clearable="false"
             />
-            <div class="__czr-button">测试</div>
+            <CzrButton class="test" type="primary" title="测试"/>
           </div>
         </div>
         <div class="flex-1 flex flex flex-col">
@@ -207,7 +207,7 @@ onMounted(() => {
         }
       }
     }
-    .__czr-button {
+    .test {
       position: absolute;
       right: 1.5rem;
       bottom: 0.75rem;

+ 209 - 5
src/views/manage/knowledge/upload/index.vue

@@ -1,18 +1,133 @@
-<template>
-  文档上传
+ <template>
+  <div class="w-full h-full flex flex-col">
+    <div class="__hover text-[var(--czr-main-color)] text-[0.88rem] flex items-center ml-[var(--czr-gap)]" @click="$router.push({
+      name: '78430247-a531-4c8f-8a08-c88e93a836e2',
+      params: {
+        id: state.ID
+      }
+    })"><SvgIcon name="czr_arrow" :rotate="180" size="12" :active="true"/>文档列表</div>
+    <div class="bm-main-box mt-[1.25rem]">
+      <div class="flex gap-[1rem]">
+        <CzrButton :type="state.uploadType == UploadTypeEnum.Text ? 'primary' : 'normal'" title="文本文件" @click="state.uploadType = UploadTypeEnum.Text, state.step = 1"/>
+        <CzrButton :type="state.uploadType == UploadTypeEnum.QA ? 'primary' : 'normal'" title="QA问答" @click="state.uploadType = UploadTypeEnum.QA, state.step = 1"/>
+      </div>
+      <div class="mt-[1.75rem] flex justify-center items-center gap-[1rem]">
+        <template v-for="(item, index) in (state.uploadType == UploadTypeEnum.Text ? TextSteps : QASteps)">
+          <div v-if="index > 0" class="step-split" :class="{active: state.step > index }"/>
+          <div class="step-item" :class="{active: state.step > index, current: state.step === index + 1}">
+            <div>{{state.step > index + 1 ? '✔' : index + 1}}</div>
+            {{ item }}
+          </div>
+        </template>
+      </div>
+      <template v-if="state.step == 1">
+        <div class="w-full rounded-[0.25rem] bg-[#F6F8FC] px-[1.5rem] py-[1rem] mt-[1.5rem] flex">
+          <SvgIcon name="czr_tip" color="#fdb648" class="mr-[1rem]"/>
+          <div class="flex flex flex-col gap-[0.5rem] text-[#606266] text-[0.88rem]">
+            <template v-if="state.uploadType == UploadTypeEnum.Text">
+              <div>1、文件上传前,建议规范文件的分段标识</div>
+              <div>2、每次最多上传 {{ UploadConfig.limit }} 个文件,每个文件不超过 {{ UploadConfig.maxSize }}MB</div>
+            </template>
+            <template v-else-if="state.uploadType == UploadTypeEnum.QA">
+              <div class="flex">1、点击下载对应模版并完善信息<CzrButton type="table" title="下载Excel模板"/></div>
+              <div>2、上传的表格文件中每个 sheet 会作为一个文档,sheet名称为文档名称</div>
+              <div>3、每次最多上传 {{ UploadConfig.limit }} 个文件,每个文件不超过 {{ UploadConfig.maxSize }}MB</div>
+            </template>
+          </div>
+        </div>
+        <div class="mt-[1rem] w-full h-[12.5rem] rounded-[0.25rem] bg-[#F6F8FC]">
+          <el-upload
+            class="upload"
+            drag
+            action="#"
+            multiple
+            :accept="AcceptType[state.uploadType].map(v => '.' + v).join(',')"
+            :show-file-list="false"
+            :before-upload="handleBeforeUpload"
+            :http-request="handleHttpRequest"
+            :disabled="state.uploadFiles.size === UploadConfig.limit"
+          >
+            <div class="flex flex-col justify-center items-center text-[#6F7889] text-[0.88rem] gap-[0.5rem]">
+              <SvgIcon name="upload" color="#6F7889" size="40"/>
+              <div>点击或拖拽文件到此区域,或 <span class="text-[var(--czr-main-color)]">选择文件</span></div>
+              <div>支持格式:{{AcceptType[state.uploadType].join('、')}}</div>
+            </div>
+          </el-upload>
+        </div>
+        <div class="mt-[1rem] flex-1 grid grid-cols-4 auto-rows-max gap-[1.5rem] overflow-y-auto">
+          <template v-for="[id, file] in state.uploadFiles">
+            <div class="h-[3.13rem] flex items-center gap-[0.25rem] px-[var(--czr-gap)] rounded-[0.25rem] bg-[#F6F8FC]" @mouseenter="file.hover__ = true" @mouseleave="file.hover__ = false">
+              <img src="@/assets/images/file-word.png" class="w-[1.25rem] h-[1.25rem]"/>
+              <div class="flex-1 flex items-center gap-[0.25rem] relative overflow-hidden">
+                <div v-title class="flex-1 text-[#333333] text-[0.88rem]">{{file.name}}</div>
+                <div class="text-[#A7ADB9] text-[0.75rem] h-full ml-auto">
+                  <template v-if="file.hover__">
+                    <div class="__hover flex items-center gap-[0.25rem]" @click="state.uploadFiles.delete(id)">
+                      <SvgIcon name="czr_del" size="14" class="mb-[2px]"/>删除
+                    </div>
+                  </template>
+                  <template v-else>
+                    <template v-if="file.success">
+                      <SvgIcon name="success" color="#1CC78D"/>
+                    </template>
+                    <template v-else>
+                      {{file.process}}%
+                    </template>
+                  </template>
+                </div>
+                <template v-if="!file.success">
+                  <div class="absolute bottom-[-0.5rem] w-full h-[0.25rem] rounded-[0.25rem]" :style="{backgroundColor: `rgba(var(--czr-main-color-rgb), 0.3)`}">
+                    <div class="absolute h-[0.25rem] rounded-[0.25rem]" :style="{backgroundColor: `rgba(var(--czr-main-color-rgb), 1)`, width: `${file.process}%`}"/>
+                  </div>
+                </template>
+              </div>
+            </div>
+          </template>
+        </div>
+      </template>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import {getCurrentInstance, onMounted, reactive, ref} from "vue";
-import {useRoute} from "vue-router";
+import {getCurrentInstance, inject, onMounted, reactive, ref} from "vue";
+import {useRoute, useRouter} from "vue-router";
+import {ElMessage} from "element-plus";
+import {axUpload} from "@/api/modules/global/upload";
+import {v4} from "uuid";
 
 const route = useRoute();
+const router = useRouter();
 const emits = defineEmits([])
 const props = defineProps({})
 const {proxy}: any = getCurrentInstance()
+enum UploadTypeEnum {
+  Text = 'text',
+  QA = 'qa',
+}
 const state: any = reactive({
-  ID: route.params.id
+  ID: route.params.id,
+  uploadType: UploadTypeEnum.Text,
+  step: 1,
+  uploadFiles: new Map(),
 })
+const UploadConfig = {
+  limit: 2,
+  maxSize: 100,
+}
+const TextSteps = [
+  '文件上传',
+  '分段及清洗',
+  '处理并完成',
+]
+const QASteps = [
+  '文件上传',
+  '处理并完成',
+]
+const AcceptType = {
+  [UploadTypeEnum.Text]: ['TXT', 'MARKDOWN', 'MDX', 'PDF', 'HTML', 'XLSX', 'XLS', 'DOCX', 'DOC', 'CSV', 'MD', 'HTM'].map(v => v.toUpperCase()),
+  [UploadTypeEnum.QA]: ['XLS', 'XLSX'].map(v => v.toUpperCase()),
+}
 const initDetail = () => {
   if (state.ID) {
 
@@ -20,10 +135,99 @@ const initDetail = () => {
     router.push({name: '4342bfff-1ea8-4f4c-b562-3cdc1fde116f'})
   }
 }
+const handleBeforeUpload = (file) => {
+  const suffix = file.name.split('.').pop().toUpperCase()
+  const msg = `文件${file.name}上传失败`
+  if (!AcceptType[state.uploadType].includes(suffix)) {
+    ElMessage.warning(`${msg},格式不正确!`)
+    return false
+  }
+  if ((file.size / 1024 / 1024) > UploadConfig.maxSize) {
+    ElMessage.warning(`${msg},大小不正确!`)
+    return false
+  }
+  if (state.uploadFiles.size >= UploadConfig.limit) {
+    ElMessage.warning(`${msg},数量已达上限!`)
+    return false
+  }
+  state.uploadFiles.set(file.uid, {
+    name: file.name,
+    success: false,
+    process: 0
+  })
+  return true
+}
+const handleHttpRequest = (options) => {
+  const id = options.file.uid
+  const formData = new FormData();
+  formData.append('file', options.file);
+  axUpload(formData, (process) => {
+    if (state.uploadFiles.get(id)) {
+      state.uploadFiles.get(id).process = process
+    }
+  }).then(res => {
+    if (state.uploadFiles.get(id)) {
+      state.uploadFiles.get(id).success = true
+    }
+  })
+}
 onMounted(() => {
   initDetail()
 })
 </script>
 
 <style lang="scss" scoped>
+.step-split {
+  width: 16.5rem;
+  height: 2px;
+  background-color: #EAEBEF;
+  &.active {
+    background-color: var(--czr-main-color);
+  }
+}
+.step-item {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+  font-size: 1rem;
+  color: #A7ADB9;
+  >div:first-child {
+    width: 2rem;
+    height: 2rem;
+    border-radius: 2rem;
+    border: 1px solid #A7ADB9;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  &.active {
+    color: #576275;
+    >div:first-child {
+      border-color: var(--czr-main-color);
+      color:  var(--czr-main-color);
+    }
+  }
+  &.current {
+    font-weight: bold;
+    color: #2E3238;
+    >div:first-child {
+      background-color:  var(--czr-main-color);
+      border-color:  var(--czr-main-color);
+      color: #ffffff;
+    }
+  }
+}
+:deep(.upload) {
+  width: 100%;
+  height: 100%;
+  .el-upload {
+    width: 100%;
+    height: 100%;
+    .el-upload-dragger {
+      width: 100%;
+      height: 100%;
+      background-color: transparent;
+    }
+  }
+}
 </style>

+ 1 - 1
src/views/workflow/instance/component/vars/vars-detail.vue

@@ -61,7 +61,7 @@ const state: any = reactive({
 })
 const optionsType = [
   {type: 'String', label: '文本'},
-  {type: 'Textarea', label: '段落'},
+  // {type: 'Textarea', label: '段落'},
   {type: 'Number', label: '数字'},
   // {type: 'Select', label: '下拉选项'},
 ]