Browse Source

附件管理,图片管理

wangwei 4 years ago
parent
commit
4bb482514d

File diff suppressed because it is too large
+ 0 - 0
src/assets/svg/preview/p-rotate.svg


+ 1 - 0
src/assets/svg/preview/resume.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="1595307154239" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7317" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M316 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8zM512 622c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39zM512 482c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39z" p-id="7318" fill="#ffffff"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="7319" fill="#ffffff"></path><path d="M648 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8z" p-id="7320" fill="#ffffff"></path></svg>

+ 1 - 0
src/assets/svg/preview/scale.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="1595307195033" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8116" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M887.081 904.791a25.8 25.8 0 0 1-18.376-7.619L705.618 734.075l-4.163 3.369c-58.255 47.18-131.522 73.16-206.32 73.16-181.07 0-328.377-147.308-328.377-328.367 0-181.068 147.308-328.376 328.377-328.376 181.063 0 328.376 147.308 328.376 328.376 0 77.072-27.412 152.07-77.169 211.17l-3.522 4.173 162.719 162.744a25.846 25.846 0 0 1 7.639 18.432 26.081 26.081 0 0 1-26.051 26.045l-0.046-0.01zM495.13 205.957c-152.336 0-276.27 123.935-276.27 276.27 0 152.33 123.934 276.27 276.27 276.27 152.34 0 276.275-123.94 276.275-276.27 0-152.335-123.935-276.27-276.275-276.27z" fill="#ffffff" p-id="8117"></path><path d="M626.545 508.355h-262.83a26.127 26.127 0 0 1 0-52.255h262.83a26.127 26.127 0 0 1 0 52.255z" fill="#ffffff" p-id="8118"></path><path d="M495.13 639.77a26.127 26.127 0 0 1-26.128-26.128v-262.83a26.127 26.127 0 0 1 52.255 0v262.835a26.127 26.127 0 0 1-26.127 26.123z" fill="#ffffff" p-id="8119"></path></svg>

File diff suppressed because it is too large
+ 0 - 0
src/assets/svg/preview/unrotate.svg


+ 1 - 0
src/assets/svg/preview/unscale.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="1595308005241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H271v-50h400v50z" fill="#ffffff" p-id="9879"></path></svg>

+ 1 - 0
src/components/Preview/index.ts

@@ -1 +1,2 @@
 export { default as ImagePreview } from './src/Preview.vue';
+export { createImgPreview } from './src/functional';

+ 436 - 0
src/components/Preview/src/Functional.vue

