kailong321200875 1 жил өмнө
parent
commit
b69b8ed1bd

+ 1 - 1
.github/workflows/release.yml

@@ -1,7 +1,7 @@
 on:
   push:
     branches:
-      - master
+      - release
 
 name: Release
 

+ 10 - 1
mock/role/index.ts

@@ -147,7 +147,15 @@ const adminList = [
             component: 'views/Components/Table/TreeTable',
             name: 'TreeTable',
             meta: {
-              title: 'TreeTable'
+              title: 'router.TreeTable'
+            }
+          },
+          {
+            path: 'table-image-preview',
+            component: 'views/Components/Table/TableImagePreview',
+            name: 'TableImagePreview',
+            meta: {
+              title: 'router.PicturePreview'
             }
           },
           {
@@ -490,6 +498,7 @@ const testList: string[] = [
   '/components/table/default-table',
   '/components/table/use-table',
   '/components/table/tree-table',
+  '/components/table/table-image-preview',
   '/components/table/ref-table',
   '/components/editor-demo',
   '/components/editor-demo/editor',

+ 3 - 2
mock/table/index.ts

@@ -20,6 +20,7 @@ interface ListProps {
   importance: number
   display_time: string
   pageviews: number
+  image_uri: string
 }
 
 interface TreeListProps {
@@ -45,8 +46,8 @@ for (let i = 0; i < count; i++) {
       content: baseContent,
       importance: '@integer(1, 3)',
       display_time: '@datetime',
-      pageviews: '@integer(300, 5000)'
-      // image_uri
+      pageviews: '@integer(300, 5000)',
+      image_uri: Mock.Random.image('@integer(300, 5000)x@integer(300, 5000)')
     })
   )
 }

+ 2 - 0
package.json

@@ -47,6 +47,7 @@
     "pinia-plugin-persist": "^1.0.0",
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
+    "sortablejs": "^1.15.0",
     "url": "^0.11.1",
     "vue": "3.3.4",
     "vue-i18n": "9.2.2",
@@ -66,6 +67,7 @@
     "@types/nprogress": "^0.2.0",
     "@types/qrcode": "^1.5.1",
     "@types/qs": "^6.9.7",
+    "@types/sortablejs": "^1.15.1",
     "@typescript-eslint/eslint-plugin": "^6.1.0",
     "@typescript-eslint/parser": "^6.1.0",
     "@unocss/transformer-variant-group": "^0.53.5",

+ 1 - 1
src/components/Form/src/Form.vue

