Skip to content

Commit b69b8ed

Browse files
feat: 拖拽表格
1 parent cfb3b3a commit b69b8ed

File tree

18 files changed

+254
-29
lines changed

18 files changed

+254
-29
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
on:
22
push:
33
branches:
4-
- master
4+
- release
55

66
name: Release
77

mock/role/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,15 @@ const adminList = [
147147
component: 'views/Components/Table/TreeTable',
148148
name: 'TreeTable',
149149
meta: {
150-
title: 'TreeTable'
150+
title: 'router.TreeTable'
151+
}
152+
},
153+
{
154+
path: 'table-image-preview',
155+
component: 'views/Components/Table/TableImagePreview',
156+
name: 'TableImagePreview',
157+
meta: {
158+
title: 'router.PicturePreview'
151159
}
152160
},
153161
{
@@ -490,6 +498,7 @@ const testList: string[] = [
490498
'/components/table/default-table',
491499
'/components/table/use-table',
492500
'/components/table/tree-table',
501+
'/components/table/table-image-preview',
493502
'/components/table/ref-table',
494503
'/components/editor-demo',
495504
'/components/editor-demo/editor',

mock/table/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface ListProps {
2020
importance: number
2121
display_time: string
2222
pageviews: number
23+
image_uri: string
2324
}
2425

2526
interface TreeListProps {
@@ -45,8 +46,8 @@ for (let i = 0; i < count; i++) {
4546
content: baseContent,
4647
importance: '@integer(1, 3)',
4748
display_time: '@datetime',
48-
pageviews: '@integer(300, 5000)'
49-
// image_uri
49+
pageviews: '@integer(300, 5000)',
50+
image_uri: Mock.Random.image('@integer(300, 5000)x@integer(300, 5000)')
5051
})
5152
)
5253
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"pinia-plugin-persist": "^1.0.0",
4848
"qrcode": "^1.5.3",
4949
"qs": "^6.11.2",
50+
"sortablejs": "^1.15.0",
5051
"url": "^0.11.1",
5152
"vue": "3.3.4",
5253
"vue-i18n": "9.2.2",
@@ -66,6 +67,7 @@
6667
"@types/nprogress": "^0.2.0",
6768
"@types/qrcode": "^1.5.1",
6869
"@types/qs": "^6.9.7",
70+
"@types/sortablejs": "^1.15.1",
6971
"@typescript-eslint/eslint-plugin": "^6.1.0",
7072
"@typescript-eslint/parser": "^6.1.0",
7173
"@unocss/transformer-variant-group": "^0.53.5",

src/components/Form/src/Form.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default defineComponent({
7878
validateOnRuleChange: propTypes.bool.def(true),
7979
size: {
8080
type: String as PropType<ComponentSize>,
81-
default: 'small'
81+
default: undefined
8282
},
8383
disabled: propTypes.bool.def(false),
8484
scrollToError: propTypes.bool.def(false),

src/components/InputPassword/src/InputPassword.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ const getPasswordStrength = computed(() => {
104104
height: inherit;
105105
background-color: transparent;
106106
border-radius: inherit;
107-
transition: width 0.5s ease-in-out, background 0.25s;
107+
transition:
108+
width 0.5s ease-in-out,
109+
background 0.25s;
108110
109111
&[data-score='0'] {
110112
width: 20%;

src/components/Table/src/Table.vue

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
<script lang="tsx">
2-
import { ElTable, ElTableColumn, ElPagination, ComponentSize, ElTooltipProps } from 'element-plus'
3-
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
2+
import {
3+
ElTable,
4+
ElTableColumn,
5+
ElPagination,
6+
ComponentSize,
7+
ElTooltipProps,
8+
ElImage
9+
} from 'element-plus'
10+
import { defineComponent, PropType, ref, computed, unref, watch, onMounted, nextTick } from 'vue'
411
import { propTypes } from '@/utils/propTypes'
512
import { setIndex } from './helper'
613
import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
714
import { set } from 'lodash-es'
815
import { CSSProperties } from 'vue'
916
import { getSlot } from '@/utils/tsxHelper'
1017
import TableActions from './components/TableActions.vue'
18+
import Sortable from 'sortablejs'
19+
import { Icon } from '@/components/Icon'
1120
1221
export default defineComponent({
1322
name: 'Table',
@@ -48,6 +57,12 @@ export default defineComponent({
4857
type: Array as PropType<Recordable[]>,
4958
default: () => []
5059
},
60+
// 是否自动预览
61+
preview: {
62+
type: Array as PropType<string[]>,
63+
default: () => []
64+
},
65+
sortable: propTypes.bool.def(false),
5166
height: propTypes.oneOfType([Number, String]),
5267
maxHeight: propTypes.oneOfType([Number, String]),
5368
stripe: propTypes.bool.def(false),
@@ -173,7 +188,7 @@ export default defineComponent({
173188
scrollbarAlwaysOn: propTypes.bool.def(false),
174189
flexible: propTypes.bool.def(false)
175190
},
176-
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
191+
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
177192
setup(props, { attrs, emit, slots, expose }) {
178193
const elTableRef = ref<ComponentRef<typeof ElTable>>()
179194
@@ -198,6 +213,33 @@ export default defineComponent({
198213
return propsObj
199214
})
200215
216+
const sortableEl = ref()
217+
// 初始化拖拽
218+
const initDropTable = () => {
219+
const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
220+
if (!el) return
221+
if (unref(sortableEl)) unref(sortableEl).destroy()
222+
223+
sortableEl.value = Sortable.create(el, {
224+
handle: '.table-move',
225+
animation: 180,
226+
onEnd(e: any) {
227+
emit('sortable-change', e)
228+
}
229+
})
230+
}
231+
232+
watch(
233+
() => getProps.value.sortable,
234+
async (v) => {
235+
await nextTick()
236+
v && initDropTable()
237+
},
238+
{
239+
immediate: true
240+
}
241+
)
242+
201243
const setProps = (props: TableProps = {}) => {
202244
mergeProps.value = Object.assign(unref(mergeProps), props)
203245
outsideProps.value = { ...props } as any
@@ -301,7 +343,7 @@ export default defineComponent({
301343
})
302344
303345
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
304-
const { align, headerAlign, showOverflowTooltip } = unref(getProps)
346+
const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
305347
return columnsChildren.map((v) => {
306348
if (v.hidden) return null
307349
const props = { ...v } as any
@@ -312,12 +354,20 @@ export default defineComponent({
312354
const slots = {
313355
default: (...args: any[]) => {
314356
const data = args[0]
357+
let isImageUrl = false
358+
if (preview.length) {
359+
isImageUrl = preview.some((item) => (item as string) === v.field)
360+
}
361+
315362
return children && children.length
316363
? renderTreeTableColumn(children)
317364
: props?.slots?.default
318365
? props.slots.default(args)
319-
: v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
320-
data.row[v.field]
366+
: v?.formatter
367+
? v?.formatter?.(data.row, data.column, data.row[v.field], data.$index)
368+
: isImageUrl
369+
? renderPreview(data.row[v.field])
370+
: data.row[v.field]
321371
}
322372
}
323373
if (props?.slots?.header) {
@@ -338,6 +388,21 @@ export default defineComponent({
338388
})
339389
}
340390
391+
const renderPreview = (url: string) => {
392+
return (
393+
<div class="flex items-center">
394+
<ElImage
395+
src={url}
396+
fit="cover"
397+
class="w-[100%] h-100px"
398+
lazy
399+
preview-src-list={[url]}
400+
preview-teleported
401+
/>
402+
</div>
403+
)
404+
}
405+
341406
const renderTableColumn = (columnsChildren?: TableColumn[]) => {
342407
const {
343408
columns,
@@ -347,7 +412,8 @@ export default defineComponent({
347412
align,
348413
headerAlign,
349414
showOverflowTooltip,
350-
reserveSelection
415+
reserveSelection,
416+
preview
351417
} = unref(getProps)
352418
353419
return (columnsChildren || columns).map((v) => {
@@ -384,12 +450,21 @@ export default defineComponent({
384450
const slots = {
385451
default: (...args: any[]) => {
386452
const data = args[0]
453+
454+
let isImageUrl = false
455+
if (preview.length) {
456+
isImageUrl = preview.some((item) => (item as string) === v.field)
457+
}
458+
387459
return children && children.length
388460
? renderTreeTableColumn(children)
389461
: props?.slots?.default
390462
? props.slots.default(args)
391-
: v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
392-
data.row[v.field]
463+
: v?.formatter
464+
? v?.formatter?.(data.row, data.column, data.row[v.field], data.$index)
465+
: isImageUrl
466+
? renderPreview(data.row[v.field])
467+
: data.row[v.field]
393468
}
394469
}
395470
if (props?.slots?.header) {
@@ -419,14 +494,29 @@ export default defineComponent({
419494
if (getSlot(slots, 'append')) {
420495
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
421496
}
497+
498+
const { sortable } = unref(getProps)
499+
500+
const sortableEl = sortable ? (
501+
<ElTableColumn
502+
className="table-move cursor-move"
503+
type="sortable"
504+
prop="sortable"
505+
width="60px"
506+
align="center"
507+
>
508+
<Icon icon="ant-design:drag-outlined" />
509+
</ElTableColumn>
510+
) : null
511+
422512
return (
423513
<div v-loading={unref(getProps).loading}>
424514
{unref(getProps).showAction ? (
425515
<TableActions onChangSize={changSize} onRefresh={refresh} />
426516
) : null}
427517
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
428518
{{
429-
default: () => renderTableColumn(),
519+
default: () => [sortableEl, ...renderTableColumn()],
430520
...tableSlots
431521
}}
432522
</ElTable>

src/components/Table/src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,7 @@ export interface TableProps extends Omit<Partial<ElTableProps<any[]>>, 'data'> {
9191
align?: 'left' | 'center' | 'right'
9292
// 表头对齐方式
9393
headerAlign?: 'left' | 'center' | 'right'
94+
preview?: string[]
95+
sortable?: boolean
9496
data?: Recordable
9597
}

src/components/UserInfo/src/UserInfo.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ const toDocument = () => {
9494
<style scoped lang="less">
9595
.fade-bottom-enter-active,
9696
.fade-bottom-leave-active {
97-
transition: opacity 0.25s, transform 0.3s;
97+
transition:
98+
opacity 0.25s,
99+
transform 0.3s;
98100
}
99101
100102
.fade-bottom-enter-from {

src/hooks/web/useTable.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ export const useTable = (config: UseTableConfig) => {
154154

155155
refresh: () => {
156156
methods.getList()
157+
},
158+
159+
sortableChange: (e: any) => {
160+
const { oldIndex, newIndex } = e
161+
dataList.value.splice(newIndex, 0, dataList.value.splice(oldIndex, 1)[0])
157162
}
158163
// // 删除数据
159164
// delList: async (ids: string[] | number[], multiple: boolean, message = true) => {

src/locales/en.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ export default {
158158
role: 'Role management',
159159
document: 'Document',
160160
inputPassword: 'InputPassword',
161-
sticky: 'Sticky'
161+
sticky: 'Sticky',
162+
treeTable: 'Tree table',
163+
PicturePreview: 'Table Image Preview'
162164
},
163165
permission: {
164166
hasPermission: 'Please set the operation permission value'
@@ -426,7 +428,9 @@ export default {
426428
showOrHiddenStripe: 'Show or hidden stripe',
427429
showOrHiddenBorder: 'Show or hidden border',
428430
fixedHeaderOrAuto: 'Fixed header or auto',
429-
getSelections: 'Get selections'
431+
getSelections: 'Get selections',
432+
preview: 'Preview',
433+
showOrHiddenSortable: 'Show or hidden sortable'
430434
},
431435
richText: {
432436
richText: 'Rich text',

src/locales/zh-CN.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ export default {
158158
role: '角色管理',
159159
document: '文档',
160160
inputPassword: '密码输入框',
161-
sticky: '黏性'
161+
sticky: '黏性',
162+
treeTable: '树形表格',
163+
PicturePreview: '表格图片预览'
162164
},
163165
permission: {
164166
hasPermission: '请设置操作权限值'
@@ -421,7 +423,9 @@ export default {
421423
showOrHiddenStripe: '显示/隐藏斑马纹',
422424
showOrHiddenBorder: '显示/隐藏边框',
423425
fixedHeaderOrAuto: '固定头部/自动',
424-
getSelections: '获取多选数据'
426+
getSelections: '获取多选数据',
427+
preview: '封面',
428+
showOrHiddenSortable: '显示/隐藏排序'
425429
},
426430
richText: {
427431
richText: '富文本',

src/router/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,15 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
190190
component: () => import('@/views/Components/Table/TreeTable.vue'),
191191
name: 'TreeTable',
192192
meta: {
193-
title: 'TreeTable'
193+
title: t('router.treeTable')
194+
}
195+
},
196+
{
197+
path: 'table-image-preview',
198+
component: () => import('@/views/Components/Table/TableImagePreview.vue'),
199+
name: 'TableImagePreview',
200+
meta: {
201+
title: t('router.PicturePreview')
194202
}
195203
}
196204
]

src/utils/is.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ export const isUrl = (path: string): boolean => {
103103
export const isDark = (): boolean => {
104104
return window.matchMedia('(prefers-color-scheme: dark)').matches
105105
}
106+
107+
// 是否是图片链接
108+
export const isImgPath = (path: string): boolean => {
109+
return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
110+
}

0 commit comments

Comments
 (0)