@@ -0,0 +1,436 @@
+<script lang="tsx">
+  import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
+  import { Props } from './typing';
+  import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
+  import resumeSvg from '/@/assets/svg/preview/resume.svg';
+  import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
+  import scaleSvg from '/@/assets/svg/preview/scale.svg';
+  import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
+  import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
+
+  enum StatueEnum {
+    LOADING,
+    DONE,
+    FAIL,
+  }
+  interface ImgState {
+    currentUrl: string;
+    imgScale: number;
+    imgRotate: number;
+    imgTop: number;
+    imgLeft: number;
+    currentIndex: number;
+    status: StatueEnum;
+    moveX: number;
+    moveY: number;
+    show: boolean;
+  }
+  const props = {
+    show: {
+      type: Boolean as PropType<boolean>,
+      default: false,
+    },
+    imageList: {
+      type: [Array] as PropType<string[]>,
+      default: null,
+    },
+    index: {
+      type: Number as PropType<number>,
+      default: 0,
+    },
+  };
+
+  const prefixCls = 'img-preview';
+  export default defineComponent({
+    name: 'ImagePreview',
+    props,
+    setup(props: Props) {
+      const imgState = reactive<ImgState>({
+        currentUrl: '',
+        imgScale: 1,
+        imgRotate: 0,
+        imgTop: 0,
+        imgLeft: 0,
+        status: StatueEnum.LOADING,
+        currentIndex: 0,
+        moveX: 0,
+        moveY: 0,
+        show: props.show,
+      });
+
+      const wrapElRef = ref<HTMLDivElement | null>(null);
+      const imgElRef = ref<HTMLImageElement | null>(null);
+
+      // 初始化
+      function init() {
+        initMouseWheel();
+        const { index, imageList } = props;
+
+        if (!imageList || !imageList.length) {
+          throw new Error('imageList is undefined');
+        }
+        imgState.currentIndex = index;
+        handleIChangeImage(imageList[index]);
+      }
+
+      // 重置
+      function initState() {
+        imgState.imgScale = 1;
+        imgState.imgRotate = 0;
+        imgState.imgTop = 0;
+        imgState.imgLeft = 0;
+      }
+
+      // 初始化鼠标滚轮事件
+      function initMouseWheel() {
+        const wrapEl = unref(wrapElRef);
+        if (!wrapEl) {
+          return;
+        }
+        (wrapEl as any).onmousewheel = scrollFunc;
+        // 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
+        document.body.addEventListener('DOMMouseScroll', scrollFunc);
+        // 禁止火狐浏览器下拖拽图片的默认事件
+        document.ondragstart = function () {
+          return false;
+        };
+      }
+
+      // 监听鼠标滚轮
+      function scrollFunc(e: any) {
+        e = e || window.event;
+        e.delta = e.wheelDelta || -e.detail;
+
+        e.preventDefault();
+        if (e.delta > 0) {
+          // 滑轮向上滚动
+          scaleFunc(0.015);
+        }
+        if (e.delta < 0) {
+          // 滑轮向下滚动
+          scaleFunc(-0.015);
+        }
+      }
+      // 缩放函数
+      function scaleFunc(num: number) {
+        if (imgState.imgScale <= 0.2 && num < 0) return;
+        imgState.imgScale += num;
+      }
+
+      // 旋转图片
+      function rotateFunc(deg: number) {
+        imgState.imgRotate += deg;
+      }
+
+      // 鼠标事件
+      function handleMouseUp() {
+        const imgEl = unref(imgElRef);
+        if (!imgEl) return;
+        imgEl.onmousemove = null;
+      }
+
+      // 更换图片
+      function handleIChangeImage(url: string) {
+        imgState.status = StatueEnum.LOADING;
+        const img = new Image();
+        img.src = url;
+        img.onload = () => {
+          imgState.currentUrl = url;
+          imgState.status = StatueEnum.DONE;
+        };
+        img.onerror = () => {
+          imgState.status = StatueEnum.FAIL;
+        };
+      }
+
+      // 关闭
+      function handleClose(e: MouseEvent) {
+        e && e.stopPropagation();
+        imgState.show = false;
+        // 移除火狐浏览器下的鼠标滚动事件
+        document.body.removeEventListener('DOMMouseScroll', scrollFunc);
+        // 恢复火狐及Safari浏览器下的图片拖拽
+        document.ondragstart = null;
+      }
+
+      // 图片复原
+      function resume() {
+        initState();
+      }
+
+      // 上一页下一页
+      function handleChange(direction: 'left' | 'right') {
+        const { currentIndex } = imgState;
+        const { imageList } = props;
+        if (direction === 'left') {
+          imgState.currentIndex--;
+          if (currentIndex <= 0) {
+            imgState.currentIndex = imageList.length - 1;
+          }
+        }
+        if (direction === 'right') {
+          imgState.currentIndex++;
+          if (currentIndex >= imageList.length - 1) {
+            imgState.currentIndex = 0;
+          }
+        }
+        handleIChangeImage(imageList[imgState.currentIndex]);
+      }
+
+      function handleAddMoveListener(e: MouseEvent) {
+        e = e || window.event;
+        imgState.moveX = e.clientX;
+        imgState.moveY = e.clientY;
+        const imgEl = unref(imgElRef);
+        if (imgEl) {
+          imgEl.onmousemove = moveFunc;
+        }
+      }
+
+      function moveFunc(e: MouseEvent) {
+        e = e || window.event;
+        e.preventDefault();
+        const movementX = e.clientX - imgState.moveX;
+        const movementY = e.clientY - imgState.moveY;
+        imgState.imgLeft += movementX;
+        imgState.imgTop += movementY;
+        imgState.moveX = e.clientX;
+        imgState.moveY = e.clientY;
+      }
+
+      // 获取图片样式
+      const getImageStyle = computed(() => {
+        const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
+        return {
+          transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
+          marginTop: `${imgTop}px`,
+          marginLeft: `${imgLeft}px`,
+        };
+      });
+
+      const getIsMultipleImage = computed(() => {
+        const { imageList } = props;
+        return imageList.length > 1;
+      });
+
+      watchEffect(() => {
+        if (props.show) {
+          init();
+        }
+        if (props.imageList) {
+          initState();
+        }
+      });
+
+      const renderClose = () => {
+        return (
+          <div class={`${prefixCls}__close`} onClick={handleClose}>
+            <CloseOutlined class={`${prefixCls}__close-icon`} />
+          </div>
+        );
+      };
+
+      const renderIndex = () => {
+        if (!unref(getIsMultipleImage)) {
+          return null;
+        }
+        const { currentIndex } = imgState;
+        const { imageList } = props;
+        return (
+          <div class={`${prefixCls}__index`}>
+            {currentIndex + 1} / {imageList.length}
+          </div>
+        );
+      };
+
+      const renderController = () => {
+        return (
+          <div class={`${prefixCls}__controller`}>
+            <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
+              <img src={unScaleSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
+              <img src={scaleSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={resume}>
+              <img src={resumeSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
+              <img src={unRotateSvg} />
+            </div>
+            <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
+              <img src={rotateSvg} />
+            </div>
+          </div>
+        );
+      };
+
+      const renderArrow = (direction: 'left' | 'right') => {
+        if (!unref(getIsMultipleImage)) {
+          return null;
+        }
+        return (
+          <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
+            {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
+          </div>
+        );
+      };
+
+      return () => {
+        return (
+          imgState.show && (
+            <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
+              <div class={`${prefixCls}-content`}>
+                {/*<Spin*/}
+                {/*  indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
+                {/*  spinning={true}*/}
+                {/*  class={[*/}
+                {/*    `${prefixCls}-image`,*/}
+                {/*    {*/}
+                {/*      hidden: imgState.status !== StatueEnum.LOADING,*/}
+                {/*    },*/}
+                {/*  ]}*/}
+                {/*/>*/}
+                <img
+                  style={unref(getImageStyle)}
+                  class={[
+                    `${prefixCls}-image`,
+                    imgState.status === StatueEnum.DONE ? '' : 'hidden',
+                  ]}
+                  ref={imgElRef}
+                  src={imgState.currentUrl}
+                  onMousedown={handleAddMoveListener}
+                />
+                {renderClose()}
+                {renderIndex()}
+                {renderController()}
+                {renderArrow('left')}
+                {renderArrow('right')}
+              </div>
+            </div>
+          )
+        );
+      };
+    },
+  });
+</script>
+<style lang="less">
+  .img-preview {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: @preview-comp-z-index;
+    background: rgba(0, 0, 0, 0.5);
+    user-select: none;
+
+    &-content {
+      display: flex;
+      width: 100%;
+      height: 100%;
+      color: @white;
+      justify-content: center;
+      align-items: center;
+    }
+
+    &-image {
+      cursor: pointer;
+      transition: transform 0.3s;
+    }
+
+    &__close {
+      position: absolute;
+      top: -40px;
+      right: -40px;
+      width: 80px;
+      height: 80px;
+      overflow: hidden;
+      color: @white;
+      cursor: pointer;
+      background-color: rgba(0, 0, 0, 0.5);
+      border-radius: 50%;
+      transition: all 0.2s;
+
+      &-icon {
+        position: absolute;
+        top: 46px;
+        left: 16px;
+        font-size: 16px;
+      }
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.8);
+      }
+    }
+
+    &__index {
+      position: absolute;
+      bottom: 5%;
+      left: 50%;
+      padding: 0 22px;
+      font-size: 16px;
+      background: rgba(109, 109, 109, 0.6);
+      border-radius: 15px;
+      transform: translateX(-50%);
+    }
+
+    &__controller {
+      position: absolute;
+      bottom: 10%;
+      left: 50%;
+      display: flex;
+      width: 260px;
+      height: 44px;
+      padding: 0 22px;
+      margin-left: -139px;
+      background: rgba(109, 109, 109, 0.6);
+      border-radius: 22px;
+      justify-content: center;
+
+      &-item {
+        display: flex;
+        height: 100%;
+        padding: 0 9px;
+        font-size: 24px;
+        cursor: pointer;
+        transition: all 0.2s;
+
+        &:hover {
+          transform: scale(1.2);
+        }
+
+        img {
+          width: 1em;
+        }
+      }
+    }
+
+    &__arrow {
+      position: absolute;
+      top: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 50px;
+      height: 50px;
+      font-size: 28px;
+      cursor: pointer;
+      background-color: rgba(0, 0, 0, 0.5);
+      border-radius: 50%;
+      transition: all 0.2s;
+
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.8);
+      }
+
+      &.left {
+        left: 50px;
+      }
+
+      &.right {
+        right: 50px;
+      }
+    }
+  }
+</style>