@@ -78,7 +78,7 @@ export default defineComponent({
     validateOnRuleChange: propTypes.bool.def(true),
     size: {
       type: String as PropType<ComponentSize>,
-      default: 'small'
+      default: undefined
     },
     disabled: propTypes.bool.def(false),
     scrollToError: propTypes.bool.def(false),

+ 3 - 1
src/components/InputPassword/src/InputPassword.vue

@@ -104,7 +104,9 @@ const getPasswordStrength = computed(() => {
       height: inherit;
       background-color: transparent;
       border-radius: inherit;
-      transition: width 0.5s ease-in-out, background 0.25s;
+      transition:
+        width 0.5s ease-in-out,
+        background 0.25s;
 
       &[data-score='0'] {
         width: 20%;

+ 100 - 10
src/components/Table/src/Table.vue

@@ -1,6 +1,13 @@
 <script lang="tsx">
-import { ElTable, ElTableColumn, ElPagination, ComponentSize, ElTooltipProps } from 'element-plus'
-import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
+import {
+  ElTable,
+  ElTableColumn,
+  ElPagination,
+  ComponentSize,
+  ElTooltipProps,
+  ElImage
+} from 'element-plus'
+import { defineComponent, PropType, ref, computed, unref, watch, onMounted, nextTick } from 'vue'
 import { propTypes } from '@/utils/propTypes'
 import { setIndex } from './helper'
 import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
@@ -8,6 +15,8 @@ import { set } from 'lodash-es'
 import { CSSProperties } from 'vue'
 import { getSlot } from '@/utils/tsxHelper'
 import TableActions from './components/TableActions.vue'
+import Sortable from 'sortablejs'
+import { Icon } from '@/components/Icon'
 
 export default defineComponent({
   name: 'Table',
@@ -48,6 +57,12 @@ export default defineComponent({
       type: Array as PropType<Recordable[]>,
       default: () => []
     },
+    // 是否自动预览
+    preview: {
+      type: Array as PropType<string[]>,
+      default: () => []
+    },
+    sortable: propTypes.bool.def(false),
     height: propTypes.oneOfType([Number, String]),
     maxHeight: propTypes.oneOfType([Number, String]),
     stripe: propTypes.bool.def(false),
@@ -173,7 +188,7 @@ export default defineComponent({
     scrollbarAlwaysOn: propTypes.bool.def(false),
     flexible: propTypes.bool.def(false)
   },
-  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
+  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
   setup(props, { attrs, emit, slots, expose }) {
     const elTableRef = ref<ComponentRef<typeof ElTable>>()
 
@@ -198,6 +213,33 @@ export default defineComponent({
       return propsObj
     })
 
+    const sortableEl = ref()
+    // 初始化拖拽
+    const initDropTable = () => {
+      const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
+      if (!el) return
+      if (unref(sortableEl)) unref(sortableEl).destroy()
+
+      sortableEl.value = Sortable.create(el, {
+        handle: '.table-move',
+        animation: 180,
+        onEnd(e: any) {
+          emit('sortable-change', e)
+        }
+      })
+    }
+
+    watch(
+      () => getProps.value.sortable,
+      async (v) => {
+        await nextTick()
+        v && initDropTable()
+      },
+      {
+        immediate: true
+      }
+    )
+
     const setProps = (props: TableProps = {}) => {
       mergeProps.value = Object.assign(unref(mergeProps), props)
       outsideProps.value = { ...props } as any
@@ -301,7 +343,7 @@ export default defineComponent({
     })
 
     const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
-      const { align, headerAlign, showOverflowTooltip } = unref(getProps)
+      const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
       return columnsChildren.map((v) => {
         if (v.hidden) return null
         const props = { ...v } as any
@@ -312,12 +354,20 @@ export default defineComponent({
         const slots = {
           default: (...args: any[]) => {
             const data = args[0]
+            let isImageUrl = false
+            if (preview.length) {
+              isImageUrl = preview.some((item) => (item as string) === v.field)
+            }
+
             return children && children.length
               ? renderTreeTableColumn(children)
               : props?.slots?.default
               ? props.slots.default(args)
-              : v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
-                data.row[v.field]
+              : v?.formatter
+              ? v?.formatter?.(data.row, data.column, data.row[v.field], data.$index)
+              : isImageUrl
+              ? renderPreview(data.row[v.field])
+              : data.row[v.field]
           }
         }
         if (props?.slots?.header) {
@@ -338,6 +388,21 @@ export default defineComponent({
       })
     }
 
+    const renderPreview = (url: string) => {
+      return (
+        <div class="flex items-center">
+          <ElImage
+            src={url}
+            fit="cover"
+            class="w-[100%] h-100px"
+            lazy
+            preview-src-list={[url]}
+            preview-teleported
+          />
+        </div>
+      )
+    }
+
     const renderTableColumn = (columnsChildren?: TableColumn[]) => {
       const {
         columns,
@@ -347,7 +412,8 @@ export default defineComponent({
         align,
         headerAlign,
         showOverflowTooltip,
-        reserveSelection
+        reserveSelection,
+        preview
       } = unref(getProps)
 
       return (columnsChildren || columns).map((v) => {
@@ -384,12 +450,21 @@ export default defineComponent({
           const slots = {
             default: (...args: any[]) => {
               const data = args[0]
+
+              let isImageUrl = false
+              if (preview.length) {
+                isImageUrl = preview.some((item) => (item as string) === v.field)
+              }
+
               return children && children.length
                 ? renderTreeTableColumn(children)
                 : props?.slots?.default
                 ? props.slots.default(args)
-                : v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
-                  data.row[v.field]
+                : v?.formatter
+                ? v?.formatter?.(data.row, data.column, data.row[v.field], data.$index)
+                : isImageUrl
+                ? renderPreview(data.row[v.field])
+                : data.row[v.field]
             }
           }
           if (props?.slots?.header) {
@@ -419,6 +494,21 @@ export default defineComponent({
       if (getSlot(slots, 'append')) {
         tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
       }
+
+      const { sortable } = unref(getProps)
+
+      const sortableEl = sortable ? (
+        <ElTableColumn
+          className="table-move cursor-move"
+          type="sortable"
+          prop="sortable"
+          width="60px"
+          align="center"
+        >
+          <Icon icon="ant-design:drag-outlined" />
+        </ElTableColumn>
+      ) : null
+
       return (
         <div v-loading={unref(getProps).loading}>
           {unref(getProps).showAction ? (
@@ -426,7 +516,7 @@ export default defineComponent({
           ) : null}
           <ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
             {{
-              default: () => renderTableColumn(),
+              default: () => [sortableEl, ...renderTableColumn()],
               ...tableSlots
             }}
           </ElTable>

+ 2 - 0
src/components/Table/src/types/index.ts

@@ -91,5 +91,7 @@ export interface TableProps extends Omit<Partial<ElTableProps<any[]>>, 'data'> {
   align?: 'left' | 'center' | 'right'
   // 表头对齐方式
   headerAlign?: 'left' | 'center' | 'right'
+  preview?: string[]
+  sortable?: boolean
   data?: Recordable
 }

+ 3 - 1
src/components/UserInfo/src/UserInfo.vue

@@ -94,7 +94,9 @@ const toDocument = () => {
 <style scoped lang="less">
 .fade-bottom-enter-active,
 .fade-bottom-leave-active {
-  transition: opacity 0.25s, transform 0.3s;
+  transition:
+    opacity 0.25s,
+    transform 0.3s;
 }
 
 .fade-bottom-enter-from {

+ 5 - 0
src/hooks/web/useTable.ts

@@ -154,6 +154,11 @@ export const useTable = (config: UseTableConfig) => {
 
     refresh: () => {
       methods.getList()
+    },
+
+    sortableChange: (e: any) => {
+      const { oldIndex, newIndex } = e
+      dataList.value.splice(newIndex, 0, dataList.value.splice(oldIndex, 1)[0])
     }
     // // 删除数据
     // delList: async (ids: string[] | number[], multiple: boolean, message = true) => {

+ 6 - 2
src/locales/en.ts

@@ -158,7 +158,9 @@ export default {
     role: 'Role management',
     document: 'Document',
     inputPassword: 'InputPassword',
-    sticky: 'Sticky'
+    sticky: 'Sticky',
+    treeTable: 'Tree table',
+    PicturePreview: 'Table Image Preview'
   },
   permission: {
     hasPermission: 'Please set the operation permission value'
@@ -426,7 +428,9 @@ export default {
     showOrHiddenStripe: 'Show or hidden stripe',
     showOrHiddenBorder: 'Show or hidden border',
     fixedHeaderOrAuto: 'Fixed header or auto',
-    getSelections: 'Get selections'
+    getSelections: 'Get selections',
+    preview: 'Preview',
+    showOrHiddenSortable: 'Show or hidden sortable'
   },
   richText: {
     richText: 'Rich text',

+ 6 - 2
src/locales/zh-CN.ts

@@ -158,7 +158,9 @@ export default {
     role: '角色管理',
     document: '文档',
     inputPassword: '密码输入框',
-    sticky: '黏性'
+    sticky: '黏性',
+    treeTable: '树形表格',
+    PicturePreview: '表格图片预览'
   },
   permission: {
     hasPermission: '请设置操作权限值'
@@ -421,7 +423,9 @@ export default {
     showOrHiddenStripe: '显示/隐藏斑马纹',
     showOrHiddenBorder: '显示/隐藏边框',
     fixedHeaderOrAuto: '固定头部/自动',
-    getSelections: '获取多选数据'
+    getSelections: '获取多选数据',
+    preview: '封面',
+    showOrHiddenSortable: '显示/隐藏排序'
   },
   richText: {
     richText: '富文本',

+ 9 - 1
src/router/index.ts

@@ -190,7 +190,15 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
             component: () => import('@/views/Components/Table/TreeTable.vue'),
             name: 'TreeTable',
             meta: {
-              title: 'TreeTable'
+              title: t('router.treeTable')
+            }
+          },
+          {
+            path: 'table-image-preview',
+            component: () => import('@/views/Components/Table/TableImagePreview.vue'),
+            name: 'TableImagePreview',
+            meta: {
+              title: t('router.PicturePreview')
             }
           }
         ]

+ 5 - 0
src/utils/is.ts

@@ -103,3 +103,8 @@ export const isUrl = (path: string): boolean => {
 export const isDark = (): boolean => {
   return window.matchMedia('(prefers-color-scheme: dark)').matches
 }
+
+// 是否是图片链接
+export const isImgPath = (path: string): boolean => {
+  return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
+}

+ 82 - 0
src/views/Components/Table/TableImagePreview.vue

@@ -0,0 +1,82 @@
+<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { getTableListApi } from '@/api/table'
+import { TableData } from '@/api/table/types'
+import { ref } from 'vue'
+import { ElTag } from 'element-plus'
+
+interface Params {
+  pageIndex?: number
+  pageSize?: number
+}
+
+const { t } = useI18n()
+
+const columns: TableColumn[] = [
+  {
+    field: 'title',
+    label: t('tableDemo.title')
+  },
+  {
+    field: 'image_uri',
+    label: t('tableDemo.preview')
+  },
+  {
+    field: 'author',
+    label: t('tableDemo.author')
+  },
+  {
+    field: 'display_time',
+    label: t('tableDemo.displayTime')
+  },
+  {
+    field: 'importance',
+    label: t('tableDemo.importance'),
+    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+      return (
+        <ElTag type={cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'}>
+          {cellValue === 1
+            ? t('tableDemo.important')
+            : cellValue === 2
+            ? t('tableDemo.good')
+            : t('tableDemo.commonly')}
+        </ElTag>
+      )
+    }
+  },
+  {
+    field: 'pageviews',
+    label: t('tableDemo.pageviews')
+  }
+]
+
+const loading = ref(true)
+
+let tableDataList = ref<TableData[]>([])
+
+const getTableList = async (params?: Params) => {
+  const res = await getTableListApi(
+    params || {
+      pageIndex: 1,
+      pageSize: 10
+    }
+  )
+    .catch(() => {})
+    .finally(() => {
+      loading.value = false
+    })
+  if (res) {
+    tableDataList.value = res.data.list
+  }
+}
+
+getTableList()
+</script>
+
+<template>
+  <ContentWrap :title="t('router.PicturePreview')">
+    <Table :columns="columns" :data="tableDataList" :loading="loading" :preview="['image_uri']" />
+  </ContentWrap>
+</template>

+ 1 - 1
src/views/Components/Table/TreeTable.vue

@@ -92,7 +92,7 @@ const actionFn = (data: TableSlotDefault) => {
 </script>
 
 <template>
-  <ContentWrap :title="`TreeTable ${t('tableDemo.example')}`">
+  <ContentWrap :title="`${t('router.treeTable')} ${t('tableDemo.example')}`">
     <Table
       v-model:pageSize="pageSize"
       v-model:currentPage="currentPage"

+ 11 - 1
src/views/Components/Table/UseTableDemo.vue

@@ -21,7 +21,8 @@ const { tableRegister, tableMethods, tableState } = useTable({
   }
 })
 const { loading, dataList, total, currentPage, pageSize } = tableState
-const { setProps, setColumn, getElTableExpose, addColumn, delColumn, refresh } = tableMethods
+const { setProps, setColumn, getElTableExpose, addColumn, delColumn, refresh, sortableChange } =
+  tableMethods
 
 const { t } = useI18n()
 
@@ -213,6 +214,11 @@ const getSelections = async () => {
   const selections = elTableRef?.getSelectionRows()
   console.log(selections)
 }
+
+const sortable = ref(false)
+const showOrHiddenSortable = () => {
+  sortable.value = !unref(sortable)
+}
 </script>
 
 <template>
@@ -244,6 +250,8 @@ const getSelections = async () => {
     <ElButton @click="fixedHeaderOrAuto">{{ t('tableDemo.fixedHeaderOrAuto') }}</ElButton>
 
     <ElButton @click="getSelections">{{ t('tableDemo.getSelections') }}</ElButton>
+
+    <ElButton @click="showOrHiddenSortable">{{ t('tableDemo.showOrHiddenSortable') }}</ElButton>
   </ContentWrap>
   <ContentWrap :title="`UseTable ${t('tableDemo.example')}`">
     <Table
@@ -253,6 +261,7 @@ const getSelections = async () => {
       :columns="columns"
       :data="dataList"
       :loading="loading"
+      :sortable="sortable"
       :pagination="
         canShowPagination
           ? {
@@ -262,6 +271,7 @@ const getSelections = async () => {
       "
       @register="tableRegister"
       @refresh="refresh"
+      @sortable-change="sortableChange"
     />
   </ContentWrap>
 </template>

+ 4 - 5
vite.config.ts

@@ -10,7 +10,6 @@ import { viteMockServe } from 'vite-plugin-mock'
 import PurgeIcons from 'vite-plugin-purge-icons'
 import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
 import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
-// @ts-expect-error
 import DefineOptions from "unplugin-vue-define-options/vite"
 import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
 import UnoCSS from 'unocss/vite'
@@ -50,10 +49,10 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
           }
         }]
       }),
-      // EslintPlugin({
-      //   cache: false,
-      //   include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
-      // }),
+      EslintPlugin({
+        cache: false,
+        include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
+      }),
       VueI18nPlugin({
         runtimeOnly: true,
         compositionOnly: true,