1122
This commit is contained in:
293
cdTMP/src/components/ma-crud/components/column.vue
Normal file
293
cdTMP/src/components/ma-crud/components/column.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<template v-for="row in props.columns" :key="row[options.pk]">
|
||||
<template v-if="!row.hide">
|
||||
<a-table-column
|
||||
:title="row.title"
|
||||
:width="row.width"
|
||||
:ellipsis="row.ellipsis ?? true"
|
||||
:filterable="row.filterable"
|
||||
:cell-class="row.cellClass"
|
||||
:header-cell-class="row.headerCellClass"
|
||||
:body-cell-class="row.bodyCellClass"
|
||||
:summary-cell-class="row.summaryCellClass"
|
||||
:cell-style="row.cellStyle"
|
||||
:header-cell-style="row.headerCellStyle"
|
||||
:body-cell-style="row.bodyCellStyle"
|
||||
:summary-cell-style="row.summaryCellStyle"
|
||||
:tooltip="row.tooltip ?? true"
|
||||
:align="row.align || 'left'"
|
||||
:fixed="row.fixed"
|
||||
v-if="row.children && row.children.length > 0"
|
||||
>
|
||||
<column
|
||||
@refresh="() => refresh()"
|
||||
:isRecovery="props.isRecovery"
|
||||
:crudFormRef="props.crudFormRef"
|
||||
:columns="row.children"
|
||||
>
|
||||
<template
|
||||
v-for="(childRow, childIndex) in row.children"
|
||||
:key="childIndex"
|
||||
#[childRow.dataIndex]="{ record, column, rowIndex }"
|
||||
>
|
||||
<slot :name="`${childRow.dataIndex}`" v-bind="{ record, column, rowIndex }" />
|
||||
</template>
|
||||
</column>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="row.title"
|
||||
:data-index="row.dataIndex"
|
||||
:width="row.width"
|
||||
:ellipsis="row.ellipsis ?? true"
|
||||
:filterable="row.filterable"
|
||||
:cell-class="row.cellClass"
|
||||
:header-cell-class="row.headerCellClass"
|
||||
:body-cell-class="row.bodyCellClass"
|
||||
:summary-cell-class="row.summaryCellClass"
|
||||
:cell-style="row.cellStyle"
|
||||
:header-cell-style="row.headerCellStyle"
|
||||
:body-cell-style="row.bodyCellStyle"
|
||||
:summary-cell-style="row.summaryCellStyle"
|
||||
:tooltip="row.dataIndex === '__operation' ? false : row.tooltip ?? true"
|
||||
:align="row.align || 'left'"
|
||||
:fixed="row.fixed"
|
||||
:sortable="row.sortable"
|
||||
v-else
|
||||
>
|
||||
<template #cell="{ record, column, rowIndex }">
|
||||
<!-- 操作栏 -->
|
||||
<template v-if="row.dataIndex === '__operation'">
|
||||
<a-scrollbar type="track" style="overflow: auto">
|
||||
<a-space size="mini">
|
||||
<slot name="operationBeforeExtend" v-bind="{ record, column, rowIndex }"></slot>
|
||||
<slot name="operationCell" v-bind="{ record, column, rowIndex }">
|
||||
<!-- <a-link
|
||||
v-if="
|
||||
options.see.show
|
||||
&& ($common.auth(options.see.auth || [])
|
||||
|| (options.see.role || []))
|
||||
"
|
||||
type="primary"
|
||||
><icon-eye /> {{ options.see.text || '查看' }}</a-link> -->
|
||||
<a-link
|
||||
v-if="
|
||||
(isFunction(options.edit.show)
|
||||
? options.edit.show(record)
|
||||
: options.edit.show) && !props.isRecovery
|
||||
"
|
||||
type="primary"
|
||||
@click="editAction(record)"
|
||||
>
|
||||
<icon-edit /> {{ options.edit.text || "编辑" }}
|
||||
</a-link>
|
||||
|
||||
<a-popconfirm
|
||||
content="确定要恢复该数据吗?"
|
||||
position="bottom"
|
||||
@ok="recoveryAction(record)"
|
||||
v-if="
|
||||
(isFunction(options.recovery.show)
|
||||
? options.recovery.show(record)
|
||||
: options.recovery.show) && props.isRecovery
|
||||
"
|
||||
>
|
||||
<a-link type="primary"
|
||||
><icon-undo /> {{ options.recovery.text || "恢复" }}
|
||||
</a-link>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-popconfirm
|
||||
content="确定要删除该数据吗?"
|
||||
position="bottom"
|
||||
@ok="deleteAction(record)"
|
||||
v-if="
|
||||
isFunction(options.delete.show)
|
||||
? options.delete.show(record)
|
||||
: options.delete.show
|
||||
"
|
||||
>
|
||||
<a-link
|
||||
type="primary"
|
||||
>
|
||||
<icon-delete />
|
||||
{{
|
||||
props.isRecovery
|
||||
? options.delete.realText || "删除"
|
||||
: options.delete.text || "删除"
|
||||
}}
|
||||
</a-link>
|
||||
</a-popconfirm>
|
||||
</slot>
|
||||
<slot name="operationAfterExtend" v-bind="{ record, column, rowIndex }"></slot>
|
||||
</a-space>
|
||||
</a-scrollbar>
|
||||
</template>
|
||||
<template v-else-if="row.customRender">
|
||||
<custom-render
|
||||
:column="column"
|
||||
:record="record"
|
||||
:render="row.customRender"
|
||||
:rowIndex="rowIndex"
|
||||
></custom-render>
|
||||
</template>
|
||||
<slot :name="row.dataIndex" v-bind="{ record, column, rowIndex }" v-else>
|
||||
<template v-if="row.dataIndex === '__index'">{{ getIndex(rowIndex) }}</template>
|
||||
|
||||
<template v-if="row.dict && row.dict.translation">
|
||||
<template v-if="isArray(get(record, row.dataIndex))">
|
||||
<a-tag v-for="item in get(record, row.dataIndex)" class="ml-1">{{
|
||||
getDataIndex(row, item)
|
||||
}}</a-tag>
|
||||
</template>
|
||||
<a-tag v-else-if="row.dict.tagColors" :color="getTagColor(row, record)">
|
||||
{{ getDataIndex(row, record) }}
|
||||
</a-tag>
|
||||
<a-tag v-else-if="row.dict.tagColor" :color="row.dict.tagColor">{{
|
||||
getDataIndex(row, record)
|
||||
}}</a-tag>
|
||||
<span v-else>{{ getDataIndex(row, record) }}</span>
|
||||
</template>
|
||||
<template v-else-if="row.dataIndex && row.dataIndex.indexOf('.') !== -1">
|
||||
{{ get(record, row.dataIndex) }}
|
||||
</template>
|
||||
<template v-else-if="row.formType === 'upload'">
|
||||
<a-link @click="imageSee(row, record, row.dataIndex)"><icon-image /> 查看图片</a-link>
|
||||
</template>
|
||||
<template v-else>{{ record[row.dataIndex] }}</template>
|
||||
</slot>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject } from "vue"
|
||||
import config from "@/config/crud"
|
||||
import uploadConfig from "@/config/upload"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { isFunction, get, isArray, isObject } from "lodash"
|
||||
import CustomRender from "../js/custom-render"
|
||||
import tool from "@/utils/tool"
|
||||
import commonApi from "@/api/common"
|
||||
|
||||
const emit = defineEmits(["refresh", "showImage"])
|
||||
const props = defineProps({
|
||||
columns: Array,
|
||||
isRecovery: Boolean,
|
||||
crudFormRef: Object
|
||||
})
|
||||
|
||||
const options = inject("options")
|
||||
const requestParams = inject("requestParams")
|
||||
const dictTrans = inject("dictTrans")
|
||||
const dictColors = inject("dictColors")
|
||||
|
||||
const imageSee = async (row, record, dataIndex) => {
|
||||
if (row.returnType) {
|
||||
if (row.returnType === "url") {
|
||||
emit("showImage", record[dataIndex])
|
||||
return
|
||||
}
|
||||
|
||||
if (!["id", "hash"].includes(row.returnType)) {
|
||||
Message.info("该图片无法查看")
|
||||
return
|
||||
}
|
||||
Message.info("获取图片中,请稍等...")
|
||||
const res =
|
||||
row.returnType === "id"
|
||||
? await commonApi.getFileInfoById({ id: record.id })
|
||||
: await commonApi.getFileInfoByHash({ hash: record.hash })
|
||||
const result = res?.success ?? false
|
||||
if (!result) {
|
||||
Message.info("图片信息无法获取")
|
||||
return
|
||||
}
|
||||
|
||||
const isImage = res.data.mime_type.indexOf("image") > -1
|
||||
result &&
|
||||
emit(
|
||||
"showImage",
|
||||
isImage
|
||||
? tool.attachUrl(res.data.url, uploadConfig.storageMode[res.data.storage_mode])
|
||||
: "not-image.png"
|
||||
)
|
||||
} else {
|
||||
if (!record[row.dataIndex]) {
|
||||
Message.info("无图片")
|
||||
return
|
||||
}
|
||||
emit("showImage", record[row.dataIndex] ?? "not-image.png")
|
||||
}
|
||||
}
|
||||
|
||||
const getTagColor = (row, record) => {
|
||||
return dictColors(
|
||||
row.dataIndex,
|
||||
row.dataIndex.indexOf(".") > -1 ? get(record, row.dataIndex) : record[row.dataIndex]
|
||||
)
|
||||
}
|
||||
|
||||
const getDataIndex = (row, record) => {
|
||||
if (isObject(record)) {
|
||||
return dictTrans(
|
||||
row.dataIndex,
|
||||
row.dataIndex.indexOf(".") > -1 ? get(record, row.dataIndex) : record[row.dataIndex]
|
||||
)
|
||||
} else {
|
||||
return dictTrans(row.dataIndex, record)
|
||||
}
|
||||
}
|
||||
|
||||
const getIndex = (rowIndex) => {
|
||||
const index = rowIndex + 1
|
||||
if (requestParams[config.request.page] === 1) {
|
||||
return index
|
||||
} else {
|
||||
return (requestParams[config.request.page] - 1) * requestParams[config.request.pageSize] + index
|
||||
}
|
||||
}
|
||||
|
||||
const editAction = (record) => {
|
||||
isFunction(options.beforeOpenEdit) && options.beforeOpenEdit(record)
|
||||
if (options.edit.action && isFunction(options.edit.action)) {
|
||||
options.edit.action(record)
|
||||
} else {
|
||||
props.crudFormRef.edit(record)
|
||||
}
|
||||
}
|
||||
|
||||
const recoveryAction = async (record) => {
|
||||
const response = await options.recovery.api({ ids: [record[options.pk]] })
|
||||
response.success && Message.success(response.message || `恢复成功!`)
|
||||
emit("refresh")
|
||||
}
|
||||
|
||||
const deleteAction = async (record) => {
|
||||
let data = {}
|
||||
if (isFunction(options.beforeDelete) && !(data = options.beforeDelete([record[options.pk]]))) {
|
||||
return false
|
||||
}
|
||||
const api = props.isRecovery ? options.delete.realApi : options.delete.api
|
||||
const response = await api(Object.assign({ ids: [record[options.pk]] }, data))
|
||||
if (options.afterDelete && isFunction(options.afterDelete)) {
|
||||
options.afterDelete(response, record)
|
||||
}
|
||||
response.success && Message.success(response.message || `删除成功!`)
|
||||
emit("refresh")
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
emit("refresh")
|
||||
}
|
||||
|
||||
defineExpose({ deleteAction, recoveryAction })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.arco-image-img) {
|
||||
object-fit: contain;
|
||||
background-color: var(--color-fill-4);
|
||||
}
|
||||
</style>
|
||||
157
cdTMP/src/components/ma-crud/components/contextMenu.vue
Normal file
157
cdTMP/src/components/ma-crud/components/contextMenu.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<ul
|
||||
class="ma-crud-contextmenu shadow-lg"
|
||||
v-show="crudContextMenuVisible"
|
||||
:style="{ left: left + 'px', top: top + 'px', height: 'auto' }"
|
||||
>
|
||||
<template v-for="item in options?.contextMenu?.items ?? []">
|
||||
<li v-if="item.operation === 'divider'"><a-divider margin="8px" /></li>
|
||||
<li v-if="item.operation === 'print'">
|
||||
<div class="context-menu-item" @click="execCommand(item)">
|
||||
<icon-printer /> <span class="ml-2">{{ item.text ?? "打印表格" }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="item.operation === 'refresh'">
|
||||
<div class="context-menu-item" @click="execCommand(item)">
|
||||
<icon-refresh /> <span class="ml-2">{{ item.text ?? "刷新表格" }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="item.operation === 'add' && showVerify('add')">
|
||||
<div class="context-menu-item" @click="execCommand(item)">
|
||||
<icon-plus /> <span class="ml-2">{{ item.text ?? "新增数据" }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="item.operation === 'edit' && showVerify('edit')">
|
||||
<div class="context-menu-item" @click="execCommand(item)">
|
||||
<icon-edit /> <span class="ml-2">{{ item.text ?? "编辑数据" }}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="item.operation === 'delete' && showVerify('delete')">
|
||||
<a-popconfirm content="确实要删除此数据吗?" @ok="execCommand(item)">
|
||||
<div class="context-menu-item">
|
||||
<icon-delete /> <span class="ml-2">{{ item.text ?? "删除数据" }}</span>
|
||||
</div>
|
||||
</a-popconfirm>
|
||||
</li>
|
||||
<li v-if="!defaultOperation.includes(item.operation)">
|
||||
<div class="context-menu-item" @click="execCommand(item)">
|
||||
<component v-if="item.icon" :is="getIcon(item)" />
|
||||
<span class="ml-2">{{ getText(item) }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, watch, nextTick } from "vue"
|
||||
import checkAuth from "@/directives/auth/auth"
|
||||
import { isArray } from "lodash"
|
||||
|
||||
const left = ref(0)
|
||||
const top = ref(0)
|
||||
const crudContextMenuVisible = ref(false)
|
||||
const currentRow = ref()
|
||||
const emit = defineEmits(["execCommand"])
|
||||
|
||||
const options = inject("options")
|
||||
const isRecovery = inject("isRecovery")
|
||||
|
||||
const defaultOperation = ["refresh", "print", "edit", "divider", "delete", "add", "nextPage", "prevPage"]
|
||||
|
||||
const showVerify = (type) => {
|
||||
if (!options[type].show) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isRecovery.value === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
const authList = options[type].auth
|
||||
if (isArray(authList)) {
|
||||
for (let index in authList) {
|
||||
if (!checkAuth(authList[index])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
watch(
|
||||
() => crudContextMenuVisible.value,
|
||||
(value) => {
|
||||
const crudContextMenuhandler = (e) => {
|
||||
const dom = document.querySelector(".ma-crud-contextmenu")
|
||||
if (dom && !dom.contains(e.target)) {
|
||||
closeCrudcontextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
? document.body.addEventListener("click", (e) => crudContextMenuhandler(e))
|
||||
: document.body.removeEventListener("click", (e) => crudContextMenuhandler(e))
|
||||
}
|
||||
)
|
||||
|
||||
const openContextMenu = async (ev, record) => {
|
||||
crudContextMenuVisible.value = true
|
||||
currentRow.value = record
|
||||
await nextTick(() => {
|
||||
const domHeight = document.querySelector(".ma-crud-contextmenu").offsetHeight
|
||||
if (document.body.offsetHeight - ev.pageY < domHeight) {
|
||||
top.value = ev.clientY - domHeight
|
||||
} else {
|
||||
top.value = ev.clientY
|
||||
}
|
||||
left.value = ev.clientX
|
||||
})
|
||||
}
|
||||
|
||||
const closeCrudcontextMenu = () => {
|
||||
currentRow.value = null
|
||||
crudContextMenuVisible.value = false
|
||||
}
|
||||
|
||||
const getIcon = (item) => {
|
||||
if (!defaultOperation.includes(item.operation)) {
|
||||
return item.icon
|
||||
}
|
||||
}
|
||||
|
||||
const getText = (item) => {
|
||||
if (!defaultOperation.includes(item.operation)) {
|
||||
return item.text
|
||||
}
|
||||
}
|
||||
|
||||
const execCommand = (item) => {
|
||||
emit("execCommand", { contextItem: item, record: currentRow.value })
|
||||
crudContextMenuVisible.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openContextMenu,
|
||||
closeCrudcontextMenu
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.ma-crud-contextmenu {
|
||||
position: fixed;
|
||||
min-width: 200px;
|
||||
z-index: 999;
|
||||
background: var(--color-bg-2);
|
||||
border: 1px solid var(--color-border-2);
|
||||
padding: 7px 0;
|
||||
border-radius: 4px;
|
||||
li .context-menu-item {
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
li:hover {
|
||||
background-color: var(--color-primary-light-1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
350
cdTMP/src/components/ma-crud/components/form.vue
Normal file
350
cdTMP/src/components/ma-crud/components/form.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<component
|
||||
:is="componentName"
|
||||
v-model:visible="dataVisible"
|
||||
:on-before-ok="submit"
|
||||
@cancel="close"
|
||||
ok-text="保存"
|
||||
cancel-text="关闭"
|
||||
draggable
|
||||
:width="options.formOption.width"
|
||||
:fullscreen="options.formOption.isFull || false"
|
||||
unmount-on-close
|
||||
>
|
||||
<template #title>{{ actionTitle }}</template>
|
||||
<a-spin :loading="dataLoading" tip="加载中..." class="w-full">
|
||||
<ma-form v-model="form" :columns="formColumns" :options="{ showButtons: false }" ref="maFormRef">
|
||||
<template v-for="slot in Object.keys($slots)" #[slot]="component">
|
||||
<slot :name="slot" v-bind="component" />
|
||||
</template>
|
||||
</ma-form>
|
||||
</a-spin>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, toRaw, getCurrentInstance, inject, provide } from "vue"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { containerItems } from "@cps/ma-form/js/utils"
|
||||
import { isArray, isFunction, get, cloneDeep, isUndefined } from "lodash"
|
||||
import { useRouter } from "vue-router"
|
||||
import tool from "@/utils/tool"
|
||||
import { useFormStore } from "@/store/index"
|
||||
|
||||
const formStore = useFormStore()
|
||||
const router = useRouter()
|
||||
const app = getCurrentInstance().appContext.app
|
||||
const maFormRef = ref()
|
||||
const componentName = ref("a-modal")
|
||||
const columns = inject("columns")
|
||||
const layoutColumns = ref(new Map())
|
||||
const options = inject("options")
|
||||
const formColumns = ref([])
|
||||
const currentAction = ref("")
|
||||
const dataVisible = ref(false)
|
||||
const form = ref({})
|
||||
const actionTitle = ref("")
|
||||
const dataLoading = ref(true)
|
||||
const emit = defineEmits(["success", "error"])
|
||||
|
||||
provide("form", toRaw(form))
|
||||
|
||||
const submit = async () => {
|
||||
const formData = maFormRef.value.getFormData()
|
||||
if (await maFormRef.value.validateForm()) {
|
||||
return false
|
||||
}
|
||||
|
||||
let response
|
||||
if (currentAction.value === "add") {
|
||||
isFunction(options.beforeAdd) && (await options.beforeAdd(formData))
|
||||
response = await options.add.api(formData)
|
||||
isFunction(options.afterAdd) && (await options.afterAdd(response, formData))
|
||||
} else {
|
||||
isFunction(options.beforeEdit) && (await options.beforeEdit(formData))
|
||||
response = await options.edit.api(formData[options.pk], formData)
|
||||
isFunction(options.afterEdit) && (await options.afterEdit(response, formData))
|
||||
}
|
||||
if (response.success) {
|
||||
Message.success(response.message || `${actionTitle.value}成功!`)
|
||||
emit("success", response)
|
||||
return true
|
||||
} else if (response.success === false && (typeof response.code === "undefined" || response.code !== 200)) {
|
||||
Message.error(response.message || `${actionTitle.value}失败!`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
const open = () => {
|
||||
formColumns.value = []
|
||||
layoutColumns.value = new Map()
|
||||
init()
|
||||
if (options.formOption.viewType === "tag") {
|
||||
if (!options.formOption.tagId) {
|
||||
Message.info("未配置 tagId")
|
||||
return
|
||||
}
|
||||
if (!options.formOption.tagName) {
|
||||
Message.info("未配置 tagName")
|
||||
return
|
||||
}
|
||||
const config = {
|
||||
options,
|
||||
formColumns: formColumns.value
|
||||
}
|
||||
|
||||
formStore.crudList[options.id] = false
|
||||
|
||||
const queryParams = {
|
||||
tagId: options.formOption.tagId,
|
||||
op: currentAction.value
|
||||
}
|
||||
|
||||
queryParams.key = form.value[options.formOption?.titleDataIndex ?? ""] ?? form.value[options.pk]
|
||||
|
||||
if (formStore.formList[options.formOption.tagId] === undefined) {
|
||||
formStore.formList[options.formOption.tagId] = {
|
||||
config,
|
||||
addData: {},
|
||||
editData: {}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentAction.value === "add") {
|
||||
formStore.formList[options.formOption.tagId].addData = cloneDeep(form.value)
|
||||
} else {
|
||||
formStore.formList[options.formOption.tagId].editData[queryParams.key] = cloneDeep(form.value)
|
||||
}
|
||||
|
||||
form.value = {}
|
||||
router.push(`/openForm/${options.formOption.tagId}` + tool.httpBuild(queryParams, true))
|
||||
} else {
|
||||
componentName.value = options.formOption.viewType === "drawer" ? "a-drawer" : "a-modal"
|
||||
dataVisible.value = true
|
||||
}
|
||||
}
|
||||
const close = () => {
|
||||
dataVisible.value = false
|
||||
formColumns.value = []
|
||||
form.value = {}
|
||||
}
|
||||
const add = () => {
|
||||
actionTitle.value = "新增"
|
||||
currentAction.value = "add"
|
||||
form.value = {}
|
||||
open()
|
||||
}
|
||||
const edit = (data) => {
|
||||
actionTitle.value = "编辑"
|
||||
currentAction.value = "edit"
|
||||
form.value = {}
|
||||
for (let i in data) form.value[i] = data[i]
|
||||
open(data)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
dataLoading.value = true
|
||||
const layout = JSON.parse(JSON.stringify(options?.formOption?.layout ?? []))
|
||||
// const layout = options?.formOption?.layout ?? []
|
||||
columns.map(async (item) => {
|
||||
await columnItemHandle(item)
|
||||
})
|
||||
// 设置表单布局
|
||||
settingFormLayout(layout)
|
||||
if (isArray(layout) && layout.length > 0) {
|
||||
formColumns.value = layout
|
||||
const excludeColumns = ["__index", "__operation"]
|
||||
columns.map((item) => {
|
||||
if (options.formExcludePk) excludeColumns.push(options.pk)
|
||||
if (excludeColumns.includes(item.dataIndex)) return
|
||||
!item.__formLayoutSetting && formColumns.value.push(item)
|
||||
})
|
||||
}
|
||||
|
||||
dataLoading.value = false
|
||||
}
|
||||
|
||||
const columnItemHandle = async (item) => {
|
||||
const excludeColumns = ["__index", "__operation"]
|
||||
if (options.formExcludePk) excludeColumns.push(options.pk)
|
||||
if (excludeColumns.includes(item.dataIndex)) {
|
||||
layoutColumns.value.set(item.dataIndex, { dataIndex: item.dataIndex, layoutFormRemove: true })
|
||||
return
|
||||
}
|
||||
layoutColumns.value.set(item.dataIndex, item)
|
||||
formColumns.value.push(item)
|
||||
|
||||
// 针对带点的数据处理
|
||||
if (item.dataIndex && item.dataIndex.indexOf(".") > -1) {
|
||||
form.value[item.dataIndex] = get(form.value, item.dataIndex)
|
||||
}
|
||||
|
||||
// add 默认值处理
|
||||
if (currentAction.value === "add") {
|
||||
if (item.addDefaultValue && isFunction(item.addDefaultValue)) {
|
||||
form.value[item.dataIndex] = await item.addDefaultValue(form.value)
|
||||
} else if (typeof item.addDefaultValue != "undefined") {
|
||||
form.value[item.dataIndex] = item.addDefaultValue
|
||||
}
|
||||
}
|
||||
// edit 默认值处理
|
||||
if (currentAction.value === "edit") {
|
||||
if (item.editDefaultValue && isFunction(item.editDefaultValue)) {
|
||||
form.value[item.dataIndex] = await item.editDefaultValue(form.value)
|
||||
} else if (typeof item.editDefaultValue != "undefined") {
|
||||
form.value[item.dataIndex] = item.editDefaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// 其他处理
|
||||
item.display = formItemShow(item)
|
||||
item.disabled = formItemDisabled(item)
|
||||
item.readonly = formItemReadonly(item)
|
||||
item.labelWidth = formItemLabelWidth(item)
|
||||
item.rules = getRules(item)
|
||||
}
|
||||
const settingFormLayout = (layout) => {
|
||||
if (!isArray(layout)) {
|
||||
return
|
||||
}
|
||||
layout.map(async (item, index) => {
|
||||
if (containerItems.includes(item.formType)) {
|
||||
switch (item.formType) {
|
||||
case "tabs":
|
||||
if (item.tabs) {
|
||||
item.tabs.map((tab) => {
|
||||
tab.formList && (tab.formList = settingFormLayout(tab.formList))
|
||||
})
|
||||
}
|
||||
break
|
||||
case "card":
|
||||
item.formList && (item.formList = settingFormLayout(item.formList))
|
||||
break
|
||||
case "grid-tailwind":
|
||||
case "grid":
|
||||
if (item.cols) {
|
||||
item.cols.map((col) => {
|
||||
col.formList && (col.formList = settingFormLayout(col.formList))
|
||||
})
|
||||
}
|
||||
break
|
||||
case "table":
|
||||
if (item.rows) {
|
||||
item.rows.map((row) => {
|
||||
if (row.cols) {
|
||||
row.cols.map((col) => {
|
||||
col.formList && (col.formList = settingFormLayout(col.formList))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
let column = layoutColumns.value.get(item.dataIndex)
|
||||
// 公共column存在以dataIndex作为判断获取配置项
|
||||
if (column) {
|
||||
// 判断是否layout配置移除
|
||||
if (column.layoutFormRemove) {
|
||||
layout[index] = undefined
|
||||
return
|
||||
}
|
||||
column["__formLayoutSetting"] = true
|
||||
item = column
|
||||
layout[index] = item
|
||||
} else {
|
||||
// 当公共column不存在,则执行column配置项处理
|
||||
await columnItemHandle(item)
|
||||
let column = layoutColumns.value.get(item.dataIndex)
|
||||
if (column.layoutFormRemove) {
|
||||
layout[index] = undefined
|
||||
return
|
||||
}
|
||||
item["__formLayoutSetting"] = true
|
||||
layout[index] = item
|
||||
}
|
||||
}
|
||||
})
|
||||
// 移除
|
||||
return layout.filter((item) => {
|
||||
return !isUndefined(item)
|
||||
})
|
||||
}
|
||||
|
||||
const formItemShow = (item) => {
|
||||
if (currentAction.value === "add") {
|
||||
return item.addDisplay !== false
|
||||
}
|
||||
if (currentAction.value === "edit") {
|
||||
return item.editDisplay !== false
|
||||
}
|
||||
return item.display !== false
|
||||
}
|
||||
const formItemDisabled = (item) => {
|
||||
if (currentAction.value === "add" && !isUndefined(item.addDisabled)) {
|
||||
return item.addDisabled
|
||||
}
|
||||
if (currentAction.value === "edit" && !isUndefined(item.editDisabled)) {
|
||||
return item.editDisabled
|
||||
}
|
||||
if (!isUndefined(item.disabled)) {
|
||||
return item.disabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
const formItemReadonly = (item) => {
|
||||
if (currentAction.value === "add" && !isUndefined(item.addReadonly)) {
|
||||
return item.addReadonly
|
||||
}
|
||||
if (currentAction.value === "edit" && !isUndefined(item.editReadonly)) {
|
||||
return item.editReadonly
|
||||
}
|
||||
if (!isUndefined(item.readonly)) {
|
||||
return item.readonly
|
||||
}
|
||||
return false
|
||||
}
|
||||
const formItemLabelWidth = (item) => {
|
||||
return item.labelWidth ?? options.labelWidth ?? undefined
|
||||
}
|
||||
|
||||
const toRules = (rules) => {
|
||||
if (!rules) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (isArray(rules)) {
|
||||
return rules.map((v) => ({ ...v }))
|
||||
}
|
||||
|
||||
if (!rules.validator && isFunction(rules.validatorFormData)) {
|
||||
rules.validator = (value, cb) => {
|
||||
rules.validatorFormData(value, cb, form.value)
|
||||
}
|
||||
}
|
||||
return { ...rules }
|
||||
}
|
||||
|
||||
const getRules = (item) => {
|
||||
if (currentAction.value === "add") {
|
||||
return toRules(item.addRules ?? item.commonRules ?? [])
|
||||
}
|
||||
if (currentAction.value === "edit") {
|
||||
return toRules(item.editRules ?? item.commonRules ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
const getFormColumns = async (type = "add") => {
|
||||
await init()
|
||||
return formColumns.value
|
||||
}
|
||||
defineExpose({ add, edit, currentAction, form, getFormColumns })
|
||||
</script>
|
||||
69
cdTMP/src/components/ma-crud/components/import.vue
Normal file
69
cdTMP/src/components/ma-crud/components/import.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-modal v-model:visible="visible" :footer="false" @cancel="close" draggable>
|
||||
<template #title>导入</template>
|
||||
<a-upload draggable :custom-request="upload" :show-file-list="false" accept=".xlsx,.xls">
|
||||
<template #upload-button>
|
||||
<div
|
||||
style="background-color: var(--color-fill-2); border: 1px dashed var(--color-fill-4)"
|
||||
class="rounded text-center p-7"
|
||||
>
|
||||
<div>
|
||||
<icon-upload class="text-5xl text-gray-400" />
|
||||
<div class="text-red-600 font-bold">导入Excel</div>
|
||||
将文件拖到此处,或<span style="color: #3370ff">点击上传</span>,只能上传 xls/xlsx 文件
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-upload>
|
||||
<div class="mt-5 italic text-right"><a-link @click="sendDownload">下载Excel模板</a-link></div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject } from "vue"
|
||||
import commonApi from "@/api/common"
|
||||
import tool from "@/utils/tool"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const options = inject("options")
|
||||
|
||||
const open = () => (visible.value = true)
|
||||
const close = () => (visible.value = false)
|
||||
|
||||
const upload = (fileOption) => {
|
||||
Message.info("文件上传导入中...")
|
||||
|
||||
const dataForm = new FormData()
|
||||
dataForm.append("file", fileOption.fileItem.file)
|
||||
commonApi.importExcel(options.import.url, dataForm).then((res) => {
|
||||
res.success && Message.success(res.message || "导入成功")
|
||||
close()
|
||||
})
|
||||
}
|
||||
|
||||
const sendDownload = () => {
|
||||
Message.info("请求服务器下载文件中...")
|
||||
const url = options.import.templateUrl
|
||||
if (/^(http|https)/g.test(url)) {
|
||||
window.open(url)
|
||||
} else {
|
||||
commonApi.download(url).then((res) => {
|
||||
tool.download(res)
|
||||
Message.success("请求成功,文件开始下载")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
180
cdTMP/src/components/ma-crud/components/search.vue
Normal file
180
cdTMP/src/components/ma-crud/components/search.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-spin :loading="searchLoading" :tip="options.searchLoadingText" v-if="showSearch">
|
||||
<a-form
|
||||
:model="searchForm"
|
||||
layout="inline"
|
||||
:label-align="options?.searchLabelAlign"
|
||||
ref="searchRef"
|
||||
@submit="handlerSearch"
|
||||
>
|
||||
<div :class="[gridClass, options?.searchCustomClass]">
|
||||
<template v-for="(component, index) in searchColumns" :key="index">
|
||||
<a-form-item
|
||||
:field="component.dataIndex"
|
||||
:label="component.title"
|
||||
label-col-flex="auto"
|
||||
:label-col-style="{ width: component.searchLabelWidth ?? options.searchLabelWidth }"
|
||||
>
|
||||
<slot :name="`${component.dataIndex}`" v-bind="{ searchForm, component }">
|
||||
<component :is="getComponentName(component.formType)" :component="component" />
|
||||
</slot>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-center mt-5 w-full" v-if="searchColumns.length > 0">
|
||||
<a-space size="medium">
|
||||
<slot name="searchBeforeButtons" />
|
||||
<slot name="searchButtons">
|
||||
<a-button type="primary" html-type="submit">
|
||||
<template #icon><icon-search /></template>
|
||||
{{ options.searchSubmitButtonText }}
|
||||
</a-button>
|
||||
<a-button @click="resetSearch">
|
||||
<template #icon><icon-delete /></template>
|
||||
{{ options.searchResetButtonText }}
|
||||
</a-button>
|
||||
</slot>
|
||||
<slot name="searchAfterButtons" />
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, provide, markRaw } from "vue"
|
||||
import MaFormInput from "./searchFormItem/form-input.vue"
|
||||
import MaFormPicker from "./searchFormItem/form-picker.vue"
|
||||
import MaFormSelect from "./searchFormItem/form-select.vue"
|
||||
import MaFormCascader from "./searchFormItem/form-cascader.vue"
|
||||
import MaFormTreeSelect from "./searchFormItem/form-tree-select.vue"
|
||||
import { cloneDeep, isFunction } from "lodash"
|
||||
|
||||
const options = inject("options")
|
||||
const columns = inject("columns")
|
||||
const requestParams = inject("requestParams")
|
||||
|
||||
const gridClass = ref(["ma-search-grid", "w-full", "grid", "lg:grid-cols-" + options.searchColNumber ?? 4])
|
||||
|
||||
const searchLoading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const searchRef = ref(null)
|
||||
const searchColumns = ref([])
|
||||
|
||||
const searchForm = ref({})
|
||||
|
||||
provide("searchForm", searchForm)
|
||||
provide("columns", columns)
|
||||
|
||||
const emit = defineEmits(["search"])
|
||||
|
||||
if (columns.length > 0) {
|
||||
searchColumns.value = cloneDeep(
|
||||
columns.filter((item) => item.search === true && options.tabs?.dataIndex != item.dataIndex)
|
||||
)
|
||||
}
|
||||
|
||||
const handlerSearch = () => {
|
||||
emit("search", searchForm.value)
|
||||
}
|
||||
|
||||
const resetSearch = async () => {
|
||||
searchRef.value.resetFields()
|
||||
if (options.resetSearch && isFunction(options.resetSearch)) {
|
||||
await options.resetSearch(searchForm.value)
|
||||
}
|
||||
emit("search", searchForm.value)
|
||||
}
|
||||
|
||||
const componentList = ref({
|
||||
MaFormSelect: markRaw(MaFormSelect),
|
||||
MaFormPicker: markRaw(MaFormPicker),
|
||||
MaFormCascader: markRaw(MaFormCascader),
|
||||
MaFormTreeSelect: markRaw(MaFormTreeSelect),
|
||||
MaFormInput: markRaw(MaFormInput)
|
||||
})
|
||||
|
||||
const getComponentName = (formType) => {
|
||||
if (["select", "radio", "checkbox", "transfer"].includes(formType)) {
|
||||
return componentList.value["MaFormSelect"]
|
||||
} else if (["date", "month", "year", "week", "quarter", "range", "time"].includes(formType)) {
|
||||
return componentList.value["MaFormPicker"]
|
||||
} else if (formType === "cascader") {
|
||||
return componentList.value["MaFormCascader"]
|
||||
} else if (formType === "tree-select") {
|
||||
return componentList.value["MaFormTreeSelect"]
|
||||
} else {
|
||||
return componentList.value["MaFormInput"]
|
||||
}
|
||||
}
|
||||
|
||||
const setSearchHidden = () => (showSearch.value = false)
|
||||
const setSearchDisplay = () => (showSearch.value = true)
|
||||
const setSearchLoading = () => (searchLoading.value = true)
|
||||
const setSearchUnLoading = () => (searchLoading.value = false)
|
||||
const getSearchFormRef = () => searchRef.value
|
||||
const getSearchColumns = () => searchColumns.value
|
||||
|
||||
defineExpose({
|
||||
getSearchFormRef,
|
||||
getSearchColumns,
|
||||
showSearch,
|
||||
setSearchHidden,
|
||||
setSearchDisplay,
|
||||
setSearchLoading,
|
||||
setSearchUnLoading
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-form-item-label-required-symbol svg) {
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:grid-cols-1 {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-5 {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-6 {
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-7 {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-8 {
|
||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-9 {
|
||||
grid-template-columns: repeat(9, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-10 {
|
||||
grid-template-columns: repeat(10, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-11 {
|
||||
grid-template-columns: repeat(11, minmax(0, 1fr));
|
||||
}
|
||||
.lg\:grid-cols-12 {
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-cascader
|
||||
v-model="value"
|
||||
:placeholder="props.component.searchPlaceholder || `请选择${props.component.title}`"
|
||||
allow-clear
|
||||
allow-search
|
||||
:expand-trigger="props.component.trigger || 'click'"
|
||||
:options="dicts[props.component.dataIndex]"
|
||||
:multiple="props.component.multiple"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, watch } from "vue"
|
||||
import { get, set } from "lodash"
|
||||
const props = defineProps({
|
||||
component: Object
|
||||
})
|
||||
const searchForm = inject("searchForm")
|
||||
const dicts = inject("dicts")
|
||||
|
||||
const value = ref(get(searchForm.value, props.component.dataIndex, props.component.searchDefaultValue))
|
||||
set(searchForm.value, props.component.dataIndex, value.value)
|
||||
|
||||
watch(
|
||||
() => get(searchForm.value, props.component.dataIndex),
|
||||
(vl) => (value.value = vl)
|
||||
)
|
||||
watch(
|
||||
() => value.value,
|
||||
(v) => set(searchForm.value, props.component.dataIndex, v)
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-input
|
||||
v-model="value"
|
||||
:placeholder="props.component.searchPlaceholder ?? `请输入${props.component.title}`"
|
||||
allow-clear
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, watch } from "vue"
|
||||
import { get, set } from "lodash"
|
||||
const props = defineProps({
|
||||
component: Object
|
||||
})
|
||||
const searchForm = inject("searchForm")
|
||||
|
||||
const value = ref(get(searchForm.value, props.component.dataIndex, props.component.searchDefaultValue ?? ""))
|
||||
set(searchForm.value, props.component.dataIndex, value.value)
|
||||
|
||||
watch(
|
||||
() => get(searchForm.value, props.component.dataIndex),
|
||||
(vl) => (value.value = vl)
|
||||
)
|
||||
watch(
|
||||
() => value.value,
|
||||
(v) => set(searchForm.value, props.component.dataIndex, v)
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<component
|
||||
:is="getComponentName()"
|
||||
v-model="value"
|
||||
:placeholder="
|
||||
props.component.formType === 'range'
|
||||
? ['请选择开始时间', '请选择结束时间']
|
||||
: `请选择${props.component.title}`
|
||||
"
|
||||
:time-picker-props="props.component.formType == 'range' ? { defaultValue: ['00:00:00', '23:59:59'] } : {}"
|
||||
:show-time="props.component.showTime"
|
||||
:format="props.component.format || ''"
|
||||
:mode="props.component.mode"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, watch } from "vue"
|
||||
import { get, set } from "lodash"
|
||||
const props = defineProps({
|
||||
component: Object
|
||||
})
|
||||
const searchForm = inject("searchForm")
|
||||
|
||||
const getComponentName = () => {
|
||||
if (["date", "month", "year", "week", "quarter", "range", "time"].includes(props.component.formType)) {
|
||||
return `a-${props.component.formType}-picker`
|
||||
}
|
||||
}
|
||||
|
||||
let defaultValue
|
||||
|
||||
if (props.component.formType === "range") {
|
||||
defaultValue = props.component.searchDefaultValue ?? []
|
||||
} else {
|
||||
defaultValue = props.component.searchDefaultValue ?? ""
|
||||
}
|
||||
|
||||
const value = ref(get(searchForm.value, props.component.dataIndex, defaultValue))
|
||||
set(searchForm.value, props.component.dataIndex, value.value)
|
||||
|
||||
watch(
|
||||
() => get(searchForm.value, props.component.dataIndex),
|
||||
(vl) => (value.value = vl)
|
||||
)
|
||||
watch(
|
||||
() => value.value,
|
||||
(v) => set(searchForm.value, props.component.dataIndex, v)
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model="value"
|
||||
:virtual-list-props="props.component.virtualListProps"
|
||||
:placeholder="props.component.searchPlaceholder ?? `请选择${props.component.title}`"
|
||||
allow-clear
|
||||
allow-search
|
||||
:scrollbar="props.component.scrollbar"
|
||||
:format-label="props.component.formatLabel"
|
||||
:max-tag-count="1"
|
||||
:options="dicts[props.component.dataIndex]"
|
||||
:multiple="props.component.multiple || ['transfer', 'checkbox'].includes(props.component.formType)"
|
||||
@change="handlerChangeeEvent"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, watch } from "vue"
|
||||
import { handlerCascader } from "@cps/ma-form/js/networkRequest"
|
||||
import { get, set } from "lodash"
|
||||
const props = defineProps({
|
||||
component: Object
|
||||
})
|
||||
const searchForm = inject("searchForm")
|
||||
const columns = inject("columns")
|
||||
const dicts = inject("dicts")
|
||||
|
||||
let defaultValue
|
||||
|
||||
if (props.component.multiple === true) {
|
||||
defaultValue = props.component.searchDefaultValue ?? []
|
||||
} else {
|
||||
defaultValue = props.component.searchDefaultValue ?? ""
|
||||
}
|
||||
|
||||
const value = ref(get(searchForm.value, props.component.dataIndex, defaultValue))
|
||||
set(searchForm.value, props.component.dataIndex, value.value)
|
||||
|
||||
watch(
|
||||
() => get(searchForm.value, props.component.dataIndex),
|
||||
(vl) => (value.value = vl)
|
||||
)
|
||||
watch(
|
||||
() => value.value,
|
||||
(v) => set(searchForm.value, props.component.dataIndex, v)
|
||||
)
|
||||
|
||||
const handlerChangeeEvent = (value) => {
|
||||
handlerCascader(value, props.component, columns, dicts, searchForm.value)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-tree-select
|
||||
v-model="value"
|
||||
:treeProps="props.component.treeProps"
|
||||
:placeholder="props.component.searchPlaceholder || `请选择${props.component.title}`"
|
||||
allow-clear
|
||||
allow-search
|
||||
:field-names="props.component.dict.props || { key: 'value', title: 'label' }"
|
||||
:tree-checkable="props.component.multiple"
|
||||
:multiple="props.component.multiple"
|
||||
:data="dicts[props.component.dataIndex]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, watch } from "vue"
|
||||
import { get, set } from "lodash"
|
||||
const props = defineProps({
|
||||
component: Object
|
||||
})
|
||||
const searchForm = inject("searchForm")
|
||||
const dicts = inject("dicts")
|
||||
|
||||
let defaultValue
|
||||
|
||||
if (props.component.multiple === true) {
|
||||
defaultValue = props.component.searchDefaultValue ?? []
|
||||
} else {
|
||||
defaultValue = props.component.searchDefaultValue ?? ""
|
||||
}
|
||||
|
||||
const value = ref(get(searchForm.value, props.component.dataIndex, defaultValue))
|
||||
set(searchForm.value, props.component.dataIndex, value.value)
|
||||
|
||||
watch(
|
||||
() => get(searchForm.value, props.component.dataIndex),
|
||||
(vl) => (value.value = vl)
|
||||
)
|
||||
watch(
|
||||
() => value.value,
|
||||
(v) => set(searchForm.value, props.component.dataIndex, v)
|
||||
)
|
||||
</script>
|
||||
178
cdTMP/src/components/ma-crud/components/setting.vue
Normal file
178
cdTMP/src/components/ma-crud/components/setting.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-drawer :visible="visible" unmountOnClose :footer="false" :width="900" @cancel="onCancel">
|
||||
<template #title>设置</template>
|
||||
|
||||
<a-space class="mt-3">
|
||||
<span>表格大小:</span>
|
||||
<a-radio-group type="button" v-model="options.size">
|
||||
<a-radio value="mini">迷你</a-radio>
|
||||
<a-radio value="small">小</a-radio>
|
||||
<a-radio value="medium">中</a-radio>
|
||||
<a-radio value="large">大</a-radio>
|
||||
</a-radio-group>
|
||||
<span class="ml-3">表格边框:</span>
|
||||
<a-radio-group type="button" v-model="bordered" @change="changeBordered">
|
||||
<a-radio value="hide">不显示外边框</a-radio>
|
||||
<a-radio value="show">全部显示</a-radio>
|
||||
<a-radio value="row">不显示行</a-radio>
|
||||
<a-radio value="column">不显示列</a-radio>
|
||||
</a-radio-group>
|
||||
<a-checkbox v-model="options.stripe" class="ml-3">斑马纹</a-checkbox>
|
||||
</a-space>
|
||||
|
||||
<a-alert type="warning" class="mt-2"
|
||||
>排序:本页是指当前页排序;服务器是指后台排序,若自定义服务器排序可用
|
||||
<a-tag>@sorterChange</a-tag> 事件来实现</a-alert
|
||||
>
|
||||
<a-table
|
||||
:data="allowShowColumns"
|
||||
:pagination="false"
|
||||
:bordered="{ wrapper: true, cell: false }"
|
||||
:draggable="{ type: 'handle', width: 40 }"
|
||||
@change="onTableChange"
|
||||
stripe
|
||||
class="mt-3"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column title="列名称" data-index="title" align="center">
|
||||
<template #cell="{ record }">{{ record.title }}</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="宽度" data-index="width" align="center">
|
||||
<template #cell="{ record }">
|
||||
<a-input-number
|
||||
v-if="!['__index', '__operation'].includes(record.dataIndex)"
|
||||
style="width: 150px"
|
||||
placeholder="列宽度"
|
||||
v-model="record.width"
|
||||
mode="button"
|
||||
@change="changeColumn($event, 'width', record.dataIndex)"
|
||||
/>
|
||||
<span v-else> / </span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="隐藏" data-index="hide" align="center">
|
||||
<template #cell="{ record }"
|
||||
><a-checkbox v-model="record.hide" @change="changeColumn($event, 'hide', record.dataIndex)"
|
||||
/></template>
|
||||
</a-table-column>
|
||||
<a-table-column title="固定" data-index="fixed" align="center">
|
||||
<template #cell="{ record }">
|
||||
<a-space v-if="!['__index', '__operation'].includes(record.dataIndex)">
|
||||
<a-radio
|
||||
v-model="record.fixed"
|
||||
value=""
|
||||
@change="changeColumn($event, 'fixed', record.dataIndex)"
|
||||
>无</a-radio
|
||||
>
|
||||
<a-radio
|
||||
v-model="record.fixed"
|
||||
value="left"
|
||||
@change="changeColumn($event, 'fixed', record.dataIndex)"
|
||||
>左</a-radio
|
||||
>
|
||||
<a-radio
|
||||
v-model="record.fixed"
|
||||
value="right"
|
||||
@change="changeColumn($event, 'fixed', record.dataIndex)"
|
||||
>右</a-radio
|
||||
>
|
||||
</a-space>
|
||||
<span v-else> / </span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="排序" data-index="order" align="center">
|
||||
<template #cell="{ record }">
|
||||
<a-space v-if="!['__index', '__operation'].includes(record.dataIndex)">
|
||||
<a-radio
|
||||
v-model="record.__order"
|
||||
value=""
|
||||
@change="changeColumn($event, 'order', record.dataIndex)"
|
||||
>无</a-radio
|
||||
>
|
||||
<a-radio
|
||||
v-model="record.__order"
|
||||
value="page"
|
||||
@change="changeColumn($event, 'order', record.dataIndex)"
|
||||
>本页</a-radio
|
||||
>
|
||||
<a-radio
|
||||
v-model="record.__order"
|
||||
value="serve"
|
||||
@change="changeColumn($event, 'order', record.dataIndex)"
|
||||
>服务器</a-radio
|
||||
>
|
||||
</a-space>
|
||||
<span v-else> / </span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject } from "vue"
|
||||
|
||||
const options = inject("options")
|
||||
let columns = inject("columns")
|
||||
|
||||
const allowShowColumns = columns.filter((item) => {
|
||||
return !(item?.settingHide ?? false)
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
const bordered = ref("column")
|
||||
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const changeColumn = (ev, type, name) => {
|
||||
const column = columns.find((item) => item.dataIndex === name)
|
||||
switch (type) {
|
||||
case "order":
|
||||
if (ev === "page") {
|
||||
column.sortable = { sortDirections: ["ascend", "descend"], sorter: false }
|
||||
} else if (ev === "serve") {
|
||||
column.sortable = { sortDirections: ["ascend", "descend"], sorter: true }
|
||||
} else {
|
||||
column.sortable = undefined
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const changeBordered = (v) => {
|
||||
if (v === "hide") {
|
||||
options.bordered = { wrapper: false, cell: false }
|
||||
}
|
||||
if (v === "show") {
|
||||
options.bordered = { wrapper: true, cell: true }
|
||||
}
|
||||
if (v === "row") {
|
||||
options.bordered = { wrapper: false, cell: true }
|
||||
}
|
||||
if (v === "column") {
|
||||
options.bordered = { wrapper: true, cell: false }
|
||||
}
|
||||
}
|
||||
|
||||
const onTableChange = (_data) => {
|
||||
columns = _data
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
841
cdTMP/src/components/ma-crud/index.vue
Normal file
841
cdTMP/src/components/ma-crud/index.vue
Normal file
@@ -0,0 +1,841 @@
|
||||
<template>
|
||||
<a-layout-content class="flex flex-col lg:h-full relative w-full">
|
||||
<div class="_crud-header flex flex-col mb-2" ref="crudHeaderRef">
|
||||
<a-tabs
|
||||
v-if="isArray(options.tabs.data) && options.tabs.data.length > 0"
|
||||
v-model:active-key="options.tabs.defaultKey"
|
||||
:trigger="options.tabs.trigger"
|
||||
:type="options.tabs.type"
|
||||
:hide-content="true"
|
||||
@change="tabChange"
|
||||
@tab-click="maEvent.customeEvent(options.tabs, $event, 'onClick')"
|
||||
class="ma-tabs mb-5"
|
||||
>
|
||||
<template #extra><slot name="tabExtra"></slot></template>
|
||||
<a-tab-pane :key="item.value" :title="item.label" v-for="item in options.tabs.data"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<ma-search @search="searchSubmitHandler" class="__search-panel" ref="crudSearchRef">
|
||||
<template v-for="(slot, slotIndex) in searchSlots" :key="slotIndex" #[slot]="{ searchForm, component }">
|
||||
<slot :name="`search-${slot}`" v-bind="{ searchForm, component }" />
|
||||
</template>
|
||||
<template #searchBeforeButtons>
|
||||
<slot name="searchBeforeButtons"></slot>
|
||||
</template>
|
||||
<template #searchButtons>
|
||||
<slot name="searchButtons"></slot>
|
||||
</template>
|
||||
<template #searchAfterButtons>
|
||||
<slot name="searchAfterButtons"></slot>
|
||||
</template>
|
||||
</ma-search>
|
||||
</div>
|
||||
<div class="_crud-content">
|
||||
<div class="operation-tools lg:flex justify-between mb-3" ref="crudOperationRef">
|
||||
<a-space class="lg:flex block lg:inline-block">
|
||||
<slot name="tableBeforeButtons"></slot>
|
||||
<slot name="tableButtons">
|
||||
<a-button
|
||||
v-if="options.add.show"
|
||||
@click="addAction"
|
||||
type="primary"
|
||||
class="w-full lg:w-auto mt-2 lg:mt-0"
|
||||
>
|
||||
<template #icon><icon-plus /></template>{{ options.add.text || "新增" }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm content="确定要删除数据吗?" position="bottom" @ok="deletesMultipleAction">
|
||||
<a-button
|
||||
v-if="options.delete.show"
|
||||
type="primary"
|
||||
status="danger"
|
||||
class="w-full lg:w-auto mt-2 lg:mt-0"
|
||||
>
|
||||
<template #icon><icon-delete /></template>
|
||||
{{ isRecovery ? options.delete.realText || "删除" : options.delete.text || "删除" }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-popconfirm content="确定要恢复数据吗?" position="bottom" @ok="recoverysMultipleAction">
|
||||
<a-button
|
||||
v-if="options.recovery.show && isRecovery"
|
||||
type="primary"
|
||||
status="success"
|
||||
class="w-full lg:w-auto mt-2 lg:mt-0"
|
||||
>
|
||||
<template #icon><icon-undo /></template>{{ options.recovery.text || "恢复" }}</a-button
|
||||
>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button v-if="options.import.show" @click="importAction" class="w-full lg:w-auto mt-2 lg:mt-0"
|
||||
><template #icon><icon-upload /></template>{{ options.import.text || "导入" }}</a-button
|
||||
>
|
||||
|
||||
<a-button v-if="options.export.show" @click="exportAction" class="w-full lg:w-auto mt-2 lg:mt-0"
|
||||
><template #icon><icon-download /></template>{{ options.export.text || "导出" }}</a-button
|
||||
>
|
||||
|
||||
<a-button
|
||||
type="secondary"
|
||||
@click="handlerExpand"
|
||||
v-if="options.isExpand"
|
||||
class="w-full lg:w-auto mt-2 lg:mt-0"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-expand v-if="!expandState" />
|
||||
<icon-shrink v-else />
|
||||
</template>
|
||||
{{ expandState ? " 折叠" : " 展开" }}
|
||||
</a-button>
|
||||
</slot>
|
||||
<slot name="tableAfterButtons"></slot>
|
||||
</a-space>
|
||||
<a-space class="lg:mt-0 mt-2" v-if="options.showTools">
|
||||
<slot name="tools"></slot>
|
||||
<a-tooltip
|
||||
:content="isRecovery ? '显示正常数据' : '显示回收站数据'"
|
||||
v-if="options.recycleApi && isFunction(options.recycleApi)"
|
||||
>
|
||||
<a-button shape="circle" @click="switchDataType"><icon-swap /></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip content="刷新表格"
|
||||
><a-button shape="circle" @click="refresh"><icon-refresh /></a-button
|
||||
></a-tooltip>
|
||||
<a-tooltip content="显隐搜索"
|
||||
><a-button shape="circle" @click="toggleSearch"><icon-search /></a-button
|
||||
></a-tooltip>
|
||||
<a-tooltip content="打印表格"
|
||||
><a-button shape="circle" @click="printTable"><icon-printer /></a-button
|
||||
></a-tooltip>
|
||||
<a-tooltip content="设置"
|
||||
><a-button shape="circle" @click="tableSetting"><icon-settings /></a-button
|
||||
></a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<div ref="crudContentRef">
|
||||
<slot name="content" v-bind="tableData">
|
||||
<a-table
|
||||
v-bind="$attrs"
|
||||
ref="tableRef"
|
||||
:key="options.pk"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:sticky-header="options.stickyHeader"
|
||||
:pagination="options.tablePagination"
|
||||
:stripe="options.stripe"
|
||||
:bordered="options.bordered"
|
||||
:rowSelection="options.rowSelection || undefined"
|
||||
:row-key="options?.rowSelection?.key ?? options.pk"
|
||||
:scroll="options.scroll"
|
||||
:column-resizable="options.resizable"
|
||||
:size="options.size"
|
||||
:row-class="options.rowClass"
|
||||
:hide-expand-button-on-empty="options.hideExpandButtonOnEmpty"
|
||||
:default-expand-all-rows="options.expandAllRows"
|
||||
:summary="options.customerSummary || __summary || options.showSummary"
|
||||
@selection-change="setSelecteds"
|
||||
@sorter-change="handlerSort"
|
||||
>
|
||||
<template #tr="{ record }">
|
||||
<tr
|
||||
class="ma-crud-table-tr"
|
||||
:class="
|
||||
isFunction(options.rowCustomClass)
|
||||
? options.rowCustomClass(record, rowIndex) ?? []
|
||||
: options.rowCustomClass
|
||||
"
|
||||
@contextmenu.prevent="openContextMenu($event, record)"
|
||||
@dblclick="dbClickOpenEdit(record)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #expand-row="record" v-if="options.showExpandRow">
|
||||
<slot name="expand-row" v-bind="record"></slot>
|
||||
</template>
|
||||
<template #columns>
|
||||
<ma-column
|
||||
ref="crudColumnRef"
|
||||
v-if="reloadColumn"
|
||||
:columns="props.columns"
|
||||
:isRecovery="isRecovery"
|
||||
:crudFormRef="crudFormRef"
|
||||
@refresh="() => refresh()"
|
||||
@showImage="showImage"
|
||||
>
|
||||
<template #operationBeforeExtend="{ record, column, rowIndex }">
|
||||
<slot name="operationBeforeExtend" v-bind="{ record, column, rowIndex }"></slot>
|
||||
</template>
|
||||
|
||||
<template #operationCell="{ record, column, rowIndex }">
|
||||
<slot name="operationCell" v-bind="{ record, column, rowIndex }"></slot>
|
||||
</template>
|
||||
|
||||
<template #operationAfterExtend="{ record, column, rowIndex }">
|
||||
<slot name="operationAfterExtend" v-bind="{ record, column, rowIndex }"></slot>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-for="(slot, slotIndex) in slots"
|
||||
:key="slotIndex"
|
||||
#[slot]="{ record, column, rowIndex }"
|
||||
>
|
||||
<slot :name="`${slot}`" v-bind="{ record, column, rowIndex }" />
|
||||
</template>
|
||||
</ma-column>
|
||||
</template>
|
||||
<template
|
||||
#summary-cell="{ column, record, rowIndex }"
|
||||
v-if="options.customerSummary || options.showSummary"
|
||||
>
|
||||
<slot name="summaryCell" v-bind="{ record, column, rowIndex }">{{
|
||||
record[column.dataIndex]
|
||||
}}</slot>
|
||||
</template>
|
||||
</a-table>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_crud-footer mt-3 text-right"
|
||||
ref="crudFooterRef"
|
||||
v-if="total > 0 && openPagination && !options.tablePagination"
|
||||
>
|
||||
<a-pagination
|
||||
:total="total"
|
||||
show-total
|
||||
show-jumper
|
||||
show-page-size
|
||||
:page-size-options="options.pageSizeOption"
|
||||
@page-size-change="pageSizeChangeHandler"
|
||||
@change="pageChangeHandler"
|
||||
v-model:current="requestParams[config.request.page]"
|
||||
:page-size="requestParams[config.request.pageSize]"
|
||||
style="display: inline-flex"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ma-setting ref="crudSettingRef" />
|
||||
|
||||
<ma-form ref="crudFormRef" @success="requestSuccess">
|
||||
<template v-for="slot in Object.keys($slots)" #[slot]="component">
|
||||
<slot :name="slot" v-bind="component" />
|
||||
</template>
|
||||
</ma-form>
|
||||
|
||||
<ma-import ref="crudImportRef" />
|
||||
|
||||
<ma-context-menu ref="crudContextMenuRef" @execCommand="execContextMenuCommand" />
|
||||
|
||||
<a-image-preview :src="imgUrl" v-model:visible="imgVisible" />
|
||||
</a-layout-content>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import config from "@/config/crud"
|
||||
import { ref, watch, provide, nextTick, onMounted, onUnmounted } from "vue"
|
||||
import defaultOptions from "./js/defaultOptions"
|
||||
import { loadDict } from "@cps/ma-form/js/networkRequest.js"
|
||||
import ColumnService from "./js/columnService"
|
||||
|
||||
import MaSearch from "./components/search.vue"
|
||||
import MaForm from "./components/form.vue"
|
||||
import MaSetting from "./components/setting.vue"
|
||||
import MaImport from "./components/import.vue"
|
||||
import MaColumn from "./components/column.vue"
|
||||
import MaContextMenu from "./components/contextMenu.vue"
|
||||
import checkAuth from "@/directives/auth/auth"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { request } from "@/utils/request"
|
||||
import tool from "@/utils/tool"
|
||||
import Print from "@/utils/print"
|
||||
import { isArray, isFunction, isObject, isUndefined } from "lodash"
|
||||
import { maEvent } from "@cps/ma-form/js/formItemMixin.js"
|
||||
import globalColumn from "@/config/column.js"
|
||||
import { useFormStore } from "@/store/index"
|
||||
|
||||
const formStore = useFormStore()
|
||||
const props = defineProps({
|
||||
// 表格数据
|
||||
data: { type: [Function, Array], default: () => null },
|
||||
// 增删改查设置
|
||||
options: { type: Object, default: {} },
|
||||
crud: { type: Object, default: {} },
|
||||
// 字段列设置
|
||||
columns: { type: Array, default: [] }
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
const dicts = ref({})
|
||||
const cascaders = ref([])
|
||||
|
||||
const reloadColumn = ref(true)
|
||||
const openPagination = ref(false)
|
||||
const imgVisible = ref(false)
|
||||
const imgUrl = ref(import.meta.env.VITE_APP_BASE + "not-image.png")
|
||||
const total = ref(0)
|
||||
const requestParams = ref({})
|
||||
const slots = ref([])
|
||||
const searchSlots = ref([])
|
||||
const isRecovery = ref(false)
|
||||
const expandState = ref(false)
|
||||
|
||||
const crudHeaderRef = ref()
|
||||
const crudOperationRef = ref()
|
||||
const crudContentRef = ref()
|
||||
const crudFooterRef = ref()
|
||||
const crudSearchRef = ref()
|
||||
const crudSettingRef = ref()
|
||||
const crudFormRef = ref()
|
||||
const crudImportRef = ref()
|
||||
const crudColumnRef = ref()
|
||||
const crudContextMenuRef = ref()
|
||||
|
||||
const options = ref(Object.assign(JSON.parse(JSON.stringify(defaultOptions)), props.options, props.crud))
|
||||
|
||||
const columns = ref(props.columns)
|
||||
const headerHeight = ref(0)
|
||||
const selecteds = ref([])
|
||||
const tableData = ref([])
|
||||
const tableRef = ref()
|
||||
const currentApi = ref()
|
||||
|
||||
// 初始化
|
||||
const init = async () => {
|
||||
// 设置 组件id
|
||||
if (isUndefined(options.value.id)) {
|
||||
options.value.id = "MaCrud_" + Math.floor(Math.random() * 100000 + Math.random() * 20000 + Math.random() * 5000)
|
||||
}
|
||||
|
||||
// 收集数据
|
||||
props.columns.map((item) => {
|
||||
if (item.cascaderItem && item.cascaderItem.length > 0) {
|
||||
cascaders.value.push(...item.cascaderItem)
|
||||
}
|
||||
})
|
||||
|
||||
await props.columns.map(async (item) => {
|
||||
// 字典
|
||||
if (!cascaders.value.includes(item.dataIndex) && item.dict) {
|
||||
await loadDict(dicts.value, item)
|
||||
}
|
||||
})
|
||||
await tabsHandler()
|
||||
}
|
||||
|
||||
const dictTrans = (dataIndex, value) => {
|
||||
if (dicts.value[dataIndex] && dicts.value[dataIndex].tran) {
|
||||
return dicts.value[dataIndex].tran[value]
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const dictColors = (dataIndex, value) => {
|
||||
if (dicts.value[dataIndex] && dicts.value[dataIndex].colors) {
|
||||
return dicts.value[dataIndex].colors[value]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 公用模板
|
||||
columns.value.map((item, index) => {
|
||||
if (item.common && globalColumn[item.dataIndex]) {
|
||||
columns.value[index] = globalColumn[item.dataIndex]
|
||||
item = columns.value[index]
|
||||
}
|
||||
!item.width && (item.width = options.value.columnWidth)
|
||||
})
|
||||
|
||||
provide("options", options.value)
|
||||
provide("columns", props.columns)
|
||||
provide("layout", props.layout)
|
||||
provide("dicts", dicts.value)
|
||||
provide("dictColors", dictColors.value)
|
||||
provide("requestParams", requestParams.value)
|
||||
provide("dictTrans", dictTrans)
|
||||
provide("dictColors", dictColors)
|
||||
provide("isRecovery", isRecovery)
|
||||
|
||||
watch(
|
||||
() => props.options.api,
|
||||
(vl) => (options.value.api = vl)
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.crud.api,
|
||||
(vl) => (options.value.api = vl)
|
||||
)
|
||||
|
||||
watch(
|
||||
() => openPagination.value,
|
||||
() => options.value.pageLayout === "fixed" && settingFixedPage()
|
||||
)
|
||||
|
||||
watch(
|
||||
() => formStore.crudList[options.value.id],
|
||||
async (vl) => {
|
||||
vl === true && (await requestData())
|
||||
formStore.crudList[options.value.id] = false
|
||||
}
|
||||
)
|
||||
|
||||
const getSlot = (cls = []) => {
|
||||
let sls = []
|
||||
cls.map((item) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
let tmp = getSlot(item.children)
|
||||
sls.push(...tmp)
|
||||
} else if (item.dataIndex) {
|
||||
sls.push(item.dataIndex)
|
||||
}
|
||||
})
|
||||
return sls
|
||||
}
|
||||
|
||||
const showImage = (url) => {
|
||||
imgUrl.value = url
|
||||
imgVisible.value = true
|
||||
}
|
||||
|
||||
const getSearchSlot = () => {
|
||||
let sls = []
|
||||
props.columns.map((item) => {
|
||||
if (item.search && item.search === true) {
|
||||
sls.push(item.dataIndex)
|
||||
}
|
||||
})
|
||||
return sls
|
||||
}
|
||||
|
||||
slots.value = getSlot(props.columns)
|
||||
searchSlots.value = getSearchSlot(props.columns)
|
||||
|
||||
const requestData = async () => {
|
||||
await init()
|
||||
if (options.value.showIndex && columns.value.length > 0 && columns.value[0].dataIndex !== "__index") {
|
||||
columns.value.unshift({
|
||||
title: options.value.indexLabel,
|
||||
dataIndex: "__index",
|
||||
width: options.value.indexColumnWidth,
|
||||
fixed: options.value.indexColumnFixed
|
||||
})
|
||||
}
|
||||
if (
|
||||
options.value.operationColumn &&
|
||||
columns.value.length > 0 &&
|
||||
columns.value[columns.value.length - 1].dataIndex !== "__operation"
|
||||
) {
|
||||
columns.value.push({
|
||||
title: options.value.operationColumnText,
|
||||
dataIndex: "__operation",
|
||||
width: options.value.operationColumnWidth ?? options.value.operationWidth,
|
||||
align: options.value.operationColumnAlign,
|
||||
fixed: options.value.operationColumnFixed
|
||||
})
|
||||
}
|
||||
|
||||
initRequestParams()
|
||||
if (!options.value.tabs?.dataIndex && !options.value.tabs.data) {
|
||||
await refresh()
|
||||
} else {
|
||||
options.value.tabs.defaultKey = options.value.tabs?.defaultKey ?? options.value.tabs.data[0].value
|
||||
await tabChange(options.value.tabs?.defaultKey)
|
||||
}
|
||||
}
|
||||
|
||||
const initRequestParams = () => {
|
||||
requestParams.value[config.request.page] = 1
|
||||
requestParams.value[config.request.pageSize] = options.value.pageSize ?? 10
|
||||
if (options.value.requestParamsLabel) {
|
||||
requestParams.value[options.value.requestParamsLabel] = options.value.requestParams
|
||||
} else {
|
||||
requestParams.value = Object.assign(requestParams.value, options.value.requestParams)
|
||||
}
|
||||
}
|
||||
|
||||
const requestHandle = async () => {
|
||||
loading.value = true
|
||||
isFunction(options.value.beforeRequest) && options.value.beforeRequest(requestParams.value)
|
||||
if (isFunction(currentApi.value)) {
|
||||
const response = config.parseResponseData(await currentApi.value(requestParams.value))
|
||||
if (response.rows) {
|
||||
tableData.value = response.rows
|
||||
if (response.pageInfo) {
|
||||
total.value = response.pageInfo.total
|
||||
openPagination.value = true
|
||||
} else {
|
||||
openPagination.value = false
|
||||
}
|
||||
} else {
|
||||
tableData.value = response
|
||||
}
|
||||
} else {
|
||||
console.error(`ma-crud error:crud.api not is Function.`)
|
||||
}
|
||||
isFunction(options.value.afterRequest) && options.value.afterRequest(tableData.value)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
if (props.data) {
|
||||
loading.value = true
|
||||
const data = isArray(props.data) ? props.data : config.parseResponseData(await props.data(requestParams.value))
|
||||
if (data.rows) {
|
||||
tableData.value = data.rows
|
||||
openPagination.value = true
|
||||
} else {
|
||||
tableData.value = data
|
||||
}
|
||||
loading.value = false
|
||||
} else {
|
||||
currentApi.value =
|
||||
isRecovery.value && options.value.recycleApi && isFunction(options.value.recycleApi)
|
||||
? options.value.recycleApi
|
||||
: options.value.api
|
||||
await requestHandle()
|
||||
}
|
||||
}
|
||||
|
||||
const searchSubmitHandler = async (formData) => {
|
||||
if (options.value.requestParamsLabel && requestParams.value[options.value.requestParamsLabel]) {
|
||||
requestParams.value[options.value.requestParamsLabel] = Object.assign(
|
||||
requestParams.value[options.value.requestParamsLabel],
|
||||
formData
|
||||
)
|
||||
} else if (options.value.requestParamsLabel) {
|
||||
requestParams.value[options.value.requestParamsLabel] = Object.assign({}, formData)
|
||||
} else {
|
||||
requestParams.value = Object.assign(requestParams.value, formData)
|
||||
}
|
||||
if (options.value.beforeSearch && isFunction(options.value.beforeSearch)) {
|
||||
options.value.beforeSearch(requestParams.value)
|
||||
}
|
||||
await pageChangeHandler(1)
|
||||
if (options.value.afterSearch && isFunction(options.value.afterSearch)) {
|
||||
options.value.afterSearch(requestParams.value)
|
||||
}
|
||||
}
|
||||
|
||||
const pageSizeChangeHandler = async (pageSize) => {
|
||||
requestParams.value[config.request.page] = 1
|
||||
requestParams.value[config.request.pageSize] = pageSize
|
||||
await refresh()
|
||||
}
|
||||
|
||||
const pageChangeHandler = async (currentPage) => {
|
||||
requestParams.value[config.request.page] = currentPage
|
||||
await refresh()
|
||||
}
|
||||
|
||||
const toggleSearch = async () => {
|
||||
const dom = crudHeaderRef.value?.style
|
||||
if (dom) {
|
||||
crudSearchRef.value.showSearch ? crudSearchRef.value.setSearchHidden() : crudSearchRef.value.setSearchDisplay()
|
||||
|
||||
await nextTick(() => {
|
||||
headerHeight.value = crudHeaderRef.value.offsetHeight
|
||||
options.value.pageLayout === "fixed" && settingFixedPage()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const settingFixedPage = () => {
|
||||
const workAreaHeight = document.querySelector(".work-area").offsetHeight
|
||||
const tableHeight = workAreaHeight - headerHeight.value - (openPagination.value ? 152 : 108)
|
||||
crudContentRef.value.style.height = tableHeight + "px"
|
||||
}
|
||||
|
||||
const tableSetting = () => {
|
||||
crudSettingRef.value.open()
|
||||
}
|
||||
|
||||
const requestSuccess = async (response) => {
|
||||
if (response && response.code && response.code === 200) {
|
||||
options.value.dataCompleteRefresh && (await refresh())
|
||||
if (reloadColumn.value) {
|
||||
reloadColumn.value = false
|
||||
await nextTick(() => {
|
||||
reloadColumn.value = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addAction = () => {
|
||||
if (isFunction(options.value.beforeOpenAdd) && !options.value.beforeOpenAdd()) {
|
||||
return false
|
||||
}
|
||||
if (options.value.add.action && isFunction(options.value.add.action)) {
|
||||
options.value.add.action()
|
||||
} else {
|
||||
crudFormRef.value.add()
|
||||
}
|
||||
}
|
||||
|
||||
const editAction = (record) => {
|
||||
if (isFunction(options.value.beforeOpenEdit) && !options.value.beforeOpenEdit(record)) {
|
||||
return false
|
||||
}
|
||||
if (options.value.edit.action && isFunction(options.value.edit.action)) {
|
||||
options.value.edit.action(record)
|
||||
} else {
|
||||
crudFormRef.value.edit(record)
|
||||
}
|
||||
}
|
||||
|
||||
const dbClickOpenEdit = (record) => {
|
||||
if (options.value.isDbClickEdit) {
|
||||
if (isRecovery.value) {
|
||||
Message.error("回收站数据不可编辑")
|
||||
return
|
||||
}
|
||||
|
||||
if (options.value.edit.api && isFunction(options.value.edit.api)) {
|
||||
editAction(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const importAction = () => crudImportRef.value.open()
|
||||
|
||||
const exportAction = () => {
|
||||
Message.info("请求服务器下载文件中...")
|
||||
const data = options.value.requestParamsLabel
|
||||
? requestParams.value[options.value.requestParamsLabel]
|
||||
: requestParams.value
|
||||
const download = (url) => request({ url, data, method: "post", timeout: 60 * 1000, responseType: "blob" })
|
||||
|
||||
download(options.value.export.url)
|
||||
.then((res) => {
|
||||
tool.download(res)
|
||||
Message.success("请求成功,文件开始下载")
|
||||
})
|
||||
.catch(() => {
|
||||
Message.error("请求服务器错误,下载失败")
|
||||
})
|
||||
}
|
||||
|
||||
const deletesMultipleAction = async () => {
|
||||
if (selecteds.value && selecteds.value.length > 0) {
|
||||
const api = isRecovery.value ? options.value.delete.realApi : options.value.delete.api
|
||||
let data = {}
|
||||
if (isFunction(options.value.beforeDelete) && !(data = options.value.beforeDelete(selecteds.value))) {
|
||||
return false
|
||||
}
|
||||
const response = await api(Object.assign({ ids: selecteds.value }, data))
|
||||
if (options.value.afterDelete && isFunction(options.value.afterDelete)) {
|
||||
options.value.afterDelete(response)
|
||||
}
|
||||
response.success && Message.success(response.message || `删除成功!`)
|
||||
await refresh()
|
||||
} else {
|
||||
Message.error("至少选择一条数据")
|
||||
}
|
||||
}
|
||||
|
||||
const recoverysMultipleAction = async () => {
|
||||
if (selecteds.value && selecteds.value.length > 0) {
|
||||
const response = await options.value.recovery.api({ ids: selecteds.value })
|
||||
response.success && Message.success(response.message || `恢复成功!`)
|
||||
await refresh()
|
||||
} else {
|
||||
Message.error("至少选择一条数据")
|
||||
}
|
||||
}
|
||||
|
||||
const setSelecteds = (key) => {
|
||||
selecteds.value = key
|
||||
}
|
||||
|
||||
const switchDataType = async () => {
|
||||
isRecovery.value = !isRecovery.value
|
||||
currentApi.value =
|
||||
isRecovery.value && options.value.recycleApi && isFunction(options.value.recycleApi)
|
||||
? options.value.recycleApi
|
||||
: options.value.api
|
||||
await requestData()
|
||||
}
|
||||
|
||||
const handlerExpand = () => {
|
||||
expandState.value = !expandState.value
|
||||
expandState.value ? tableRef.value.expandAll(true) : tableRef.value.expandAll(false)
|
||||
}
|
||||
|
||||
const handlerSort = async (name, type) => {
|
||||
const col = columns.value.find((item) => name === item.dataIndex)
|
||||
if (col.sortable && col.sortable.sorter) {
|
||||
if (type) {
|
||||
requestParams.value.orderBy = name
|
||||
requestParams.value.orderType = type === "ascend" ? "asc" : "desc"
|
||||
} else {
|
||||
requestParams.value.orderBy = undefined
|
||||
requestParams.value.orderType = undefined
|
||||
}
|
||||
await refresh()
|
||||
}
|
||||
}
|
||||
|
||||
const getTableData = () => {
|
||||
return tableData.value
|
||||
}
|
||||
|
||||
const __summary = ({ data }) => {
|
||||
if (options.value.showSummary && isArray(options.value.summary)) {
|
||||
const summary = options.value.summary
|
||||
let summaryData = {}
|
||||
let summaryPrefixText = {}
|
||||
let summarySuffixText = {}
|
||||
let length = data.length || 0
|
||||
summary.map((item) => {
|
||||
summaryData[item.dataIndex] = 0
|
||||
summaryPrefixText[item.dataIndex] = item?.prefixText ?? ""
|
||||
summarySuffixText[item.dataIndex] = item?.suffixText ?? ""
|
||||
data.map((record) => {
|
||||
if (record[item.dataIndex]) {
|
||||
if (item.action && item.action === "sum") {
|
||||
summaryData[item.dataIndex] += parseFloat(record[item.dataIndex])
|
||||
}
|
||||
if (item.action && item.action === "avg") {
|
||||
summaryData[item.dataIndex] += parseFloat(record[item.dataIndex]) / length
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
for (let i in summaryData) {
|
||||
summaryData[i] =
|
||||
summaryPrefixText[i] + tool.groupSeparator(summaryData[i].toFixed(2)) + summarySuffixText[i]
|
||||
}
|
||||
|
||||
return [summaryData]
|
||||
}
|
||||
}
|
||||
|
||||
const resizeHandler = () => {
|
||||
headerHeight.value = crudHeaderRef.value.offsetHeight
|
||||
settingFixedPage()
|
||||
}
|
||||
|
||||
const tabChange = async (value) => {
|
||||
const searchKey = options.value.tabs?.searchKey ?? options.value.tabs?.dataIndex ?? "tabValue"
|
||||
const params = {}
|
||||
params[searchKey] = value
|
||||
requestParams.value = Object.assign(requestParams.value, params)
|
||||
await maEvent.customeEvent(options.value.tabs, value, "onChange")
|
||||
await refresh()
|
||||
}
|
||||
|
||||
const printTable = () => {
|
||||
new Print(crudContentRef.value)
|
||||
}
|
||||
|
||||
const openContextMenu = (ev, record) => {
|
||||
options.value?.contextMenu?.enabled === true && crudContextMenuRef.value.openContextMenu(ev, record)
|
||||
}
|
||||
|
||||
const execContextMenuCommand = async (args) => {
|
||||
const item = args.contextItem
|
||||
const record = args.record
|
||||
switch (item.operation) {
|
||||
case "print":
|
||||
await printTable()
|
||||
break
|
||||
case "refresh":
|
||||
await refresh()
|
||||
break
|
||||
case "add":
|
||||
addAction()
|
||||
break
|
||||
case "edit":
|
||||
editAction(record)
|
||||
break
|
||||
case "delete":
|
||||
crudColumnRef.value.deleteAction(record)
|
||||
break
|
||||
default:
|
||||
await maEvent.customeEvent(item, args, "onCommand")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const tabsHandler = async () => {
|
||||
// 处理tabs
|
||||
const tabs = options.value.tabs
|
||||
if (isFunction(tabs.data) || isArray(tabs.data)) {
|
||||
tabs.data = isFunction(tabs.data) ? await tabs.data() : tabs.data
|
||||
} else if (!isUndefined(tabs.dataIndex)) {
|
||||
const col = props.columns.find((item) => item.dataIndex === tabs.dataIndex)
|
||||
if (col.search === true && isObject(col.dict)) {
|
||||
tabs.data = dicts.value[tabs.dataIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (typeof options.value.autoRequest == "undefined" || options.value.autoRequest) {
|
||||
await requestData()
|
||||
}
|
||||
|
||||
if (!options.value.expandSearch && crudSearchRef.value) {
|
||||
crudSearchRef.value.setSearchHidden()
|
||||
}
|
||||
|
||||
if (options.value.pageLayout === "fixed") {
|
||||
window.addEventListener("resize", resizeHandler, false)
|
||||
headerHeight.value = crudHeaderRef.value.offsetHeight
|
||||
settingFixedPage()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (options.value.pageLayout === "fixed") {
|
||||
window.removeEventListener("resize", resizeHandler, false)
|
||||
}
|
||||
})
|
||||
|
||||
const getCurrentAction = () => crudFormRef.value.currentAction
|
||||
const getFormData = () => crudFormRef.value.form
|
||||
|
||||
const getFormColumns = async (type = "add") => {
|
||||
return await crudFormRef.value.getFormColumns(type)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取column属性服务类
|
||||
* @returns ColumnService
|
||||
*/
|
||||
const getColumnService = (strictMode = true) => {
|
||||
return new ColumnService({ columns: columns.value, cascaders: cascaders.value, dicts: dicts.value }, strictMode)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
refresh,
|
||||
requestData,
|
||||
addAction,
|
||||
editAction,
|
||||
getTableData,
|
||||
setSelecteds,
|
||||
getCurrentAction,
|
||||
getFormData,
|
||||
getFormColumns,
|
||||
getColumnService,
|
||||
requestParams,
|
||||
isRecovery,
|
||||
tableRef,
|
||||
crudFormRef,
|
||||
crudSearchRef,
|
||||
crudImportRef,
|
||||
crudSettingRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.__search-panel {
|
||||
transition: display 1s;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
._crud-footer {
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
111
cdTMP/src/components/ma-crud/js/columnService.js
Normal file
111
cdTMP/src/components/ma-crud/js/columnService.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import { loadDict } from "@cps/ma-form/js/networkRequest"
|
||||
|
||||
/**
|
||||
* columnService 列服务处理类
|
||||
* 首先感谢 @NEKGod 提交的PR,此功能原本写在了 Ma-Crud 组件,我特意摘出来,封装成类通过引用来调用
|
||||
* @author NEKGod, X.Mo <root@imoi.cn>
|
||||
*/
|
||||
|
||||
const objectService = function (item) {
|
||||
this.setAttr = (key, value) => {
|
||||
item[key] = value
|
||||
}
|
||||
|
||||
this.getAttr = (key) => {
|
||||
return item[key]
|
||||
}
|
||||
|
||||
this.get = () => {
|
||||
return item
|
||||
}
|
||||
|
||||
this.set = (config = {}) => {
|
||||
for (let [key, value] of Object.entries(config)) {
|
||||
item[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ColumnService {
|
||||
/**
|
||||
* @type {Map<string, Object>}
|
||||
*/
|
||||
columnMap = new Map()
|
||||
|
||||
columns
|
||||
|
||||
cascaders
|
||||
|
||||
dicts
|
||||
|
||||
strictMode
|
||||
|
||||
/**
|
||||
* @param data
|
||||
* @param strictMode
|
||||
*/
|
||||
constructor(data, strictMode) {
|
||||
this.columns = data.columns
|
||||
this.cascaders = data.cascaders
|
||||
this.dicts = data.dicts
|
||||
this.strictMode = strictMode
|
||||
|
||||
this.columns.forEach((item) => {
|
||||
this.columnMap.set(item.dataIndex, new objectService(item))
|
||||
})
|
||||
}
|
||||
|
||||
get(dataIndex) {
|
||||
return this.columnMap.get(dataIndex)
|
||||
}
|
||||
|
||||
isEmpty(dataIndex) {
|
||||
return !this.columnMap.has(dataIndex)
|
||||
}
|
||||
|
||||
exist(dataIndex) {
|
||||
return !this.isEmpty(dataIndex)
|
||||
}
|
||||
|
||||
async append(item, appendStartDataIndex = null) {
|
||||
if (this.strictMode === true && item.dataIndex && this.exist(item.dataIndex)) {
|
||||
console.warn(
|
||||
`严格模式:columnService.append(item) 参数中未有item.dataIndex属性或者item.dataIndex已存在column.${item.dataIndex}`
|
||||
)
|
||||
return false
|
||||
}
|
||||
if (this.cascaders.includes(item.dataIndex) && item.dict) {
|
||||
await loadDict(this.dicts, item)
|
||||
}
|
||||
this.columns.push(item)
|
||||
this.columnMap.set(item.dataIndex, new objectService(item))
|
||||
// 获取排序
|
||||
if (appendStartDataIndex !== null) {
|
||||
let appendIndex =
|
||||
this.columns
|
||||
.map((item) => {
|
||||
return item.dataIndex
|
||||
})
|
||||
?.indexOf(appendStartDataIndex) ?? -1
|
||||
if (appendIndex === -1) {
|
||||
return this.append(item, null)
|
||||
}
|
||||
let sortIndex = 0
|
||||
let appendPosIndex = 0
|
||||
this.columns.forEach((sortItem) => {
|
||||
if (sortItem.dataIndex === appendStartDataIndex) {
|
||||
appendPosIndex = sortIndex
|
||||
} else if (sortItem.dataIndex === item.dataIndex) {
|
||||
sortIndex = appendPosIndex + 1
|
||||
} else {
|
||||
}
|
||||
sortItem.sortIndex = sortIndex
|
||||
sortIndex += 2
|
||||
})
|
||||
this.columns.sort((a, b) => a.sortIndex - b.sortIndex)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export default ColumnService
|
||||
16
cdTMP/src/components/ma-crud/js/custom-render.js
Normal file
16
cdTMP/src/components/ma-crud/js/custom-render.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineComponent } from "vue"
|
||||
export default defineComponent({
|
||||
name: "CustomRender",
|
||||
props: {
|
||||
record: Object,
|
||||
render: Function,
|
||||
rowIndex: Number,
|
||||
column: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return this.render({ record: this.record, column: this.column, rowIndex: this.rowIndex })
|
||||
}
|
||||
})
|
||||
277
cdTMP/src/components/ma-crud/js/defaultOptions.js
Normal file
277
cdTMP/src/components/ma-crud/js/defaultOptions.js
Normal file
@@ -0,0 +1,277 @@
|
||||
export default {
|
||||
// 当前crud组件的 id,全局唯一,不指定则随机生成一个
|
||||
id: undefined,
|
||||
// 主键名称
|
||||
pk: "id",
|
||||
// 表单是否排除PK
|
||||
formExcludePk: true,
|
||||
// 请求api方法
|
||||
api: () => {},
|
||||
// 请求回收站api方法
|
||||
recycleApi: () => {},
|
||||
// 是否自动请求
|
||||
autoRequest: true,
|
||||
// 请求参数
|
||||
requestParams: {},
|
||||
// 设置分页组件每页记录数
|
||||
pageSizeOption: [10, 20, 30, 50, 100],
|
||||
// 是否开启表格分页
|
||||
tablePagination: false,
|
||||
// 设置选择列
|
||||
rowSelection: undefined,
|
||||
// 是否显示边框
|
||||
bordered: { wrapper: true, cell: false },
|
||||
// 是否开启拖拽排序
|
||||
dragSort: false,
|
||||
// 每页记录数
|
||||
pageSize: 10,
|
||||
// 子节点为空隐藏节点按钮
|
||||
hideExpandButtonOnEmpty: true,
|
||||
// 默认展开所有行
|
||||
expandAllRows: false,
|
||||
// 默认展开搜索
|
||||
expandSearch: true,
|
||||
// 斑马线
|
||||
stripe: true,
|
||||
// 新增、编辑、删除完成后是否刷新表格
|
||||
dataCompleteRefresh: true,
|
||||
// 表格大小
|
||||
size: "large",
|
||||
// 是否开启双击编辑数据
|
||||
isDbClickEdit: true,
|
||||
// 是否显示展开/折叠按钮
|
||||
isExpand: false,
|
||||
// 是否显示自定义
|
||||
showExpandRow: false,
|
||||
// 是否显示总结行
|
||||
showSummary: false,
|
||||
// 自定义总结行,要传入函数
|
||||
customerSummary: false,
|
||||
// 是否显示工具栏
|
||||
showTools: true,
|
||||
// 表头是否吸顶
|
||||
stickyHeader: true,
|
||||
// 页面布局方式,支持 normal(标准)和 fixed(固定)两种
|
||||
pageLayout: "normal",
|
||||
// 默认统一设置列宽度
|
||||
columnWidth: 100,
|
||||
// 搜索标签对齐方式
|
||||
searchLabelAlign: "right",
|
||||
// 全局搜索标签宽度
|
||||
searchLabelWidth: "85px",
|
||||
// 搜索每行列数
|
||||
searchColNumber: 4,
|
||||
// 搜索提交按钮文案
|
||||
searchSubmitButtonText: "搜索",
|
||||
// 搜索重置按钮文案
|
||||
searchResetButtonText: "重置",
|
||||
// 搜索栏加载提示文案
|
||||
searchLoadingText: "加载数据中...",
|
||||
|
||||
// 搜索提交前置方法
|
||||
beforeSearch: (requestParams) => {},
|
||||
// 搜索提交后置方法
|
||||
afterSearch: (requestParams) => {},
|
||||
|
||||
// 重置搜索钩子
|
||||
resetSearch: (searchData) => {},
|
||||
|
||||
// 请求前置处理
|
||||
beforeRequest: (requestParams) => {},
|
||||
// 请求后置处理
|
||||
afterRequest: (tableData) => {},
|
||||
// 新增打开前方法
|
||||
beforeOpenAdd: () => {},
|
||||
// 新增提交前方法
|
||||
beforeAdd: (formData) => {},
|
||||
// 新增提交后方法
|
||||
afterAdd: (response, formData) => {},
|
||||
// 编辑打开前方法
|
||||
beforeOpenEdit: (record) => {},
|
||||
// 编辑提交前方法
|
||||
beforeEdit: (formData) => {},
|
||||
// 编辑提交后方法
|
||||
afterEdit: (response, formData) => {},
|
||||
// 删除前方法
|
||||
beforeDelete: (ids) => {},
|
||||
// 删除后方法
|
||||
afterDelete: (response) => {},
|
||||
|
||||
// 组件初始化事件
|
||||
onInit: () => {},
|
||||
|
||||
// 列表 选项卡 参数配置项
|
||||
tabs: {
|
||||
// 选项卡类型,参考 arco 官方 tabs 的api
|
||||
type: "line",
|
||||
// 选项卡触发方式: click | hover
|
||||
trigger: "click",
|
||||
// 指定一个字段作为选项卡,该字段的 search 必须为 true, 并且使用了字典
|
||||
dataIndex: undefined,
|
||||
// 自定义选项卡项 [{ label: 'tab 1', value: 1, disabled: false }],也可函数返回一个数组
|
||||
data: undefined,
|
||||
// 默认选中的 tab
|
||||
defaultKey: undefined,
|
||||
// 切换选项卡时,请求后台数据的参数名
|
||||
searchKey: undefined,
|
||||
// 选项卡切换事件
|
||||
onChange: (value) => {},
|
||||
// 选项卡单击事件
|
||||
onClick: (value) => {}
|
||||
},
|
||||
|
||||
// 表单配置项
|
||||
formOption: {
|
||||
// 显示方式支持模态框和抽屉: modal drawer tag
|
||||
viewType: "modal",
|
||||
// 只有 viewType 为 tag 时生效,此值在所有 MaCrud 内唯一
|
||||
tagId: "",
|
||||
// 只有 viewType 为 tag 时生效,tag标题名称
|
||||
tagName: "",
|
||||
// tag页设置标签标题的字段名称
|
||||
titleDataIndex: undefined,
|
||||
// 显示宽度
|
||||
width: 600,
|
||||
// 是否全屏,只有modal有效
|
||||
isFull: false,
|
||||
// 表单布局
|
||||
layout: []
|
||||
},
|
||||
add: {
|
||||
// 新增api
|
||||
api: undefined,
|
||||
// 显示新增按钮的权限
|
||||
auth: [],
|
||||
// 显示新增按钮的角色
|
||||
role: [],
|
||||
// 按钮文案
|
||||
text: "新增",
|
||||
// 是否显示
|
||||
show: false
|
||||
},
|
||||
edit: {
|
||||
// 编辑api
|
||||
api: undefined,
|
||||
// 显示编辑按钮的权限
|
||||
auth: [],
|
||||
// 显示编辑按钮的角色
|
||||
role: [],
|
||||
// 按钮文案
|
||||
text: "编辑",
|
||||
// 是否显示
|
||||
show: false
|
||||
},
|
||||
delete: {
|
||||
// 删除api
|
||||
api: undefined,
|
||||
// 显示删除按钮的权限
|
||||
auth: [],
|
||||
// 显示删除按钮的角色
|
||||
role: [],
|
||||
// 按钮文案
|
||||
text: "删除",
|
||||
|
||||
// 真实删除api
|
||||
realApi: undefined,
|
||||
// 显示真实删除按钮的权限
|
||||
realAuth: [],
|
||||
// 显示真实删除按钮的角色
|
||||
realRole: [],
|
||||
// 真实按钮文案
|
||||
realText: "删除",
|
||||
|
||||
// 是否显示
|
||||
show: false
|
||||
},
|
||||
recovery: {
|
||||
// 恢复api
|
||||
api: undefined,
|
||||
// 显示恢复按钮的权限
|
||||
auth: [],
|
||||
// 显示恢复按钮的角色
|
||||
role: [],
|
||||
// 按钮文案
|
||||
text: "恢复",
|
||||
// 是否显示
|
||||
show: false
|
||||
},
|
||||
// see: {
|
||||
// // 显示查看按钮的权限
|
||||
// auth: [],
|
||||
// // 显示查看按钮的角色
|
||||
// role: [],
|
||||
// // 按钮文案
|
||||
// text: '查看',
|
||||
// // 是否显示
|
||||
// show: false,
|
||||
// },
|
||||
import: {
|
||||
// 导入url
|
||||
url: undefined,
|
||||
// 下载模板地址
|
||||
templateUrl: undefined,
|
||||
// 显示导入按钮的权限
|
||||
auth: [],
|
||||
// 显示导入按钮的角色
|
||||
role: [],
|
||||
// 按钮文案
|
||||
text: "导入",
|
||||
// 是否显示
|
||||
show: false
|
||||
},
|
||||
export: {
|
||||
// 导出url
|
||||
url: undefined,
|
||||
// 显示导出按钮的权限
|
||||
auth: [],
|
||||
// 显示导出按钮的角色
|
||||
role: [],
|
||||
// 按钮文案
|
||||
text: "导出",
|
||||
// 是否显示
|
||||
show: false
|
||||
},
|
||||
// 行自定义 class 名称
|
||||
rowCustomClass: (record, rowIndex) => [],
|
||||
// 是否显示索引列
|
||||
showIndex: false,
|
||||
// 索引列名称
|
||||
indexLabel: "序号",
|
||||
// 索引列宽度
|
||||
indexColumnWidth: 70,
|
||||
// 索引列固定方向,false 为不固定
|
||||
indexColumnFixed: "left",
|
||||
// 设置请求数据label
|
||||
requestParamsLabel: undefined,
|
||||
// 表格滚动默认宽高
|
||||
scroll: { x: "100%", y: "100%" },
|
||||
// 调整列宽
|
||||
resizable: true,
|
||||
// 是否显示操作列
|
||||
operationColumn: false,
|
||||
// 操作列宽度
|
||||
operationWidth: 160,
|
||||
// 操作列宽度 (新api)
|
||||
operationColumnWidth: 160,
|
||||
// 操作列名称
|
||||
operationColumnText: "操作",
|
||||
// 操作列文字对齐方式
|
||||
operationColumnAlign: "right",
|
||||
// 操作列固定方向,false 为不固定
|
||||
operationColumnFixed: "right",
|
||||
|
||||
// 右键菜单配置
|
||||
contextMenu: {
|
||||
// 是否开启右键菜单
|
||||
enabled: true,
|
||||
// 右键菜单配置
|
||||
items: [
|
||||
{ operation: "print" },
|
||||
{ operation: "refresh" },
|
||||
{ operation: "divider" },
|
||||
{ operation: "add" },
|
||||
{ operation: "edit" },
|
||||
{ operation: "delete" }
|
||||
]
|
||||
}
|
||||
}
|
||||
116
cdTMP/src/components/ma-crud/types/columns.ts
Normal file
116
cdTMP/src/components/ma-crud/types/columns.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { VNodeChild } from "vue"
|
||||
|
||||
/**
|
||||
* 表单组件类型
|
||||
*/
|
||||
import { FieldRule } from "@arco-design/web-vue"
|
||||
|
||||
export type FormDateType =
|
||||
| "radio"
|
||||
| "checkbox"
|
||||
| "select"
|
||||
| "transfer"
|
||||
| "tree-select"
|
||||
| "cascader"
|
||||
| "date"
|
||||
| "month"
|
||||
| "year"
|
||||
| "week"
|
||||
| "quarter"
|
||||
| "range"
|
||||
| "time"
|
||||
| "input"
|
||||
| "password"
|
||||
| "textarea"
|
||||
| "upload"
|
||||
| "select-user"
|
||||
| "editor"
|
||||
| "code-editor"
|
||||
| "icon"
|
||||
| "user-info"
|
||||
| "city-linkage"
|
||||
| "form-group"
|
||||
| "select-resource"
|
||||
/**
|
||||
* 列字典
|
||||
*/
|
||||
export interface ColumnDict {
|
||||
// 字典名称,快捷查询字典接口查询
|
||||
name?: string
|
||||
// 自定义url查询
|
||||
url?: string
|
||||
// url查询方法,填写url之后生效
|
||||
method?: "GET" | "POST" | "PUT" | "DELETE"
|
||||
// url查询params数据,填写url之后生效
|
||||
params?: object
|
||||
// url查询body数据,填写url之后生效
|
||||
body?: object
|
||||
// 直接设置字典值
|
||||
data?: object | Function
|
||||
// 表格列的值是否翻译为字典对应标签
|
||||
translation?: boolean
|
||||
// 表格key 和 value的props设置
|
||||
props?: {
|
||||
label?: string
|
||||
value?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface BasicColumn {
|
||||
// 标题
|
||||
title: string
|
||||
// 字段名称
|
||||
dataIndex: string
|
||||
// 组件类型
|
||||
formType?: FormDateType
|
||||
// 表格列对齐方式
|
||||
align?: "center" | "right" | "left"
|
||||
// 字段是否加入搜索
|
||||
search?: boolean
|
||||
// 列宽
|
||||
width?: number | "auto"
|
||||
// 表格列是否隐藏
|
||||
hide?: boolean
|
||||
// 编辑|创建 通用是否显示字段
|
||||
display?: boolean
|
||||
// 添加弹窗是否显示字段
|
||||
addDisplay?: boolean
|
||||
// 编辑弹窗是否显示字段
|
||||
editDisplay?: boolean
|
||||
// 编辑|创建 通用是否禁用字段
|
||||
disabled?: boolean
|
||||
// 添加弹窗是否禁用字段
|
||||
addDisabled?: boolean
|
||||
// 编辑弹窗是否禁用字段
|
||||
editDisabled?: boolean
|
||||
// 编辑|创建 通用是否只读字段
|
||||
readonly?: boolean
|
||||
// 添加弹窗是否只读字段
|
||||
addReadonly?: boolean
|
||||
// 编辑弹窗是否只读字段
|
||||
editReadonly?: boolean
|
||||
// 自定义渲染
|
||||
customRender?: (({ record, column, rowIndex }) => VNodeChild | JSX.Element) | VNodeChild | JSX.Element
|
||||
// 字段新增时默认值
|
||||
addDefaultValue?: number | string | boolean | undefined | ((record) => number | string | boolean | undefined)
|
||||
// 字段编辑时默认值
|
||||
editDefaultValue?: number | string | boolean | undefined | ((record) => number | string | boolean | undefined)
|
||||
// select,radio,treeSelect,下拉字典配置
|
||||
dict?: ColumnDict
|
||||
// 继承公用配置
|
||||
common?: boolean
|
||||
// select 和 tree-select 组件是否开启虚拟列表
|
||||
virtualList?: boolean
|
||||
// 搜索默认值
|
||||
searchDefaultValue?: number | string | undefined
|
||||
// 搜索描述
|
||||
searchPlaceholder?: string
|
||||
//编辑|创建 通用规则
|
||||
commonRules?: FieldRule | FieldRule[]
|
||||
// 创建时规则
|
||||
addRules?: FieldRule | FieldRule[]
|
||||
// 编辑时规则
|
||||
editRules?: FieldRule | FieldRule[]
|
||||
// 子表单数据
|
||||
children?: BasicColumn[]
|
||||
}
|
||||
189
cdTMP/src/components/ma-crud/types/crud.ts
Normal file
189
cdTMP/src/components/ma-crud/types/crud.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
export interface BasicCrud {
|
||||
// 表格接口
|
||||
api?: undefined | any
|
||||
// 主键名称
|
||||
pk?: string
|
||||
// 设置选择列
|
||||
rowSelection?:
|
||||
| undefined
|
||||
| {
|
||||
// 选择值的标识,默认id
|
||||
key?: string
|
||||
// 选择列是否显示全选
|
||||
showCheckedAll?: boolean
|
||||
// 行选择器类型
|
||||
type?: "checkbox" | "radio"
|
||||
// 选择器列标题
|
||||
title?: string | "#"
|
||||
// 列宽度
|
||||
width?: number | 60
|
||||
// 是否固定
|
||||
fixed?: boolean | false
|
||||
// 是否仅展示当前页的keys
|
||||
onlyCurrent?: boolean | true
|
||||
}
|
||||
// 搜索label宽度
|
||||
searchLabelWidth?: string | "auto"
|
||||
// 搜索label对齐方式
|
||||
searchLabelAlign?: "center" | "right" | "left"
|
||||
// 一行多少列
|
||||
searchLabelCols?: number
|
||||
// 是否显示边框
|
||||
bordered?: { wrapper?: boolean; cell?: boolean }
|
||||
// 是否开启拖拽排序
|
||||
dragSort?: boolean
|
||||
// 子节点为空隐藏节点按钮
|
||||
hideExpandButtonOnEmpty?: boolean
|
||||
// 默认展开所有行
|
||||
expandAllRows?: boolean
|
||||
// 斑马线
|
||||
stripe?: boolean
|
||||
// 新增、编辑、删除完成后是否刷新表格
|
||||
dataCompleteRefresh?: boolean
|
||||
// 表格大小
|
||||
size?: "mini" | "small" | "medium" | "large"
|
||||
// 是否开启双击编辑数据
|
||||
isDbClickEdit?: boolean
|
||||
// 是否显示展开/折叠按钮
|
||||
isExpand?: boolean
|
||||
// 是否显示自定义
|
||||
showExpandRow?: boolean
|
||||
// 是否显示总结行
|
||||
showSummary?: boolean
|
||||
// 自定义总结行,要传入函数
|
||||
customerSummary?: boolean
|
||||
// 是否显示工具栏
|
||||
showTools?: boolean
|
||||
// 新增和编辑显示设置
|
||||
formOption?: {
|
||||
// 显示方式支持模态框和抽屉?: modal drawer
|
||||
viewType?: "modal" | "drawer"
|
||||
// 显示宽度
|
||||
width?: number
|
||||
// 是否全屏,只有modal有效
|
||||
isFull?: boolean
|
||||
}
|
||||
//新增确定之前修改form值
|
||||
beforeAdd?: (form) => void
|
||||
//新增确定之后调用,返回接口response和form值
|
||||
afterAdd?: (response, form) => void
|
||||
//编辑确定之前修改form值
|
||||
beforeEdit?: (form) => void
|
||||
//编辑确定之后调用,返回接口response和form值
|
||||
afterEdit?: (response, form) => void
|
||||
add?: {
|
||||
// 新增api
|
||||
api?: undefined | any
|
||||
// 显示新增按钮的权限
|
||||
auth?: string[]
|
||||
// 显示新增按钮的角色
|
||||
role?: string[]
|
||||
// 按钮文案
|
||||
text?: string
|
||||
// 是否显示
|
||||
show?: boolean
|
||||
}
|
||||
edit?: {
|
||||
// 编辑api
|
||||
api?: undefined | any
|
||||
// 显示编辑按钮的权限
|
||||
auth?: string[]
|
||||
// 显示编辑按钮的角色
|
||||
role?: string[]
|
||||
// 按钮文案
|
||||
text?: string
|
||||
// 是否显示
|
||||
show?: boolean
|
||||
}
|
||||
delete?: {
|
||||
// 删除api
|
||||
api?: undefined | any
|
||||
// 显示删除按钮的权限
|
||||
auth?: string[]
|
||||
// 显示删除按钮的角色
|
||||
role?: string[]
|
||||
// 按钮文案
|
||||
text?: string
|
||||
|
||||
// 真实删除api
|
||||
realApi?: undefined | any
|
||||
// 显示真实删除按钮的权限
|
||||
realAuth?: string[]
|
||||
// 显示真实删除按钮的角色
|
||||
realRole?: string[]
|
||||
// 真实按钮文案
|
||||
realText?: string
|
||||
|
||||
// 是否显示
|
||||
show?: boolean
|
||||
}
|
||||
// Todo
|
||||
recycleApi?: any
|
||||
recovery?: {
|
||||
// 显示恢复按钮的权限
|
||||
auth?: string[]
|
||||
// 显示恢复按钮的角色
|
||||
role?: string[]
|
||||
// 按钮文案
|
||||
text?: string
|
||||
// 是否显示
|
||||
show?: boolean
|
||||
// 恢复列表查询api
|
||||
api?: undefined | any
|
||||
}
|
||||
// see?: {
|
||||
// // 显示查看按钮的权限
|
||||
// auth?: string[]
|
||||
// // 显示查看按钮的角色
|
||||
// role?: string[]
|
||||
// // 按钮文案
|
||||
// text?: string
|
||||
// // 是否显示
|
||||
// show?: boolean
|
||||
// }
|
||||
import?: {
|
||||
// 导入url
|
||||
url?: undefined | any
|
||||
// 下载模板地址
|
||||
templateUrl?: undefined | any
|
||||
// 显示导入按钮的权限
|
||||
auth?: string[]
|
||||
// 显示导入按钮的角色
|
||||
role?: string[]
|
||||
// 按钮文案
|
||||
text?: string
|
||||
// 是否显示
|
||||
show?: boolean
|
||||
}
|
||||
export?: {
|
||||
// 导出url
|
||||
url?: undefined | any
|
||||
// 显示导出按钮的权限
|
||||
auth?: string[]
|
||||
// 显示导出按钮的角色
|
||||
role?: string[]
|
||||
// 按钮文案
|
||||
text?: string
|
||||
// 是否显示
|
||||
show?: boolean
|
||||
}
|
||||
// 是否显示索引列
|
||||
showIndex?: boolean
|
||||
// 索引列名称
|
||||
indexLabel?: string
|
||||
// 设置请求数据label
|
||||
requestParamsLabel?: undefined
|
||||
// 表格滚动默认宽高
|
||||
scroll?: {
|
||||
x?: string
|
||||
y?: string
|
||||
}
|
||||
// 调整列宽
|
||||
resizable?: boolean
|
||||
// 是否显示操作列
|
||||
operationColumn?: boolean
|
||||
// 操作列宽度
|
||||
operationWidth?: number
|
||||
// 操作列名称
|
||||
operationColumnText?: string
|
||||
}
|
||||
2
cdTMP/src/components/ma-crud/types/index.ts
Normal file
2
cdTMP/src/components/ma-crud/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./columns"
|
||||
export * from "./crud"
|
||||
Reference in New Issue
Block a user