+ 20 - 0
src/components/Preview/src/functional.ts

@@ -0,0 +1,20 @@
+import type { Options, Props } from './typing';
+import ImgPreview from './Functional.vue';
+import { isClient } from '/@/utils/is';
+import { createVNode, render } from 'vue';
+
+let instance: ReturnType<typeof createVNode> | null = null;
+export function createImgPreview(options: Options) {
+  if (!isClient) return;
+  const { imageList, show = true, index = 0 } = options;
+
+  const propsData: Partial<Props> = {};
+  const container = document.createElement('div');
+  propsData.imageList = imageList;
+  propsData.show = show;
+  propsData.index = index;
+
+  instance = createVNode(ImgPreview, propsData);
+  render(instance, container);
+  document.body.appendChild(container);
+}

+ 30 - 0
src/components/Preview/src/typing.ts

@@ -0,0 +1,30 @@
+export interface Options {
+  show?: boolean;
+  imageList: string[];
+  index?: number;
+}
+
+export interface Props {
+  show: boolean;
+  instance: Props;
+  imageList: string[];
+  index: number;
+}
+
+export interface ImageProps {
+  alt?: string;
+  fallback?: string;
+  src: string;
+  width: string | number;
+  height?: string | number;
+  placeholder?: string | boolean;
+  preview?:
+    | boolean
+    | {
+        visible?: boolean;
+        onVisibleChange?: (visible: boolean, prevVisible: boolean) => void;
+        getContainer: string | HTMLElement | (() => HTMLElement);
+      };
+}
+
+export type ImageItem = string | ImageProps;

+ 14 - 1
src/components/Table/src/BasicTable.vue

@@ -14,6 +14,18 @@
       </template>
     </BasicForm>
 
+    <!-- <draggable
+      v-model="getBindValues.dataSource"
+      group="title"
+      @start="drag = true"
+      @end="drag = false"
+      item-key="id"
+    >
+      <template #item="{ element }">
+        <div>{{ element }}</div>
+      </template>
+    </draggable> -->
+
     <Table
       ref="tableElRef"
       v-bind="getBindValues"
