feat: 新增Uload

This commit is contained in:
kailong321200875 2023-07-23 12:49:57 +08:00
parent 040476fe24
commit c181887f7f
10 changed files with 191 additions and 156 deletions

View File

@ -17,7 +17,8 @@ import {
ElTransfer, ElTransfer,
ElAutocomplete, ElAutocomplete,
ElDivider, ElDivider,
ElTreeSelect ElTreeSelect,
ElUpload
} from 'element-plus' } from 'element-plus'
import { InputPassword } from '@/components/InputPassword' import { InputPassword } from '@/components/InputPassword'
import { Editor } from '@/components/Editor' import { Editor } from '@/components/Editor'
@ -45,7 +46,8 @@ const componentMap: Recordable<Component, ComponentName> = {
SelectV2: ElSelectV2, SelectV2: ElSelectV2,
InputPassword: InputPassword, InputPassword: InputPassword,
Editor: Editor, Editor: Editor,
TreeSelect: ElTreeSelect TreeSelect: ElTreeSelect,
Upload: ElUpload
} }
export { componentMap } export { componentMap }

View File

@ -17,7 +17,8 @@ import {
DatePickerProps, DatePickerProps,
FormItemProps as ElFormItemProps, FormItemProps as ElFormItemProps,
FormProps as ElFormProps, FormProps as ElFormProps,
ISelectProps ISelectProps,
UploadProps
} from 'element-plus' } from 'element-plus'
import { IEditorConfig } from '@wangeditor/editor' import { IEditorConfig } from '@wangeditor/editor'
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
@ -51,7 +52,8 @@ export enum ComponentNameEnum {
SELECT_V2 = 'SelectV2', SELECT_V2 = 'SelectV2',
INPUT_PASSWORD = 'InputPassword', INPUT_PASSWORD = 'InputPassword',
EDITOR = 'Editor', EDITOR = 'Editor',
TREE_SELECT = 'TreeSelect' TREE_SELECT = 'TreeSelect',
UPLOAD = 'Upload'
} }
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
@ -505,6 +507,16 @@ export interface FormItemProps extends Partial<ElFormItemProps> {
} }
} }
export interface UploadComponentProps extends Partial<UploadProps> {
slots?: {
default?: (...args: any[]) => JSX.Element | null
trigger?: (...args: any[]) => JSX.Element | null
tip?: (...args: any[]) => JSX.Element | null
file?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface TreeSelectComponentProps export interface TreeSelectComponentProps
extends Omit<Partial<SelectComponentProps>, 'props' | 'on' | 'slots'> { extends Omit<Partial<SelectComponentProps>, 'props' | 'on' | 'slots'> {
data?: any[] data?: any[]
@ -607,6 +619,7 @@ export interface FormSchema {
| TimePickerComponentProps | TimePickerComponentProps
| InputPasswordComponentProps | InputPasswordComponentProps
| TreeSelectComponentProps | TreeSelectComponentProps
| UploadComponentProps
/** /**
* formItem组件属性element-plus文档 * formItem组件属性element-plus文档

View File

@ -7,7 +7,7 @@ import {
ElTooltipProps, ElTooltipProps,
ElImage ElImage
} from 'element-plus' } from 'element-plus'
import { defineComponent, PropType, ref, computed, unref, watch, onMounted, nextTick } from 'vue' import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { setIndex } from './helper' import { setIndex } from './helper'
import type { TableProps, TableColumn, Pagination, TableSetProps } from './types' import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
@ -15,8 +15,8 @@ import { set } from 'lodash-es'
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
import { getSlot } from '@/utils/tsxHelper' import { getSlot } from '@/utils/tsxHelper'
import TableActions from './components/TableActions.vue' import TableActions from './components/TableActions.vue'
import Sortable from 'sortablejs' // import Sortable from 'sortablejs'
import { Icon } from '@/components/Icon' // import { Icon } from '@/components/Icon'
export default defineComponent({ export default defineComponent({
name: 'Table', name: 'Table',
@ -62,7 +62,7 @@ export default defineComponent({
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [] default: () => []
}, },
sortable: propTypes.bool.def(false), // sortable: propTypes.bool.def(false),
height: propTypes.oneOfType([Number, String]), height: propTypes.oneOfType([Number, String]),
maxHeight: propTypes.oneOfType([Number, String]), maxHeight: propTypes.oneOfType([Number, String]),
stripe: propTypes.bool.def(false), stripe: propTypes.bool.def(false),
@ -213,32 +213,32 @@ export default defineComponent({
return propsObj return propsObj
}) })
const sortableEl = ref() // const sortableEl = ref()
// //
const initDropTable = () => { // const initDropTable = () => {
const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody') // const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
if (!el) return // if (!el) return
if (unref(sortableEl)) unref(sortableEl).destroy() // if (unref(sortableEl)) unref(sortableEl).destroy()
sortableEl.value = Sortable.create(el, { // sortableEl.value = Sortable.create(el, {
handle: '.table-move', // handle: '.table-move',
animation: 180, // animation: 180,
onEnd(e: any) { // onEnd(e: any) {
emit('sortable-change', e) // emit('sortable-change', e)
} // }
}) // })
} // }
watch( // watch(
() => getProps.value.sortable, // () => getProps.value.sortable,
async (v) => { // async (v) => {
await nextTick() // await nextTick()
v && initDropTable() // v && initDropTable()
}, // },
{ // {
immediate: true // immediate: true
} // }
) // )
const setProps = (props: TableProps = {}) => { const setProps = (props: TableProps = {}) => {
mergeProps.value = Object.assign(unref(mergeProps), props) mergeProps.value = Object.assign(unref(mergeProps), props)
@ -495,19 +495,19 @@ export default defineComponent({
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args) tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
} }
const { sortable } = unref(getProps) // const { sortable } = unref(getProps)
const sortableEl = sortable ? ( // const sortableEl = sortable ? (
<ElTableColumn // <ElTableColumn
className="table-move cursor-move" // className="table-move cursor-move"
type="sortable" // type="sortable"
prop="sortable" // prop="sortable"
width="60px" // width="60px"
align="center" // align="center"
> // >
<Icon icon="ant-design:drag-outlined" /> // <Icon icon="ant-design:drag-outlined" />
</ElTableColumn> // </ElTableColumn>
) : null // ) : null
return ( return (
<div v-loading={unref(getProps).loading}> <div v-loading={unref(getProps).loading}>
@ -520,7 +520,7 @@ export default defineComponent({
) : null} ) : null}
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}> <ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
{{ {{
default: () => [sortableEl, ...renderTableColumn()], default: () => renderTableColumn(),
...tableSlots ...tableSlots
}} }}
</ElTable> </ElTable>

View File

@ -5,16 +5,16 @@ import {
ElDropdown, ElDropdown,
ElDropdownMenu, ElDropdownMenu,
ElDropdownItem, ElDropdownItem,
ComponentSize, ComponentSize
ElPopover, // ElPopover,
ElTree // ElTree
} from 'element-plus' } from 'element-plus'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { TableColumn } from '../types' import { TableColumn } from '../types'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { eachTree } from '@/utils/tree' // import { eachTree } from '@/utils/tree'
const appStore = useAppStore() const appStore = useAppStore()
const sizeMap = computed(() => appStore.sizeMap) const sizeMap = computed(() => appStore.sizeMap)
@ -39,16 +39,6 @@ export default defineComponent({
emit('changSize', size) emit('changSize', size)
} }
const defaultCheckeds = computed(() => {
const checkeds: string[] = []
eachTree(unref(columns), (item: TableColumn) => {
if (!item.hidden) {
checkeds.push(item.field)
}
})
return checkeds
})
const columns = computed(() => { const columns = computed(() => {
return cloneDeep(props.columns).filter((v) => { return cloneDeep(props.columns).filter((v) => {
// typeselectionexpand // typeselectionexpand
@ -68,81 +58,6 @@ export default defineComponent({
} }
) )
//
function updateTreeData(treeData, draggedNode, targetNode, placement) {
let updatedTreeData = cloneDeep(treeData) //
//
function findNodes(nodeId, nodes) {
for (const node of nodes) {
if (node?.field === nodeId) {
return node
}
if (node.children && node.children.length) {
const foundNode = findNodes(nodeId, node.children)
if (foundNode) {
return foundNode
}
}
}
return null
}
const draggedNodeToUpdate = findNodes(draggedNode?.data?.field, updatedTreeData)
const targetNodeToUpdate = targetNode
? findNodes(targetNode?.data?.field, updatedTreeData)
: null
if (!draggedNodeToUpdate || (targetNode && !targetNodeToUpdate)) {
//
console.error('无法找到要更新的节点或目标节点')
return treeData //
}
//
function removeNode(node) {
const parent = updatedTreeData.find((n) => n.children.includes(node))
if (parent) {
parent.children = parent.children.filter((n) => n?.field !== node?.field)
} else {
updatedTreeData = updatedTreeData.filter((n) => n?.field !== node?.field)
}
}
removeNode(draggedNodeToUpdate)
//
function insertNode(placement) {
if (placement === 'before' || placement === 'after') {
const parent = updatedTreeData.find((n) => n.children.includes(targetNodeToUpdate))
const index = parent.children.findIndex((n) => n?.field === targetNodeToUpdate?.data?.id)
const insertionIndex = placement === 'before' ? index : index + 1
parent.children.splice(insertionIndex, 0, draggedNodeToUpdate)
} else if (placement === 'inner') {
targetNodeToUpdate.children.push(draggedNodeToUpdate)
}
}
if (targetNode) {
insertNode(placement)
} else {
// targetNode
updatedTreeData.push(draggedNodeToUpdate)
}
return updatedTreeData
}
const onNodeDragEnd = (before: any, after: any, inner: string) => {
if (inner === 'none') return
console.log(before, after, inner)
const cloneDeepColumns = cloneDeep(unref(props.columns))
const newColumns = updateTreeData(cloneDeepColumns, after, before, inner)
console.log(newColumns)
}
const onCheckChange = (data: TableColumn, isChecked: boolean, childrenHasChecked) => {
console.log(data, isChecked, childrenHasChecked)
}
return () => ( return () => (
<> <>
<div class="text-right h-28px flex items-center justify-end"> <div class="text-right h-28px flex items-center justify-end">
@ -189,7 +104,7 @@ export default defineComponent({
</ElTooltip> </ElTooltip>
{/* <ElTooltip content={t('common.columnSetting')} placement="top"> */} {/* <ElTooltip content={t('common.columnSetting')} placement="top"> */}
<ElPopover trigger="click" placement="left"> {/* <ElPopover trigger="click" placement="left">
{{ {{
default: () => { default: () => {
return ( return (
@ -223,7 +138,7 @@ export default defineComponent({
) )
} }
}} }}
</ElPopover> </ElPopover> */}
{/* </ElTooltip> */} {/* </ElTooltip> */}
</div> </div>
</> </>

View File

@ -18,7 +18,7 @@ export interface TableColumn {
minWidth?: string | number minWidth?: string | number
fixed?: boolean | 'left' | 'right' fixed?: boolean | 'left' | 'right'
renderHeader?: (...args: any[]) => JSX.Element | null renderHeader?: (...args: any[]) => JSX.Element | null
sortable?: boolean | 'custom' // sortable?: boolean
sortMethod?: (...args: any[]) => number sortMethod?: (...args: any[]) => number
sortBy?: string | string[] | ((...args: any[]) => string | string[]) sortBy?: string | string[] | ((...args: any[]) => string | string[])
sortOrders?: (string | null)[] sortOrders?: (string | null)[]

View File

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

View File

@ -302,7 +302,10 @@ export default {
// 自定义节点内容 // 自定义节点内容
customContent: 'Custom content', customContent: 'Custom content',
// 懒加载 // 懒加载
lazyLoad: 'Lazy load' lazyLoad: 'Lazy load',
upload: 'Upload',
// 用户头像
userAvatar: 'User avatar'
}, },
guideDemo: { guideDemo: {
guide: 'Guide', guide: 'Guide',

View File

@ -299,7 +299,9 @@ export default {
multiple: '多选', multiple: '多选',
filterable: '可筛选', filterable: '可筛选',
customContent: '自定义内容', customContent: '自定义内容',
lazyLoad: '懒加载' lazyLoad: '懒加载',
upload: '上传',
userAvatar: '用户头像'
}, },
guideDemo: { guideDemo: {
guide: '引导页', guide: '引导页',

View File

@ -13,7 +13,10 @@ import {
ElRadioButton, ElRadioButton,
ElCheckbox, ElCheckbox,
ElCheckboxButton, ElCheckboxButton,
ElInput ElInput,
ElMessage,
ElMessageBox,
ElIcon
} from 'element-plus' } from 'element-plus'
import { getDictOneApi } from '@/api/common' import { getDictOneApi } from '@/api/common'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
@ -441,6 +444,8 @@ const treeSelectData = [
let id = 0 let id = 0
const imageUrl = ref('')
const schema = reactive<FormSchema[]>([ const schema = reactive<FormSchema[]>([
{ {
field: 'field1', field: 'field1',
@ -1663,6 +1668,88 @@ const schema = reactive<FormSchema[]>([
}, },
data: treeSelectData data: treeSelectData
} }
},
{
field: 'field82',
component: 'Divider',
label: `${t('formDemo.upload')}`
},
{
field: 'field83',
component: 'Upload',
label: `${t('formDemo.default')}`,
componentProps: {
limit: 3,
action: 'https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15',
fileList: [
{
name: 'element-plus-logo.svg',
url: 'https://element-plus.org/images/element-plus-logo.svg'
},
{
name: 'element-plus-logo2.svg',
url: 'https://element-plus.org/images/element-plus-logo.svg'
}
],
multiple: true,
onPreview: (uploadFile) => {
console.log(uploadFile)
},
onRemove: (file) => {
console.log(file)
},
beforeRemove: (uploadFile) => {
return ElMessageBox.confirm(`Cancel the transfer of ${uploadFile.name} ?`).then(
() => true,
() => false
)
},
onExceed: (files, uploadFiles) => {
ElMessage.warning(
`The limit is 3, you selected ${files.length} files this time, add up to ${
files.length + uploadFiles.length
} totally`
)
},
slots: {
default: () => <ElButton type="primary">Click to upload</ElButton>,
tip: () => <div class="el-upload__tip">jpg/png files with a size less than 500KB.</div>
}
}
},
{
field: 'field84',
component: 'Upload',
label: `${t('formDemo.userAvatar')}`,
componentProps: {
action: 'https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15',
showFileList: false,
onSuccess: (_response, uploadFile) => {
imageUrl.value = URL.createObjectURL(uploadFile.raw!)
},
beforeUpload: (rawFile) => {
if (rawFile.type !== 'image/jpeg') {
ElMessage.error('Avatar picture must be JPG format!')
return false
} else if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('Avatar picture size can not exceed 2MB!')
return false
}
return true
},
slots: {
default: () => (
<>
{imageUrl.value ? <img src={imageUrl.value} class="avatar" /> : null}
{!imageUrl.value ? (
<ElIcon class="avatar-uploader-icon" size="large">
add
</ElIcon>
) : null}
</>
)
}
}
} }
]) ])
</script> </script>
@ -1714,4 +1801,25 @@ const schema = reactive<FormSchema[]>([
padding: 6px 5px; padding: 6px 5px;
margin-left: 15px; margin-left: 15px;
} }
.el-upload {
position: relative;
overflow: hidden;
cursor: pointer;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
transition: var(--el-transition-duration-fast);
}
.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
width: 178px;
height: 178px;
font-size: 28px;
color: #8c939d;
text-align: center;
}
</style> </style>

View File

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