UNPKG

jobsys-newbie

Version:

Enhanced component based on ant-design-vue

1,207 lines (1,080 loc) 31.5 kB
import { computed, defineComponent, inject, nextTick, onMounted, reactive, ref, watch, withModifiers } from "vue" import { Button, Card, Checkbox, CheckboxGroup, Col, Divider, Dropdown, Flex, Image, Input, Menu, MenuItem, message, Modal, Row, Switch, Select, Table, Tag, Tooltip, InputNumber, } from "ant-design-vue" import { cloneDeep, isArray, isBoolean, isEqual, isFunction, isObject, isUndefined } from "lodash-es" import { DownOutlined, FilePdfOutlined, PictureOutlined, SyncOutlined, InfoCircleOutlined, EditOutlined, CloseCircleOutlined, CheckCircleOutlined, } from "@ant-design/icons-vue" import { useCache, useFetch, useProcessStatusSuccess, useSm3, useT } from "../../hooks" import { NEWBIE_TABLE } from "../provider/NewbieProvider.jsx" import NewbieButton from "../button/NewbieButton.jsx" import NewbieSearch from "../search/NewbieSearch.jsx" import "./index.less" /** * Newbie 表格组件 * @version 1.0.0 */ export default defineComponent({ name: "NewbieTable", props: { /** * 表格标题 */ title: { type: String, default: "" }, /** * 是否使用表单搜索 */ filterable: { type: Boolean, default: true }, /** * 原生表格属性 */ tableProps: { type: Object, default: () => ({}) }, /** * 原生表格事件 */ tableEvents: { type: Object, default: () => ({}) }, /** * 原生 slot */ tableSlots: { type: Object, default: () => ({}) }, /** * 是否使用分页,为 Object 时时使用自定义分页 */ pagination: { type: [Boolean, Object], default: true }, /** * 原生翻页事件 */ pageEvents: { type: Object, default: () => ({}) }, /** * 是否在搜索条件变化时自动搜索 */ autoQuery: { type: Boolean, default: false }, /** * 表格数据请求 URL */ url: { type: String, default: "" }, /** * v1.18 * 编辑后保存的 URL */ editable: { type: Boolean, default: false }, /** * v1.18 * 编辑后保存的 URL */ submitUrl: { type: String, default: "" }, /** * v1.18 * 单元格编辑提交的前置方法, 返回一个 Promise */ beforeCellSubmit: { type: Function, default: null }, /** * 表格请求方式,默认为 GET * * @values get, post */ method: { type: String, default: "get" }, /** * 请求数据时额外提交的参数 */ extraData: { type: Object, default: () => ({}) }, /** * 请求后 Res 的处理方法, 如有 url, 则该方法必须传 */ afterFetched: { type: Function, default: null }, /** * @typedef {Object} TableColumnConfig * @property {string} title - 列标题 * @property {string} dataIndex - 列数据在数据项中对应的 key,支持 a.b.c 的嵌套写法 * @property {string} key - Vue 需要的 key,如果已经设置了唯一的 dataIndex,可以忽略这个属性 * @property {string} [width] - 列宽度 * @property {string} [minWidth] - 最小宽度,支持占满其它空位 与 onClick 冲突 v1.18 * @property {string} [align] - 对齐方式 * @property {string|Function} [tooltip] - 提示 v1.18 * @property {Function} [onClick] - 生成链接可点击,有 minWidth 冲突失效 v1.18 * @property {boolean|TableColumnEditable} [editable] - 是否可编辑,如果是对象,则为编辑配置,为 true 时 `editable.type = 'input'` * @property {boolean} [ellipsis] - 是否自动缩略 * @property {boolean} [fixed] - 列是否固定,可选 true(等效于 left) 'left' 'right' * @property {boolean|SearchItemConfig} [filterable] - 是否可过滤,如果是对象,则为过滤配置,为 true 时 `filterable.type = 'input'` * @property {boolean} [isOnlyForQuery] - 是否只用于搜索 */ /** * @typedef {Object} TableColumnEditable * @property {string} [type] - 编辑类型, 支持 input(默认), number, select, switch * @property {string} [key] - 字段 key, 默认与 dataIndex 一致 * @property {array} [options] - 选项,当 type 为 select 时必填,如果 type 为 switch, options[0] 为 checked, options[1] 为 unchecked * @property {Function} [beforeCellSubmit] - 单元格编辑提交的前置方法, 返回一个 Promise * @property {*} [defaultValue] 默认编辑时的值 * @property {Object} [defaultProps] - 编辑项配置 * @property {Object} [defaultSlots] - 编辑项 Slot */ /** * 表格列定义, [TableColumnConfig ](#tablecolunmconfig-配置) */ columns: { type: [Array, Function], default: () => [] }, /** * 选择功能的配置 */ rowSelection: { type: [Boolean, Object], default: () => null }, /** * 数据值需要指定 key 值,当启用 `rowSelection` 时一定要指定这个 key */ rowKey: { type: [String, Function], default: "id" }, /** * 表格数据 */ dataSource: { type: Array, default: () => [] }, /** * 表格数据, alias of `dataSource` */ formData: { type: Array, default: () => [] }, /** * 额外的展开行 */ expandRender: { type: [String, Function], default: null }, /** * 展开的行 */ expandedRowKeys: { type: Array, default: () => null }, /** * 是否显示刷新按钮 */ showRefresh: { type: Boolean, default: true }, /** * 持久化,传入 localStorage 的 key,如果为 true, 将会以 URL Hash 为 key */ persistence: { type: [Boolean, String], default: false }, /** * v1.18 * 是否用 `Card` 包裹 */ cardWrapper: { type: Boolean, default: true }, }, emits: [ /** * 未传入 `url` 时的手动请求方法 */ "fetch", /** * 行点击 v1.18 */ "rowClick", /** * 单元格编辑 v1.18 */ "cellSubmit", ], setup(props, { emit, slots, expose }) { const searchRef = ref() const footerElemRef = ref() const tableProvider = inject(NEWBIE_TABLE, () => {}) const genPersistenceKey = (prefix) => { if (!props.persistence) { return null } prefix = prefix || "" if (isBoolean(props.persistence)) { return `newbieTable_${prefix}` + useSm3(location.href) } return `newbieTable_${prefix}` + useSm3(location.pathname + "_" + props.persistence) } let persistencePagination = props.persistence ? useCache(genPersistenceKey()).get({}) : {} const state = reactive({ customColumns: [], tableColumns: [], editingData: {}, //编辑中的单元格 editingSwitchData: {}, //可编辑的 Switch 的值, 因为 Switch 的逻辑与其它类型不同,不能复用editingData filterableColumns: [], // 可过滤的列 sortableColumns: [], // 可排序的列 temporary: {}, // 用于存放一些临时数据 tableLoading: { loading: false }, // 翻页Loading customColumnVisible: false, columnKeyModal: [], pagination: { // 翻页数据 totalSize: 0, currentPage: persistencePagination.currentPage || 1, pageSize: persistencePagination.pageSize || props.pagination?.pageSize || 10, }, id: "", tableSelection: [], // 给外部用 tableKeySelection: [], // 内部用 items: [], // 表格内容 columnKey: {}, // 自定义列 fetchQueue: [], // 请示队列,以最后那个为准 searchFormData: {}, // 搜索表单数据 }) /** * 持久化翻页与滚动 */ const onPersistence = () => { if (!props.persistence) { return } const data = { ...state.pagination, } useCache(genPersistenceKey()).set(data) } const onOpenCustomColumns = () => { // 复制一份 state.columnKeyModal = [] Object.keys(state.columnKey).forEach((key) => { if (state.columnKey[key]) { state.columnKeyModal.push(key) } }) state.customColumnVisible = true } const onCustomClearAll = () => { state.columnKeyModal = [] } const onCustomSelectAll = () => { onCustomClearAll() Object.keys(state.columnKey).forEach((key) => { state.columnKeyModal.push(key) }) } const onSelectionChange = (selectedRowKeys, selectedRows) => { state.tableKeySelection = [].concat(selectedRowKeys) state.tableSelection = [].concat(selectedRows) } const onResizeColumn = (w, col) => { col.width = w } const onCustomSubmit = () => { Object.keys(state.columnKey).forEach((key) => { state.columnKey[key] = state.columnKeyModal.indexOf(key) > -1 }) state.customColumnVisible = false } const getQueryData = () => { let params = { ...state.searchFormData, ...props.extraData } if (props.pagination) { if (state.pagination.pageSize) { params[tableProvider.pageSizeKey] = state.pagination.pageSize } params[tableProvider.pageKey] = state.pagination.currentPage } return params } const setQueryData = (fields) => { if (searchRef.value) { searchRef.value?.setQueryForm(fields) } } const fetchItems = async () => { let data = {}, params = getQueryData() const type = new Date().getTime() state.fetchQueue.push(type) const method = props.method if (method === "get") { data = { params } } else if (method === "post") { data = { ...params } } const res = await useFetch(state.tableLoading)[method](props.url, data) useProcessStatusSuccess(res, () => { // 根据队列决定是否处理数据 if (!state.fetchQueue.length) { return } if (state.fetchQueue[state.fetchQueue.length - 1] === type) { state.fetchQueue = [] } state.tableKeySelection = [] state.tableSelection = [] const fetched = props.afterFetched || tableProvider.afterFetched const result = fetched(res) if (props.pagination) { state.pagination.totalSize = result.totalSize } state.items = result.items }) } /** * 执行获取数据 * @param {boolean} refresh 是否刷新 * @return {*} */ const doFetch = async (refresh) => { if (refresh === true) { state.pagination.currentPage = 1 } if (props.url) { await fetchItems() } else { emit("fetch") } onPersistence() } const onSearch = (searchData) => { state.searchFormData = searchData doFetch(!searchData.persistence) } const getKey = (item) => { if (item.key) { return item.key } if (isArray(item.dataIndex)) { return item.dataIndex.join(".") } return item.dataIndex } const tidyColumnsRecursion = (columns) => { const result = [] if (columns && columns.length) { columns.forEach((column) => { const item = cloneDeep(column) const key = getKey(item) if (key && isUndefined(state.columnKey[key])) { state.columnKey[key] = true } if (!item.children) { if ((key && state.columnKey[key]) || !key) { result.push({ ...item, resizable: true }) } } else { const res = tidyColumnsRecursion([].concat(item.children)) if (res.length) { item.children = res if ((key && state.columnKey[key]) || !key) { result.push(item) } } } }) } return result } /** * 用于获取自定义列 * @param columns * @returns {*[]} */ const tidyCustomColumnsRecursion = (columns) => { let result = [] if (columns && columns.length) { columns.forEach((column) => { const item = cloneDeep(column) item.checkKey = getKey(item) if (!item.children) { result.push(item) } else { const res = tidyCustomColumnsRecursion(item.children) delete item.children result.push(item) result = result.concat(res) } }) } return result } const totalColumns = computed(() => { let columns if (isFunction(props.columns)) { columns = props.columns() } else if (props.columns.length) { columns = props.columns } else { columns = props.tableProps.columns } return columns.map((item) => { if (item.minWidth) { item.RC_TABLE_INTERNAL_COL_DEFINE = { style: { "min-width": `${item.minWidth}px`, }, } item.width = item.minWidth } item.key = getKey(item) return item }) }) const prepareFormColumns = () => { const originColumns = totalColumns.value.filter((item) => { return !item.isOnlyForQuery }) state.customColumns = tidyCustomColumnsRecursion([].concat(originColumns)) // 显示列表数组 state.tableColumns = tidyColumnsRecursion([].concat(originColumns)) } const prepareSearchColumns = () => { state.filterableColumns = totalColumns.value .filter((item) => { return item.filterable || item.isOnlyForQuery }) .map((item) => { return { title: item.title, key: item.dataIndex || item.key, options: item.options, ...item.filterable, } }) state.sortableColumns = totalColumns.value .filter((item) => item.sortable) .map((item) => { let column = { title: item.title, key: item.dataIndex || item.key, } if (item.sortable === "asc" || item.sortable === "ASC") { column.direction = "asc" } else if (item.sortable === "desc" || item.sortable === "DESC") { column.direction = "desc" } else { column = { ...column, ...item.sortable } } return column }) } watch( () => [totalColumns, state.columnKey], (newValues, preValues) => { prepareFormColumns() //只有 Columns 确实发生变化了才重新渲染 Search,否则会导致 Search 的值丢失 if ( !preValues || !isEqual(newValues[0].value.map((item) => getKey(item)).sort(), preValues[0].value.map((item) => getKey(item)).sort()) ) { prepareSearchColumns() } }, { immediate: true, deep: true }, ) //为了避免出现 Append 在一开始覆盖 Table 内容的情况,在 doFetch 后 Pagination 确定下来再添加兼容样式 watch( () => state.pagination, (value) => { if (value) { footerElemRef.value?.classList.add("pagination-adapt") } }, { deep: true }, ) watch( () => props.formData, (newValue, oldValue) => { if (!isEqual(newValue, oldValue)) { state.items = newValue } }, ) /** * 第一次拿数据 */ const initFetch = () => { if (props.url) { //如果有默认搜索条件先拿出来 state.searchFormData = searchRef.value?.getSearch() nextTick(() => fetchItems()) } else { if (props.formData.length) { state.items = props.formData } if (props.dataSource.length) { state.items = props.dataSource } emit("fetch") } } onMounted(() => { //1. 因为手动 fetch 需要调用 $ref 的 getQueryForm 等方法,所以需要 mounted 后才执行 fetch //2. 当有持久化时需要等待 Search 和 Table 的持久化数据加载完成后再进行数据获取,放在 Search 初始化后中处理 if (!props.persistence) { initFetch() } }) /** * 获取当前页的数据 * @returns {*[]} */ const getData = () => { return [].concat(state.items) } /** * 设置数据 * @param items */ const setData = (items) => { state.items = items } /** * 设置翻页数据 * @param {int} total * @param {int} currentPage * @param {int} pageSize */ const setPagination = (total, currentPage, pageSize) => { if (!isUndefined(total)) { state.pagination.totalSize = total } if (!isUndefined(currentPage)) { state.pagination.currentPage = currentPage } if (!isUndefined(pageSize)) { state.pagination.pageSize = pageSize } } /** * 获取翻页数据 * @returns {*} */ const getPagination = () => state.pagination /** * 获取已选择的行数据 * @returns {*[]} */ const getSelection = () => [].concat(state.tableSelection) /** * 设置表格 loading 状态 * @param {boolean} loading */ const setTableLoading = (loading) => (state.tableLoading.loading = loading) /********** exposes **********/ expose({ getData, setData, setPagination, getPagination, getSelection, getQueryData, setQueryData, doFetch, setTableLoading, }) /********** render **********/ const titleElem = () => { if (slots.title) { return <div class={"newbie-table-title-wrapper"}>{slots.title()}</div> } else if (props.title) { return <div class={"newbie-table-title-wrapper"}>{props.title}</div> } return null } const prependElem = () => (slots.prepend ? <div class={"newbie-table-prepend-wrapper"}>{slots.prepend()}</div> : null) const filterElem = () => props.filterable ? [ <NewbieSearch ref={searchRef} persistence={props.persistence} filterableColumns={state.filterableColumns} sortableColumns={state.sortableColumns} onSearch={onSearch} ></NewbieSearch>, <Divider></Divider>, ] : null const functionalElem = () => ( <div class={"newbie-table-functional-wrapper"}> <div class={"newbie-table-functional"}>{slots.functional ? slots.functional() : null}</div> <div class={"newbie-table-functional-default"}> {props.filterable ? ( <Button type={"link"} style={{ marginRight: "5px" }} onClick={onOpenCustomColumns}> {() => useT("table.custom")} </Button> ) : null} {props.showRefresh ? ( <Button class={"newbie-table-refresh"} icon={<SyncOutlined />} onClick={() => doFetch(false)}></Button> ) : null} </div> </div> ) const handleTable = () => { return { bordered: true, size: "middle", scroll: { y: 570, x: 1000, scrollToFirstRowOnChange: true, }, rowClassName: (_record, index) => (index % 2 === 1 ? "newbie-table-striped" : null), expandedRowRender: props.expandRender, expandedRowKeys: props.expandedRowKeys, rowKey: props.rowKey, customRow: (record, index) => ({ onClick: (event) => { const isEditing = state.editingData[record[props.rowKey]] if (isEditing) { //如果在编辑就不触发 rowClick, 否则在输入等情况下 Click 操作都会触发,不好避免 return } emit("rowClick", { record, index, event }) }, }), ...props.tableProps, } } const handlePagination = () => { let pagination = !props.pagination ? false : isObject(props.pagination) ? cloneDeep(props.pagination) : {} if (isObject(pagination)) { pagination = { size: "small", showQuickJumper: true, showSizeChanger: true, showTotal: (total) => useT("table.total", { total }), pageSizeOptions: ["10", "30", "50", "100"], total: state.pagination.totalSize, pageSize: state.pagination.pageSize, current: state.pagination.currentPage, "onUpdate:current": (value) => { state.pagination.currentPage = value }, "onUpdate:pageSize": (value) => { state.pagination.pageSize = value }, onChange: () => { doFetch(false) }, showSizeChange: () => { doFetch(true) }, ...pagination, } } return pagination } const handleRowSelection = () => { let rowSelection = !props.rowSelection ? null : isObject(props.rowSelection) ? cloneDeep(props.rowSelection) : {} if (isObject(rowSelection)) { rowSelection = { selectedRowKeys: state.tableKeySelection, preserveSelectedRowKeys: true, columnWidth: 40, fixed: true, onChange: onSelectionChange, } } return rowSelection } const onEditTableCell = (editable, record, key, value) => { if (state.editingData[record[props.rowKey]]) { state.editingData[record[props.rowKey]][key] = value } else { state.editingData[record[props.rowKey]] = { [key]: value } } } const onCancelEditTableCell = (record, key) => { if (state.editingData[record[props.rowKey]]) { delete state.editingData[record[props.rowKey]][key] } } const onSubmitTableCell = async (editable, record, key) => { if (!props.submitUrl && !props.beforeCellSubmit) { return } let value = null if (editable.type === "switch") { value = state.editingSwitchData[record[props.rowKey]][key] } else { value = state.editingData[record[props.rowKey]][key] } const data = { [props.rowKey]: record[props.rowKey], [key]: value, } onCancelEditTableCell(record, key) if (editable.beforeCellSubmit) { state.tableLoading.loading = true await editable.beforeCellSubmit(data) state.tableLoading.loading = false } else if (props.beforeCellSubmit) { state.tableLoading.loading = true await props.beforeCellSubmit(data) state.tableLoading.loading = false } else { const res = await useFetch(state.tableLoading).post(props.submitUrl, data) useProcessStatusSuccess(res, () => { message.success(useT("common.save-success")) emit("cellSubmit", { record, key, value }) }) } } const handleEditableCell = (row) => { const { column, text, record } = row let editable = column.editable if (column.editable === true) { editable = { type: "input" } } const editKey = editable.key || column.key const value = isUndefined(editable.defaultValue) ? text : editable.defaultValue const isEditing = state.editingData[record[props.rowKey]] && !isUndefined(state.editingData[record[props.rowKey]][editKey]) const editIconElem = isEditing ? ( <span class={"table-editable-icon editing"}> <CheckCircleOutlined class={"check-icon"} onClick={withModifiers(() => onSubmitTableCell(editable, record, editKey), ["stop"])} /> <CloseCircleOutlined class={"close-icon"} onClick={withModifiers(() => onCancelEditTableCell(record, editKey), ["stop"])} /> </span> ) : ( <span class={"table-editable-icon"}> <EditOutlined class={"edit-icon"} onClick={withModifiers(() => onEditTableCell(editable, record, editKey, value), ["stop"])} /> </span> ) const editElems = () => { if (editable.type === "input") { return ( <Input v-model:value={state.editingData[record[props.rowKey]][editKey]} style={{ width: "100%" }} {...editable.defaultProps}> {{ ...editable.defaultSlots }} </Input> ) } if (editable.type === "number") { return ( <InputNumber v-model:value={state.editingData[record[props.rowKey]][editKey]} style={{ width: "100%" }} {...editable.defaultProps} > {{ ...editable.defaultSlots }} </InputNumber> ) } if (editable.type === "select") { return ( <Select v-model:value={state.editingData[record[props.rowKey]][editKey]} options={editable.options} style={{ width: "100%" }} {...editable.defaultProps} > {{ ...editable.defaultSlots }} </Select> ) } } if (editable.type === "switch") { let optionElements = { checkedChildren: () => editable.options?.[0] || null, unCheckedChildren: () => editable.options?.[1] || null, } if (isUndefined(state.editingSwitchData[record[props.rowKey]])) { state.editingSwitchData[record[props.rowKey]] = {} } if (isUndefined(state.editingSwitchData[record[props.rowKey]][editKey])) { state.editingSwitchData[record[props.rowKey]][editKey] = value } return ( <div class={"table-editable-wrapper"}> <div class={"table-editable-text"}> <Switch v-model:checked={state.editingSwitchData[record[props.rowKey]][editKey]} onClick={(checked, e) => e.stopPropagation()} onChange={(checked, e) => { e.stopPropagation() onSubmitTableCell(editable, record, editKey) }} {...editable.defaultProps} > {optionElements} </Switch> </div> </div> ) } else { return ( <div class={"table-editable-wrapper"}> <div class={"table-editable-text"}>{isEditing ? editElems() : text}</div> {editIconElem} </div> ) } } const tableElem = () => { return ( <Table rowSelection={handleRowSelection()} pagination={handlePagination()} loading={state.tableLoading.loading} dataSource={state.items} columns={state.tableColumns} onResizeColumn={onResizeColumn} {...handleTable()} > {{ headerCell: ({ column }) => { const titleElem = [ <span>{column.title}</span>, column.tooltip ? ( isFunction(column.tooltip) ? ( <Tooltip> {{ default: () => <InfoCircleOutlined style={{ marginLeft: "5px" }} />, title: column.tooltip, }} </Tooltip> ) : ( <Tooltip title={column.tooltip}>{() => <InfoCircleOutlined style={{ marginLeft: "5px" }} />}</Tooltip> ) ) : null, ] const funcElems = [] if (props.editable && column.editable) { funcElems.push(<span class={"newbie-table-header-func"}>{useT("table.editable")}</span>) } return ( <div class={`newbie-table-header-wrapper ${funcElems?.length ? "with-func" : ""}`}> <div class={"newbie-table-header-title-wrapper"}>{titleElem}</div> <div class={"newbie-table-header-func-wrapper"}>{funcElems}</div> </div> ) }, bodyCell: ({ column, text, record, index }) => { if (props.editable && column.editable) { return handleEditableCell({ column, text, record, index }) } if (column.customRender) { return column.customRender({ column, text, record, index }) } if (column.onClick) { return ( <a href="javascript:void(0);" onClick={withModifiers( () => column.onClick({ column, text, record, index, }), ["stop"], )} > {text} </a> ) } return <span>{text}</span> }, ...props.tableSlots, }} </Table> ) } const footerElem = () => slots.append || props.pagination ? ( <div ref={footerElemRef} class={`newbie-table-footer`}> <div class={"newbie-table-append-wrapper"}>{slots.append ? slots.append() : null}</div> <div class={"newbie-table-pagination-wrapper"}></div> </div> ) : null return () => ( <div class={"newbie-table"}> {props.cardWrapper ? ( <Card size={"small"} class={"newbie-table-card"}> {{ title: () => titleElem(), extra: () => (slots.extra ? slots.extra() : null), default: () => [prependElem(), filterElem(), functionalElem(), tableElem(), footerElem()], }} </Card> ) : ( [prependElem(), filterElem(), functionalElem(), tableElem(), footerElem()] )} <Modal v-model:open={state.customColumnVisible} title={useT("table.custom")} width={"900px"} onOk={onCustomSubmit}> {() => [ <CheckboxGroup v-model:value={state.columnKeyModal} style={{ width: "100%" }}> {() => ( <Row gutter={15}> {() => state.customColumns.map((item) => ( <Col span={6}> {() => ( <Checkbox style={{ width: "100%", marginBottom: "5px", overflow: "hidden" }} value={item.checkKey} > {() => item.title} </Checkbox> )} </Col> )) } </Row> )} </CheckboxGroup>, <Divider></Divider>, <Button onClick={onCustomSelectAll}>{() => useT("table.select-all")}</Button>, <Button onClick={onCustomClearAll} style={{ marginLeft: "5px" }}> {() => useT("table.unselect-all")} </Button>, ]} </Modal> </div> ) }, }) /** * * 生成表格操作按钮 * @param {array} actions * action : * { * type: 'Button', * name: '点击' * props: {type: 'primary', icon: h(EditOutlined)}, * action: function(){}, * } */ export function useTableActions(actions) { if (!actions) return null if (!isArray(actions)) { actions = [actions] } const actionElems = actions.map((action) => { let props = { ...action.props } let { type } = action const { name } = action let ActionComponent = null if (!type || type === "button") { ActionComponent = NewbieButton props.type = props.type || "text" } // 专为tag做一些样式处理 if (type === "a-tag" || type === "tag") { ActionComponent = Tag } if (type === "switch") { ActionComponent = Switch if (action.name) { props.checkedChildren = action.name?.[0] || "" props.unCheckedChildren = action.name?.[1] || "" } props.checked = action.value props.disabled = true } if (action.action) { props.onClick = withModifiers(() => action.action(), ["stop"]) } if (action.tooltip) { const title = action.tooltip return ( <Tooltip title={title} transfer={true}> {{ default: () => <ActionComponent {...props}>{() => name}</ActionComponent>, }} </Tooltip> ) } if (action.children?.length) { const menuItems = action.children return ( <Dropdown placement={"bottom"} onClick={withModifiers(() => {}, ["stop"])}> {{ default: () => ( <NewbieButton type={"text"} size={"small"} {...props}> {() => [<DownOutlined style={{ fontSize: "12px" }}></DownOutlined>, name]} </NewbieButton> ), overlay: () => ( <Menu> {() => menuItems.map((item) => ( <MenuItem> {() => ( <NewbieButton type={"text"} style={{ textAlign: "left" }} buttonProps={{ block: true }} onClick={withModifiers(() => item.action(), ["stop"])} {...item.props} > {() => item.name} </NewbieButton> )} </MenuItem> )) } </Menu> ), }} </Dropdown> ) } return <ActionComponent {...props}>{{ default: () => name }}</ActionComponent> }) return <div class={"table-actions-wrapper"}>{actionElems}</div> } /** * 生成表格图片预览 * @param {string | array} images * @param {object} [DefaultIcon] * @returns {JSX.Element|unknown[]} */ export function useTableImage(images, DefaultIcon) { if (!images) { return <span style={{ fontSize: "30px" }}>{DefaultIcon ? <DefaultIcon /> : <PictureOutlined />}</span> } if (!isArray(images)) { images = [images] } return ( <Flex gap={"small"}> {() => images.map((img) => { const src = img.thumbUrl || img.url || img return <Image src={src} preview={{ src: img.url || img.thumbUrl || img }} width={48} height={48}></Image> }) } </Flex> ) } /** * 生成附件链接 * @todo i18n * @param {string | array} files * @param {object} [options] * @returns {JSX.Element|unknown[]} */ export function useTableFile(files, options) { const { DefaultIcon, placeholder = "暂无内容", fileName } = options || {} if (!files) { return <span>{placeholder}</span> } if (!isArray(files)) { files = [files] } return files.map((file, index) => { const src = file.url || file const name = fileName || file.name || (files.length > 1 ? `查看附件${index + 1}` : "查看附件") return ( <a href={src} style={{ display: "block" }} target="_blank"> <span style={{ fontSize: "14px" }}> {DefaultIcon ? <DefaultIcon /> : <FilePdfOutlined />} {name} </span> </a> ) }) }