@@ -61,7 +73,7 @@
   import { useTableForm } from './hooks/useTableForm';
   import { useExpose } from '/@/hooks/core/useExpose';
   import { useDesign } from '/@/hooks/web/useDesign';
-
+  import draggable from 'vuedraggable/src/vuedraggable';
   import { omit } from 'lodash-es';
   import { basicProps } from './props';
   import { isFunction } from '/@/utils/is';
@@ -71,6 +83,7 @@
       Table,
       BasicForm,
       HeaderCell,
+      draggable,
     },
     props: basicProps,
     emits: [

+ 2 - 2
src/components/Upload/src/BasicUpload.vue

@@ -11,12 +11,12 @@
             {{ fileListRef.length }}
           </template>
         </template>
-        <a-button @click="openPreviewModal">
+        <!-- <a-button @click="openPreviewModal">
           <Icon icon="bi:eye" />
           <template v-if="fileListRef.length && showPreviewNumber">
             {{ fileListRef.length }}
           </template>
-        </a-button>
+        </a-button> -->
       </Tooltip>
     </a-button-group>
 

+ 31 - 0
src/components/customComponents/ThumbUrl.vue

@@ -0,0 +1,31 @@
+<template>
+  <span class="thumb">
+    <Image v-if="fileUrl" :src="fileUrl" :width="104" />
+  </span>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { propTypes } from '/@/utils/propTypes';
+  import { Image } from 'ant-design-vue';
+
+  export default defineComponent({
+    components: { Image },
+    props: {
+      fileUrl: propTypes.string.def(''),
+      fileName: propTypes.string.def(''),
+    },
+  });
+</script>
+<style lang="less">
+  .thumb {
+    img {
+      position: static;
+      display: inline-block !important;
+      width: 40px;
+      height: 40px;
+      cursor: zoom-in;
+      border-radius: 4px;
+      object-fit: cover;
+    }
+  }
+</style>

+ 130 - 0
src/components/customComponents/fileData.tsx

@@ -0,0 +1,130 @@
+import type { BasicColumn, ActionItem } from '/@/components/Table';
+
+import { PreviewFileItem } from '/@/components/Upload/src/types';
+import {
+  // checkImgType,
+  isImgTypeByName,
+} from '/@/components/Upload/src/helper';
+
+import TableAction from '/@/components/Table/src/components/TableAction.vue';
+import ThumbUrl from './ThumbUrl.vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+const { t } = useI18n();
+
+// 文件上传列表
+export function createTableColumns(): BasicColumn[] {
+  return [
+    // {
+    //   dataIndex: 'thumbUrl',
+    //   title: t('component.upload.legend'),
+    //   width: 70,
+    //   customRender: ({ record }) => {
+    //     const { thumbUrl } = (record as FileItem) || {};
+    //     return thumbUrl && <ThumbUrl fileUrl={thumbUrl} />;
+    //   },
+    // },
+    {
+      title: t('component.upload.fileName'),
+      dataIndex: 'name',
+      editRow: true,
+      width: 180,
+    },
+    {
+      dataIndex: 'size',
+      title: t('component.upload.fileSize'),
+      width: 100,
+      customRender: ({ text = 0 }) => {
+        return text && (text / 1024).toFixed(2) + 'KB';
+      },
+    },
+    {
+      dataIndex: 'type',
+      title: '文件类型',
+      width: 100,
+    },
+    {
+      title: '上传日期',
+      dataIndex: 'time',
+      editRow: true,
+      width: 200,
+    },
+  ];
+}
+export function createActionColumn(handleRemove: Function): BasicColumn {
+  return {
+    width: 120,
+    title: t('component.upload.operating'),
+    dataIndex: 'action',
+    fixed: false,
+    customRender: ({ record }) => {
+      const actions: ActionItem[] = [
+        {
+          label: t('component.upload.del'),
+          icon: 'ic:outline-delete-outline',
+          color: 'error',
+          onClick: handleRemove.bind(null, record),
+        },
+      ];
+      // if (checkImgType(record)) {
+      //   actions.unshift({
+      //     label: t('component.upload.preview'),
+      //     onClick: handlePreview.bind(null, record),
+      //   });
+      // }
+      return <TableAction actions={actions} outside={true} />;
+    },
+  };
+}
+// 文件预览列表
+export function createPreviewColumns(): BasicColumn[] {
+  return [
+    {
+      dataIndex: 'url',
+      title: t('component.upload.legend'),
+      width: 100,
+      customRender: ({ record }) => {
+        const { url } = (record as PreviewFileItem) || {};
+        return isImgTypeByName(url) && <ThumbUrl fileUrl={url} />;
+      },
+    },
+    {
+      dataIndex: 'name',
+      title: t('component.upload.fileName'),
+      align: 'left',
+    },
+  ];
+}
+
+export function createPreviewActionColumn(handleRemove, handleDownload): BasicColumn {
+  return {
+    width: 80,
+    title: t('component.upload.operating'),
+    dataIndex: 'action',
+    fixed: false,
+    customRender: ({ record }) => {
+      // const { url } = (record || {}) as PreviewFileItem;
+
+      const actions: ActionItem[] = [
+        {
+          label: t('component.upload.del'),
+          color: 'error',
+          icon: 'ic:outline-delete-outline',
+          onClick: handleRemove.bind(null, record),
+        },
+        {
+          label: t('component.upload.download'),
+          color: 'success',
+          icon: 'ic:outline-download',
+          onClick: handleDownload.bind(null, record),
+        },
+      ];
+      // if (isImgTypeByName(url)) {
+      //   actions.unshift({
+      //     label: t('component.upload.preview'),
+      //     onClick: handlePreview.bind(null, record),
+      //   });
+      // }
+      return <TableAction actions={actions} outside={true} />;
+    },
+  };
+}

+ 139 - 0
src/components/customComponents/imageData.tsx

@@ -0,0 +1,139 @@
+import type { BasicColumn, ActionItem } from '/@/components/Table';
+
+import { FileItem, PreviewFileItem } from '/@/components/Upload/src/types';
+import {
+  // checkImgType,
+  isImgTypeByName,
+} from '/@/components/Upload/src/helper';
+
+import TableAction from '/@/components/Table/src/components/TableAction.vue';
+import ThumbUrl from './ThumbUrl.vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+const { t } = useI18n();
+
+// 文件上传列表
+export function createTableColumns(): BasicColumn[] {
+  return [
+    {
+      dataIndex: 'thumbUrl',
+      title: t('component.upload.legend'),
+      width: 70,
+      customRender: ({ record }) => {
+        const { thumbUrl } = (record as FileItem) || {};
+        return thumbUrl && <ThumbUrl fileUrl={thumbUrl} />;
+      },
+    },
+    {
+      title: t('component.upload.fileName'),
+      dataIndex: 'name',
+      editRow: true,
+      width: 120,
+    },
+    {
+      dataIndex: 'size',
+      title: t('component.upload.fileSize'),
+      width: 80,
+      customRender: ({ text = 0 }) => {
+        return text && (text / 1024).toFixed(2) + 'KB';
+      },
+    },
+    {
+      dataIndex: 'type',
+      title: '文件类型',
+      width: 80,
+    },
+    {
+      title: '上传日期',
+      dataIndex: 'time',
+      editRow: true,
+      width: 120,
+    },
+  ];
+}
+export function createActionColumn(handleRemove: Function): BasicColumn {
+  return {
+    width: 120,
+    title: t('component.upload.operating'),
+    dataIndex: 'action',
+    fixed: false,
+    customRender: ({ record }) => {
+      const actions: ActionItem[] = [
+        {
+          label: t('component.upload.del'),
+          icon: 'ic:outline-delete-outline',
+          color: 'error',
+          onClick: handleRemove.bind(null, record),
+        },
+      ];
+      // if (checkImgType(record)) {
+      //   actions.unshift({
+      //     label: t('component.upload.preview'),
+      //     onClick: handlePreview.bind(null, record),
+      //   });
+      // }
+      return <TableAction actions={actions} outside={true} />;
+    },
+  };
+}
+// 文件预览列表
+export function createPreviewColumns(): BasicColumn[] {
+  return [
+    {
+      dataIndex: 'url',
+      title: t('component.upload.legend'),
+      width: 100,
+      customRender: ({ record }) => {
+        const { url } = (record as PreviewFileItem) || {};
+        return isImgTypeByName(url) && <ThumbUrl fileUrl={url} />;
+      },
+    },
+    {
+      dataIndex: 'name',
+      title: t('component.upload.fileName'),
+      align: 'left',
+    },
+  ];
+}
+
+export function createPreviewActionColumn(
+  handleRemove,
+  handlePreview,
+  handleDownload
+): BasicColumn {
+  return {
+    width: 100,
+    title: t('component.upload.operating'),
+    dataIndex: 'action',
+    fixed: false,
+    customRender: ({ record }) => {
+      // const { url } = (record || {}) as PreviewFileItem;
+
+      const actions: ActionItem[] = [
+        {
+          label: t('component.upload.preview'),
+          icon: 'ant-design:eye-outlined',
+          onClick: handlePreview.bind(null, record),
+        },
+        {
+          label: t('component.upload.del'),
+          color: 'error',
+          icon: 'ic:outline-delete-outline',
+          onClick: handleRemove.bind(null, record),
+        },
+        {
+          label: t('component.upload.download'),
+          color: 'success',
+          icon: 'ic:outline-download',
+          onClick: handleDownload.bind(null, record),
+        },
+      ];
+      // if (isImgTypeByName(url)) {
+      //   actions.unshift({
+      //     label: t('component.upload.preview'),
+      //     onClick: handlePreview.bind(null, record),
+      //   });
+      // }
+      return <TableAction actions={actions} outside={true} />;
+    },
+  };
+}

+ 49 - 0
src/components/customComponents/upload.vue

@@ -0,0 +1,49 @@
+<template>
+  <BasicForm @register="register" class="my-5" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicUpload } from '/@/components/Upload';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { PageWrapper } from '/@/components/Page';
+  import { Alert } from 'ant-design-vue';
+
+  import { uploadApi } from '/@/api/sys/upload';
+
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Upload',
+      label: '',
+      colProps: {
+        span: 8,
+      },
+      rules: [{ required: true, message: '请选择上传文件' }],
+      componentProps: {
+        api: uploadApi,
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicUpload, BasicForm, PageWrapper, [Alert.name]: Alert },
+    setup() {
+      const { createMessage } = useMessage();
+      const [register] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 16,
+        },
+      });
+      return {
+        handleChange: (list: string[]) => {
+          createMessage.info(`已上传文件${JSON.stringify(list)}`);
+        },
+        uploadApi,
+        register,
+      };
+    },
+  });
+</script>

