Browse Source

新增图片上传组件

XueNing 6 months ago
parent
commit
55b820f6df

+ 1 - 0
src/components.d.ts

@@ -13,6 +13,7 @@ declare module 'vue' {
     ElEditor: typeof import('./components/form/ElEditor.vue')['default']
     ElEmployees: typeof import('./components/form/ElEmployees.vue')['default']
     Exception: typeof import('./components/Exception.vue')['default']
+    FsImageUpload: typeof import('./components/FsImageUpload/index.vue')['default']
     FsTableSelect: typeof import('./components/FsTableSelect/index.vue')['default']
     GlobalAside: typeof import('./components/core/GlobalAside.vue')['default']
     GlobalFooter: typeof import('./components/core/GlobalFooter.vue')['default']

+ 125 - 0
src/components/FsImageUpload/index.vue

@@ -0,0 +1,125 @@
+<script lang="ts">
+import { imageUploadProps, imageUploadEmits } from './props'
+import type { UploadItem } from './types'
+
+export default defineComponent({
+  props: imageUploadProps,
+  emits: imageUploadEmits,
+  setup(props, { emit }) {
+    // 是否可上传
+    const isUpload = computed<boolean>(() => {
+      return (
+        !props.readonly &&
+        !(
+          typeof props.limit === 'number' &&
+          props.limit > 0 &&
+          props.modelValue != null &&
+          props.modelValue.length >= props.limit
+        )
+      )
+    })
+
+    // 预览图片列表
+    const previewList = computed(() => {
+      if (!props.preview) {
+        return []
+      }
+      return props.modelValue?.map(x => x.url) || []
+    })
+
+    /* 选择文件 */
+    const onUpload = (file: File) => {
+      if (!isUpload.value || props.disabled) {
+        return false
+      }
+      const item: UploadItem = {
+        key: Date.now(),
+        name: file.name,
+        status: void 0,
+        progress: 0,
+        file
+      }
+      if (file.type.startsWith('image')) {
+        item.url = window.URL.createObjectURL(file)
+      }
+      // 是否自动上传
+      if (props.autoUpload) {
+        uploadItem(item)
+      }
+      updateModelValue(props.modelValue ? props.modelValue.concat([item]) : [item])
+      return false
+    }
+
+    /* 上传文件 */
+    const uploadItem = (item: UploadItem) => {
+      emit('upload', item)
+    }
+
+    /* 修改modelValue */
+    const updateModelValue = (items: UploadItem[]) => {
+      emit('update:modelValue', items)
+    }
+
+    return {
+      isUpload,
+      previewList,
+      onUpload
+    }
+  }
+})
+</script>
+
+<template>
+  <div class="upload-container">
+    <div class="upload-image" v-for="(item, index) in modelValue" :key="item.key">
+      <el-image :src="item.url" :preview-src-list="previewList" :initial-index="index" fit="cover"></el-image>
+    </div>
+    <div>
+      <el-upload
+        action=""
+        :accept="accept"
+        :multiple="multiple"
+        :disabled="disabled"
+        :show-file-list="false"
+        :beforeUpload="onUpload"
+        v-if="isUpload"
+      >
+        <div class="upload-plus" :style="buttonStyle">
+          <el-icon size="30">
+            <Plus />
+          </el-icon>
+        </div>
+      </el-upload>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.upload-container {
+  display: flex;
+  flex-wrap: wrap;
+  .upload-image {
+    width: 100px;
+    height: 100px;
+    border: 1px dashed #dcdfe6;
+    border-radius: var(--el-border-radius-base);
+    overflow: hidden;
+    margin: 0px 8px 8px 0;
+    cursor: pointer;
+  }
+  .upload-plus {
+    width: 100px;
+    height: 100px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: var(--el-color-info);
+    border: 1px dashed #dcdfe6;
+    border-radius: var(--el-border-radius-base);
+    background-color: #fff;
+    &:hover {
+      border-color: var(--el-color-primary);
+    }
+  }
+}
+</style>

+ 54 - 0
src/components/FsImageUpload/props.ts

@@ -0,0 +1,54 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import type { UploadItem } from './types'
+export const imageUploadProps = {
+  // 已上传列表
+  modelValue: {
+    type: Array as PropType<UploadItem[]>,
+    required: true
+  },
+  // 是否自动上传
+  autoUpload: {
+    type: Boolean,
+    default: true
+  },
+  // 是否只读
+  readonly: Boolean,
+  // 是否禁用
+  disabled: Boolean,
+  // 是否点击预览
+  preview: {
+    type: Boolean,
+    default: true
+  },
+  // 可上传数量
+  limit: {
+    type: Number,
+    default: 5
+  },
+  // 是否支持多选
+  multiple: {
+    type: Boolean,
+    default: false
+  },
+  // 上传类型
+  accept: {
+    type: String,
+    default: 'image/png,image/jpeg'
+  },
+  // item 样式
+  itemStyle: Object,
+  // 上传按钮样式
+  buttonStyle: Object
+}
+
+export type ImageUploadProps = ExtractPropTypes<typeof imageUploadProps>
+
+/* 事件 */
+export const imageUploadEmits = {
+  // 上传事件
+  upload: (_value: UploadItem) => true,
+  // 单个删除事件
+  remove: (_value: UploadItem) => true,
+  // 修改modelValue
+  'update:modelValue': (_value: UploadItem[]) => true
+}

+ 20 - 0
src/components/FsImageUpload/types/index.ts

@@ -0,0 +1,20 @@
+export interface UploadItem extends Record<keyof any, any> {
+  // 唯一标识
+  key: string | number | symbol
+  // 显示的图片地址
+  url?: string
+  // 文件名称
+  name?: string
+  // 上传状态
+  status?: UploadStatus
+  // 上传进度
+  progress?: number
+  /**  */
+  // 上传文件
+  file?: File
+}
+
+/**
+ * 上传状态
+ */
+export type UploadStatus = 'uploading' | 'success' | 'danger' | null

+ 19 - 10
src/components/FsTableSelect/index.vue

@@ -256,7 +256,7 @@ export default defineComponent({
     teleported
   >
     <template #reference>
-      <div class="table-select-container" :class="{ 'table-multiple': multiple }">
+      <div class="table-select-container" :class="{ 'is-multiple': multiple, 'is-visible': visible }">
         <el-input
           :size="size"
           :disabled="disabled"
@@ -347,6 +347,7 @@ export default defineComponent({
 .table-select-container {
   width: 100%;
   position: relative;
+
   .select-clear {
     position: absolute;
     right: 10px;
@@ -369,21 +370,29 @@ export default defineComponent({
   .select-down-rotate {
     transform: rotate(180deg);
   }
+
+  &.is-multiple {
+    :deep(.el-input) {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      top: 0;
+      left: 0;
+    }
+  }
+  &.is-visible {
+    :deep(.el-input:not(.is-disabled) .el-input__wrapper) {
+      box-shadow: 0 0 0 1px var(--el-color-primary) inset;
+    }
+  }
+
   &:hover {
     .select-clear {
       opacity: 1;
     }
   }
 }
-.table-multiple {
-  :deep(.el-input) {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    top: 0;
-    left: 0;
-  }
-}
+
 .table-multiple-tag {
   position: relative;
   top: 0px;

+ 10 - 1
src/router/asyncRouter.ts

@@ -16,7 +16,16 @@ const asyncRouter: RouteRecordRaw[] = [
     component: () => import('@/views/tableSelect/index.vue'),
     meta: {
       title: '下拉表格',
-      icon: 'Table'
+      icon: 'MoreFilled'
+    }
+  },
+  {
+    path: '/imageUpload',
+    name: 'imageUpload',
+    component: () => import('@/views/imageUpload/index.vue'),
+    meta: {
+      title: '图片上传',
+      icon: 'MoreFilled'
     }
   },
 

+ 12 - 0
src/views/imageUpload/index.vue

@@ -0,0 +1,12 @@
+<script setup lang="ts">
+import ImageUpload from '@/components/FsImageUpload/index.vue'
+const images = ref([])
+</script>
+
+<template>
+  <el-card header="图片上传" shadow="never">
+    <ImageUpload v-model="images" multiple :limit="5" />
+  </el-card>
+</template>
+
+<style scoped></style>