+ 2 - 0
src/locales/lang/en/routes/table.ts

@@ -4,4 +4,6 @@ export default {
   edit: 'editTable',
   test: 'test',
   toggletest: 'toggletest',
+  image: 'image',
+  file: 'file',
 };

+ 2 - 0
src/locales/lang/zh_CN/routes/table.ts

@@ -4,4 +4,6 @@ export default {
   edit: '表格模板',
   test: '测试',
   toggletest: '权限测试',
+  image: '图片管理',
+  file: '附件管理',
 };

+ 8 - 0
src/router/menus/modules/table.ts

@@ -15,6 +15,14 @@ const menu: MenuModule = {
         path: 'edit',
         name: t('routes.table.edit'),
       },
+      {
+        path: 'image',
+        name: t('routes.table.image'),
+      },
+      {
+        path: 'attachment',
+        name: t('routes.table.file'),
+      },
     ],
   },
 };

+ 18 - 0
src/router/routes/modules/table.ts

@@ -31,6 +31,24 @@ const table: AppRouteModule = {
         icon: 'ant-design:table-outlined',
       },
     },
+    {
+      path: 'attachment',
+      name: 'Attachment',
+      component: () => import('/@/views/table/attachment/index.vue'),
+      meta: {
+        title: t('routes.table.file'),
+        icon: 'ant-design:table-outlined',
+      },
+    },
+    {
+      path: 'image',
+      name: 'Image',
+      component: () => import('/@/views/table/imageTable/index.vue'),
+      meta: {
+        title: t('routes.table.image'),
+        icon: 'ant-design:table-outlined',
+      },
+    },
   ],
 };
 

+ 10 - 51
src/views/table/attachment/index.vue

@@ -9,12 +9,11 @@
       <template #form-custom> custom-slot </template>
 
       <template #toolbar>
-        <Upload>
-          <!-- action="http://localhost:8000/api/upload/" -->
-          <!-- method="POST" -->
-          <!-- :showUploadList="false" -->
-          <!-- class="upload-modal-toolbar__btn" -->
-          <a-button type="primary"> t('component.upload.uploadBtn') </a-button>
+        <Upload
+          action="http://localhost:8000/api/upload/"
+          method="POST"
+          :showUploadList="false"
+        >
         </Upload>
       </template>
     </BasicTable>
@@ -24,10 +23,12 @@
 <script lang="ts">
   import { defineComponent, reactive, ref, unref } from 'vue';
   import { useModal } from '/@/components/Modal';
-  // import { createTableColumns, createPreviewActionColumn } from '/@/components/myData/fileData';
+  import {
+    createTableColumns,
+    createPreviewActionColumn,
+  } from '/@/components/customComponents/fileData';
   import { FileItem } from '/@/components/Upload/src/types';
-  //  import { Upload, Alert } from 'ant-design-vue';
-  // import Upload from '/@/components/myData/upload.vue'
+  import Upload from '/@/components/customComponents/upload.vue';
   import {
     BasicTable,
     useTable,
@@ -38,48 +39,6 @@
     TableActionType,
   } from '/@/components/Table';
 
-  // const columns: BasicColumn[] = [
-  //   {
-  //     title: 'id',
-  //     dataIndex: 'id',
-  //     editRow: true,
-  //     // 默认必填校验
-  //     editRule: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '姓名',
-  //     dataIndex: 'name',
-  //     edit: true, // 点击修改当前单元格
-  //     editComponentProps: {
-  //       prefix: '$',
-  //     },
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '地址',
-  //     dataIndex: 'addr',
-  //     editRow: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '年龄',
-  //     dataIndex: 'age',
-  //     editRow: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '开关',
-  //     dataIndex: 'name6',
-  //     editRow: true,
-  //     editComponent: 'Switch',
-  //     editValueMap: (value) => {
-  //       return value ? '开' : '关';
-  //     },
-  //     width: 200,
-  //   },
-  // ];
-
   interface ModelData {
     title: string;
   }

+ 68 - 111
src/views/table/imageTable/index.vue

@@ -1,40 +1,29 @@
 <template>
   <div class="p-4">
-    <BasicTable
-      ref="tableRef"
-      @register="registerTable"
-      :bordered="true"
-      rowKey="id"
-    >
+    <BasicTable ref="tableRef" @register="registerTable" :bordered="true" rowKey="id">
       <template #action="{ record, column }">
         <TableAction :actions="createActions(record, column)" />
       </template>
-      <!-- <template #action="{ record, column }">
-      </template> -->
-      <template #form-custom > custom-slot </template>
+      <template #form-custom> custom-slot </template>
 
       <template #toolbar>
         <Upload>
-        <!-- action="http://localhost:8000/api/upload/" -->
-        <!-- method="POST" -->
-        <!-- :showUploadList="false" -->
-        <!-- class="upload-modal-toolbar__btn" -->
-        <a-button type="primary">
-          t('component.upload.uploadBtn')
-        </a-button>
-      </Upload>
+          <a-button type="primary"> t('component.upload.upload') </a-button>
+        </Upload>
       </template>
     </BasicTable>
-    <!-- <Model @register="addRegister" :modelData="modelData" /> -->
   </div>
 </template>
 <script lang="ts">
   import { defineComponent, reactive, ref, unref } from 'vue';
   import { useModal } from '/@/components/Modal';
-  // import { createTableColumns, createPreviewActionColumn } from '/@/components/myData/imageData';
+  import {
+    createTableColumns,
+    createPreviewActionColumn,
+  } from '/@/components/customComponents/imageData';
+  import { createImgPreview } from '/@/components/Preview/index';
   import { FileItem } from '/@/components/Upload/src/types';
-  //  import { Upload, Alert } from 'ant-design-vue';
-  // import Upload from '/@/components/myData/upload.vue'
+  import Upload from '/@/components/customComponents/upload.vue';
   import {
     BasicTable,
     useTable,
@@ -45,79 +34,56 @@
     TableActionType,
   } from '/@/components/Table';
 
-
-  // const columns: BasicColumn[] = [
-  //   {
-  //     title: 'id',
-  //     dataIndex: 'id',
-  //     editRow: true,
-  //     // 默认必填校验
-  //     editRule: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '姓名',
-  //     dataIndex: 'name',
-  //     edit: true, // 点击修改当前单元格
-  //     editComponentProps: {
-  //       prefix: '$',
-  //     },
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '地址',
-  //     dataIndex: 'addr',
-  //     editRow: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '年龄',
-  //     dataIndex: 'age',
-  //     editRow: true,
-  //     width: 200,
-  //   },
-  //   {
-  //     title: '开关',
-  //     dataIndex: 'name6',
-  //     editRow: true,
-  //     editComponent: 'Switch',
-  //     editValueMap: (value) => {
-  //       return value ? '开' : '关';
-  //     },
-  //     width: 200,
-  //   },
-  // ];
-
   interface ModelData {
-    title: string,
+    title: string;
   }
   export default defineComponent({
     components: { BasicTable, TableAction, Upload },
     setup() {
-       const modelData = reactive<ModelData>({
+      const modelData = reactive<ModelData>({
         title: '',
-      })
+      });
       const tableRef = ref<Nullable<TableActionType>>(null);
       const currentEditKeyRef = ref('');
       const [registerTable] = useTable({
-        title: "基础示例",
-        titleHelpMessage: "温馨提醒",
+        title: '基础示例',
+        titleHelpMessage: '温馨提醒',
         rowSelection: { type: 'checkbox' },
         columns: createTableColumns(),
         clickToRowSelect: false, // 点击行不勾选
-        // showTableSetting: true,
-        // columns: [{title:'Id',dataIndex:'id',width:150},{title:'姓名',dataIndex:'name',width:200},{title:'地址',dataIndex:'addr',width:250}],
-        dataSource: [{id:1,name:'图片1',thumbUrl:'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1999921673,816131569&fm=26&gp=0.jpg', size: 205},
-        {id:2,thumbUrl:'https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2496571732,442429806&fm=26&gp=0.jpg',name:'图片2', size: 342},{id:3,name:'图片3',thumbUrl:'https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2151136234,3513236673&fm=26&gp=0.jpg', size: 18}],
+        dataSource: [
+          {
+            id: 1,
+            name: '图片1',
+            thumbUrl:
+              'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1999921673,816131569&fm=26&gp=0.jpg',
+            size: 205,
+          },
+          {
+            id: 2,
+            thumbUrl:
+              'https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2496571732,442429806&fm=26&gp=0.jpg',
+            name: '图片2',
+            size: 342,
+          },
+          {
+            id: 3,
+            name: '图片3',
+            thumbUrl:
+              'https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2151136234,3513236673&fm=26&gp=0.jpg',
+            size: 18,
+          },
+        ],
         showIndexColumn: false,
         actionColumn: createPreviewActionColumn(handleRemove, handlePreview, handleDownload),
       });
       const [addRegister] = useModal();
       const rowClick = (e: any) => {
-        console.log(e)
-      }
+        console.log(e);
+      };
 
-      function getTableAction() { // 获取组件
+      function getTableAction() {
+        // 获取组件
         const tableAction = unref(tableRef);
         if (!tableAction) {
           throw new Error('tableAction is null');
@@ -125,32 +91,26 @@
         return tableAction;
       }
 
-       function getSelectRowList() { // 获取选中行
+      function getSelectRowList() {
+        // 获取选中行
         console.log(getTableAction().getSelectRows());
       }
-      function getSelectRowKeyList() { // 获取选中行的key --- id
+      function getSelectRowKeyList() {
+        // 获取选中行的key --- id
         console.log(getTableAction().getSelectRowKeys());
       }
 
-
-
       function handleSubmit() {
-        console.log('handleSubmit')
+        console.log('handleSubmit');
         // console.log(data)
       }
 
       function deleteSelect() {
-        console.log('删除选中行')
-        let data = getTableAction().getSelectRowKeys()
-        // console.log(getTableAction().getSelectRowKeys());
-        data.map((item) => {
-          console.log(item)
-        })
+        console.log('删除选中行');
+        let data = getTableAction().getSelectRowKeys();
+        console.log(`data`, data);
       }
 
-
-
-
       function handleCancel(record: EditRecordRow) {
         currentEditKeyRef.value = '';
         record.onEdit?.(false, false);
@@ -158,39 +118,37 @@
 
       async function handleSave(record: EditRecordRow) {
         const pass = await record.onEdit?.(false, true);
-        console.log('------- 保存 ----------')
-        console.log(record)
-        console.log('------- 保存 ----------')
+        console.log('------- 保存 ----------');
+        console.log(record);
+        console.log('------- 保存 ----------');
         if (pass) {
           currentEditKeyRef.value = '';
         }
       }
 
-       // 删除
+      // 删除
       function handleRemove(record: FileItem) {
-        console.log('点击了删除')
-        console.log(record)
-        console.log('点击了删除')
+        console.log('点击了删除');
+        console.log(record);
+        console.log('点击了删除');
       }
       // 预览
-       function handlePreview(record: FileItem) {
-        // const { thumbUrl = '' } = record;
-        // createImgPreview({
-        //   imageList: [thumbUrl],
-        // });
-        console.log(record)
+      function handlePreview(record: FileItem) {
+        console.log(record);
+        const urlList = [record.thumbUrl] as string[];
+        createImgPreview({ imageList: urlList });
       }
-       // 下载
+      // 下载
       function handleDownload(record: FileItem) {
-        console.log('点击了下载')
-        console.log(record)
-        console.log('点击了下载')
+        console.log('点击了下载');
+        console.log(record);
+        console.log('点击了下载');
       }
 
       function handleDelete(record: Recordable) {
         console.log('点击了删除', record);
-        const data = getTableAction().getDataSource()
-        getTableAction().setTableData(data.filter(item => item.id !== record.id))
+        const data = getTableAction().getDataSource();
+        getTableAction().setTableData(data.filter((item) => item.id !== record.id));
       }
 
       function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
@@ -234,8 +192,7 @@
         getSelectRowList,
         getSelectRowKeyList,
         addRegister,
-        handleSubmit
-
+        handleSubmit,
       };
     },
   });

+ 1 - 1
src/views/table/tableData.ts

@@ -51,7 +51,7 @@ export function getBasicShortColumns(): BasicColumn[] {
       width: 150,
       dataIndex: 'id',
       sorter: true,
-      sortOrder: 'asc',
+      sortOrder: 'ascend',
     },
     {
       title: '姓名',

+ 23 - 26
src/views/test/index.vue

@@ -1,36 +1,33 @@
 <template>
-  <PageWrapper title="后台权限示例" contentBackground contentClass="p-4">
-    <div class="mt-2">
-      当前权限模式:
-      <a-button type="link">
-        {{ permissionMode === PermissionModeEnum.BACK ? '后台权限模式' : '前端角色权限模式' }}
-      </a-button>
-      <a-button class="ml-4" @click="togglePermissionMode" type="primary"> 切换权限模式 </a-button>
-      <Divider />
+  <div class="p-4">
+    <Alert message="有预览图" type="info" />
+    <div class="flex justify-center mt-4">
+      <img :src="img" v-for="img in imgList" :key="img" class="mr-2" @click="handleClick(img)" />
     </div>
-  </PageWrapper>
+    <Alert message="无预览图" type="info" />
+    <a-button @click="handlePreview" type="primary" class="mt-4">预览图片</a-button>
+  </div>
 </template>
 <script lang="ts">
-  import { defineComponent, computed } from 'vue';
-  import { useAppStore } from '/@/store/modules/app';
-  import { PermissionModeEnum } from '/@/enums/appEnum';
-  import { Divider } from 'ant-design-vue';
-  import { usePermission } from '/@/hooks/web/usePermission';
-  import { PageWrapper } from '/@/components/Page';
-
+  import { defineComponent } from 'vue';
+  import { Alert } from 'ant-design-vue';
+  import { createImgPreview } from '/@/components/Preview/index';
+  const imgList: string[] = [
+    'https://picsum.photos/id/66/346/216',
+    'https://picsum.photos/id/67/346/216',
+    'https://picsum.photos/id/68/346/216',
+  ];
   export default defineComponent({
-    name: 'CurrentPermissionMode',
-    components: { Divider, PageWrapper },
+    components: { Alert },
     setup() {
-      const appStore = useAppStore();
-      const permissionMode = computed(() => appStore.getProjectConfig.permissionMode);
-      const { togglePermissionMode } = usePermission();
+      function handleClick(img: string) {
+        createImgPreview({ imageList: [img] });
+      }
 
-      return {
-        permissionMode,
-        PermissionModeEnum,
-        togglePermissionMode,
-      };
+      function handlePreview() {
+        createImgPreview({ imageList: imgList });
+      }
+      return { imgList, handleClick, handlePreview };
     },
   });
 </script>

Some files were not shown because too many files changed in this diff