Pre Merge pull request !147 from 孙志强/code_gen

This commit is contained in:
孙志强 2024-09-22 06:01:36 +00:00 committed by Gitee
commit 4668af3568
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
13 changed files with 1121 additions and 0 deletions

View File

@ -57,3 +57,43 @@ export interface TenantInfo {
tenantEnabled: boolean;
voList: TenantVO[];
}
/**
*
*/
export interface Column {
label: string;
prop: string;
align?: 'left' | 'center' | 'right';
slot?: string;
}
/**
*
*/
export interface Button {
text: string;
type: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text';
icon: string;
handler: () => void;
permission: string[];
disabledCondition?: (ids: (string | number)[]) => boolean;
}
/**
*
*/
export interface Operation {
text: string;
type: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text';
icon: string;
handler: (row: any) => void;
permission: string[];
needConfirm?: boolean;
}
export interface QueryParams {
pageNum: number;
pageSize: number;
[key: string]: any;
}

View File

@ -0,0 +1,72 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<el-checkbox-group v-bind="$attrs">
<el-checkbox
v-for="item in computedItems"
:key="item.value"
:label="item.label"
:value="item.value"
></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const _props = defineProps({
label: String,
prop: String,
/**
* 每行显示几个, 默认24
*/
colSpan: {
type: Number,
default: 24
},
/**
* 传入的标准数据项
*/
items: {
type: Array as () => any[],
default: () => []
},
/**
* 传入的字典数据
*/
dicts: {
type: Array as () => any[],
default: () => []
},
/**
* 自定义配置项仅当 dicts 为空时使用
* 默认配置: { label: 'label', value: 'value' }
*/
props: {
type: Object,
default: () => ({
label: 'label',
value: 'value'
})
}
})
// 使
const computedItems = computed(() => {
if (_props.dicts.length > 0) {
return _props.dicts.map((dict) => ({
label: dict.label,
value: dict.value
}))
} else {
return _props.items.map((item) => ({
label: item[_props.props.label],
value: item[_props.props.value]
}))
}
})
</script>

View File

@ -0,0 +1,23 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<el-date-picker v-bind="$attrs">
<template v-for="(_, name) in $slots" #[name]="slotProps">
<slot :name="name" v-bind="slotProps"></slot>
</template>
</el-date-picker>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
interface Props {
label?: string
prop?: string
colSpan?: number
}
withDefaults(defineProps<Props>(), {
colSpan: 24
})
</script>

View File

@ -0,0 +1,26 @@
<template>
<el-drawer v-bind="$attrs" :size="drawerSize" :show-close="false" append-to-body>
<template #header="{ close, titleId, titleClass }">
<h4 :id="titleId" :class="titleClass">{{ title }}</h4>
<el-button @click="toggleDrawerSize" icon="FullScreen" link></el-button>
<el-button @click="close" link icon="Close"></el-button>
</template>
<template v-for="(_value, name) in $slots" #[name]="scopeData">
<slot :name="name" v-bind="scopeData || {}"></slot>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { useDrawerSize } from './useDrawerSize';
const props = defineProps({
title: String,
initialSize: {
type: Number,
default: 800
}
});
const { drawerSize, toggleDrawerSize } = useDrawerSize(props.initialSize);
</script>

View File

@ -0,0 +1,18 @@
import { ref, computed } from 'vue';
export function useDrawerSize(initialSize = 800) {
const drawerSize = ref(initialSize);
const maxDrawerSize = document.body.clientWidth;
const isFullSize = computed(() => drawerSize.value === maxDrawerSize);
const toggleDrawerSize = () => {
drawerSize.value = isFullSize.value ? initialSize : maxDrawerSize;
};
return {
drawerSize: computed(() => `${drawerSize.value}px`),
isFullSize,
toggleDrawerSize
};
}

View File

@ -0,0 +1,163 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<div>
<el-upload
v-if="type === 'url'"
:action="upload.url"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
class="editor-img-uploader"
name="file"
:show-file-list="false"
:headers="upload.headers"
ref="uploadRef"
/>
<div class="editor">
<quill-editor
ref="quillEditorRef"
v-model:content="content"
contentType="html"
@textChange="handleTextChange"
:options="options"
:style="styles"
v-bind="$attrs"
/>
</div>
</div>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { QuillEditor, Quill } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { propTypes } from '@/utils/propTypes'
import { globalHeaders } from '@/utils/request'
import { computed, ref, watch, reactive, toRaw } from 'vue'
import { getCurrentInstance } from 'vue'
const props = defineProps({
label: String,
prop: String,
colSpan: {
type: Number,
default: 24
},
modelValue: propTypes.string,
height: propTypes.number.def(400),
minHeight: propTypes.number.def(400),
readOnly: propTypes.bool.def(false),
fileSize: propTypes.number.def(5),
type: propTypes.string.def('url')
})
const emit = defineEmits(['update:modelValue'])
const { proxy } = getCurrentInstance()
const upload = reactive({
headers: globalHeaders(),
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
})
const quillEditorRef = ref()
const content = ref('')
const options = computed(() => ({
theme: 'snow',
bounds: document.body,
debug: 'warn',
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ align: [] }],
['clean'],
['link', 'image', 'video']
],
handlers: {
image: () => {
const uploadElement = document.querySelector('.editor-img-uploader>.el-upload') as HTMLElement
uploadElement?.click()
}
}
}
},
placeholder: '请输入内容',
readOnly: props.readOnly
}))
const styles = computed(() => ({
minHeight: props.minHeight ? `${props.minHeight}px` : undefined,
height: props.height ? `${props.height}px` : undefined
}))
watch(
() => props.modelValue,
(newValue) => {
if (newValue !== content.value) {
content.value = newValue ?? '<p></p>'
}
},
{ immediate: true }
)
const handleTextChange = () => emit('update:modelValue', content.value)
const handleUploadSuccess = (res: any) => {
if (res.code === 200) {
const quill = toRaw(quillEditorRef.value).getQuill()
const length = quill.selection.savedRange.index
quill.insertEmbed(length, 'image', res.data.url)
quill.setSelection(length + 1)
proxy?.$modal.closeLoading()
} else {
proxy?.$modal.msgError(res.msg)
}
}
const handleBeforeUpload = (file: File) => {
const isValidType = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'].includes(file.type)
if (!isValidType) {
proxy?.$modal.msgError(`图片格式错误!`)
return false
}
const isValidSize = file.size / 1024 / 1024 < props.fileSize
if (!isValidSize) {
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`)
return false
}
proxy?.$modal.loading('正在上传文件,请稍候...')
return true
}
const handleUploadError = (err: any) => {
console.error(err)
proxy?.$modal.msgError('上传文件失败')
}
</script>
<style>
.editor-img-uploader {
display: none;
}
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
}
/* 其他样式保持不变 */
</style>

View File

@ -0,0 +1,231 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<div class="upload-file">
<el-upload
multiple
:action="uploadFileUrl"
:before-upload="handleBeforeUpload"
:file-list="fileList"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
class="upload-file-uploader"
ref="fileUploadRef"
v-bind="$attrs"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
<!-- 文件列表 -->
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<el-link :href="`${file.url}`" :underline="false" target="_blank">
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
</div>
</li>
</transition-group>
</div>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { listByIds, delOss } from "@/api/system/oss";
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from "@/utils/request";
const props = defineProps({
label: String,
prop: String,
/**
* 每行显示几个, 默认24
*/
colSpan: {
type: Number,
default: 24
},
modelValue: [String, Object, Array],
//
limit: propTypes.number.def(5),
// (MB)
fileSize: propTypes.number.def(5),
// , ['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(["doc", "xls", "ppt", "txt", "pdf"]),
//
isShowTip: propTypes.bool.def(true),
})
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emit = defineEmits(['update:modelValue']);
const number = ref(0);
const uploadList = ref<any[]>([]);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = ref(baseUrl + "/resource/oss/upload"); //
const headers = ref(globalHeaders());
const fileList = ref<any[]>([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
const fileUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
if (val) {
let temp = 1;
//
let list = [];
if (Array.isArray(val)) {
list = val;
} else {
const res = await listByIds(val as string)
list = res.data.map((oss) => {
const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
return data;
});
}
//
fileList.value = list.map(item => {
item = { name: item.name, url: item.url, ossId: item.ossId };
item.uid = item.uid || new Date().getTime() + temp++;
return item;
});
} else {
fileList.value = [];
return [];
}
}, { deep: true, immediate: true });
//
const handleBeforeUpload = (file: any) => {
//
if (props.fileType.length) {
const fileName = file.name.split('.');
const fileExt = fileName[fileName.length - 1];
const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
if (!isTypeOk) {
proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
return false;
}
}
//
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy?.$modal.loading("正在上传文件,请稍候...");
number.value++;
return true;
}
//
const handleExceed = () => {
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
//
const handleUploadError = () => {
proxy?.$modal.msgError("上传文件失败");
}
//
const handleUploadSuccess = (res: any, file: UploadFile) => {
if (res.code === 200) {
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
uploadedSuccessfully();
} else {
number.value--;
proxy?.$modal.closeLoading();
proxy?.$modal.msgError(res.msg);
fileUploadRef.value?.handleRemove(file);
uploadedSuccessfully();
}
}
//
const handleDelete = (index: number) => {
let ossId = fileList.value[index].ossId;
delOss(ossId);
fileList.value.splice(index, 1);
emit("update:modelValue", listToString(fileList.value));
}
//
const uploadedSuccessfully = () => {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy?.$modal.closeLoading();
}
}
//
const getFileName = (name: string) => {
// url
if (name.lastIndexOf("/") > -1) {
return name.slice(name.lastIndexOf("/") + 1);
} else {
return name;
}
}
//
const listToString = (list: any[], separator?: string) => {
let strs = "";
separator = separator || ",";
list.forEach(item => {
if (item.ossId) {
strs += item.ossId + separator;
}
})
return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,230 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<div class="component-upload-image">
<el-upload
multiple
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
ref="imageUpload"
:before-remove="handleDelete"
:show-file-list="true"
:headers="headers"
:file-list="fileList"
:on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= limit }"
v-bind="$attrs"
>
<el-icon class="avatar-uploader-icon">
<plus />
</el-icon>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" v-if="showTip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
</el-dialog>
</div>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance } from "vue";
import { OssVO } from "@/api/system/oss/types";
import { propTypes } from '@/utils/propTypes';
import {globalHeaders} from "@/utils/request";
const props = defineProps({
label: String,
prop: String,
/**
* 每行显示几个, 默认24
*/
colSpan: {
type: Number,
default: 24
},
modelValue: [String, Object, Array],
//
limit: propTypes.number.def(5),
// (MB)
fileSize: propTypes.number.def(5),
// , ['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(["png", "jpg", "jpeg"]),
//
isShowTip: {
type: Boolean,
default: true
},
})
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emit = defineEmits(['update:modelValue']);
const number = ref(0);
const uploadList = ref<any[]>([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(baseUrl + "/resource/oss/upload"); //
const headers = ref(globalHeaders());
const fileList = ref<any[]>([]);
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
);
const imageUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
if (val) {
//
let list: OssVO[] = [];
if (Array.isArray(val)) {
list = val as OssVO[];
} else {
const res = await listByIds(val as string)
list = res.data
}
//
fileList.value = list.map(item => {
// url id
let itemData;
if (typeof item === "string") {
itemData = { name: item, url: item };
} else {
// name使ossId
itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
}
return itemData;
});
} else {
fileList.value = [];
return [];
}
}, { deep: true, immediate: true });
/** 上传前loading加载 */
const handleBeforeUpload = (file: any) => {
let isImg = false;
if (props.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
isImg = props.fileType.some((type: any) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
} else {
isImg = file.type.indexOf("image") > -1;
}
if (!isImg) {
proxy?.$modal.msgError(
`文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
);
return false;
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy?.$modal.loading("正在上传图片,请稍候...");
number.value++;
}
//
const handleExceed = () => {
proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
}
//
const handleUploadSuccess = (res: any, file: UploadFile) => {
if (res.code === 200) {
uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
uploadedSuccessfully();
} else {
number.value--;
proxy?.$modal.closeLoading();
proxy?.$modal.msgError(res.msg);
imageUploadRef.value?.handleRemove(file);
uploadedSuccessfully();
}
}
//
const handleDelete = (file: UploadFile): boolean => {
const findex = fileList.value.map(f => f.name).indexOf(file.name);
if (findex > -1 && uploadList.value.length === number.value) {
let ossId = fileList.value[findex].ossId;
delOss(ossId);
fileList.value.splice(findex, 1);
emit("update:modelValue", listToString(fileList.value));
return false;
}
return true;
}
//
const uploadedSuccessfully = () => {
if (number.value > 0 && uploadList.value.length === number.value) {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
emit("update:modelValue", listToString(fileList.value));
proxy?.$modal.closeLoading();
}
}
//
const handleUploadError = () => {
proxy?.$modal.msgError("上传图片失败");
proxy?.$modal.closeLoading();
}
//
const handlePictureCardPreview = (file: any) => {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
}
//
const listToString = (list: any[], separator?: string) => {
let strs = "";
separator = separator || ",";
for (let i in list) {
if (undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
strs += list[i].ossId + separator;
}
}
return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
</script>
<style scoped lang="scss">
// .el-upload--picture-card
:deep(.hide .el-upload--picture-card) {
display: none;
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<el-input v-bind="$attrs" :placeholder="computedPlaceholder">
<template v-for="(_, name) in $slots" #[name]="slotProps">
<slot :name="name" v-bind="slotProps"></slot>
</template>
</el-input>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
interface Props {
label?: string
prop?: string
colSpan?: number
placeholder?: string
}
const props = withDefaults(defineProps<Props>(), {
colSpan: 24,
placeholder: ''
})
const computedPlaceholder = computed(() => {
return props.placeholder || (props.label ? `请输入${props.label}` : '')
})
</script>

View File

@ -0,0 +1,65 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<el-radio-group v-bind="$attrs">
<el-radio v-for="item in computedItems" :key="item.value" :label="item.label" :value="item.value"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const _props = defineProps({
label: String,
prop: String,
/**
* 每行显示几个, 默认24
*/
colSpan: {
type: Number,
default: 24
},
/**
* 传入的标准数据项
*/
items: {
type: Array as () => any[],
default: () => []
},
/**
* 传入的字典数据
*/
dicts: {
type: Array as () => any[],
default: () => []
},
/**
* 自定义配置项仅当 dicts 为空时使用
* 默认配置: { label: 'label', value: 'value' }
*/
props: {
type: Object,
default: () => ({
label: 'label',
value: 'value'
})
}
})
// 使
const computedItems = computed(() => {
if (_props.dicts.length > 0) {
return _props.dicts.map((dict) => ({
label: dict.label,
value: dict.value
}))
} else {
return _props.items.map((item) => ({
label: item[_props.props.label],
value: item[_props.props.value]
}))
}
})
</script>

View File

@ -0,0 +1,69 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<el-select v-bind="$attrs">
<el-option v-for="item in computedItems" :value="item[props.value]" :label="item[props.label]"></el-option>
<template v-for="(_value, name) in $slots" #[name]="scopeData">
<slot :name="name" v-bind="scopeData"></slot>
</template>
</el-select>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const _props = defineProps({
label: String,
prop: String,
/**
* 每行显示几个, 默认24
*/
colSpan: {
type: Number,
default: 24
},
/**
* 传入的标准数据项
*/
items: {
type: Array as () => any[],
default: () => []
},
/**
* 传入的字典数据
*/
dicts: {
type: Array as () => any[],
default: () => []
},
/**
* 自定义配置项仅当 dicts 为空时使用
* 默认配置: { label: 'label', value: 'value' }
*/
props: {
type: Object,
default: () => ({
label: 'label',
value: 'value'
})
}
})
// 使
const computedItems = computed(() => {
if (_props.dicts.length > 0) {
return _props.dicts.map((dict) => ({
label: dict.label,
value: dict.value
}))
} else {
return _props.items.map((item) => ({
label: item[_props.props.label],
value: item[_props.props.value]
}))
}
})
</script>

View File

@ -0,0 +1,132 @@
<template>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-for="button in buttons" :key="button.text">
<el-button
:type="button.type"
plain
:icon="button.icon"
:disabled="button.disabledCondition ? button.disabledCondition(selectedIds) : false"
@click="() => handleButtonClick(button.handler)"
v-if="button.permission && button.permission.length > 0"
v-hasPermi="button.permission"
>
{{ button.text }}
</el-button>
<el-button
v-else
:type="button.type"
plain
:icon="button.icon"
:disabled="button.disabledCondition ? button.disabledCondition(selectedIds) : false"
@click="() => handleButtonClick(button.handler)"
>
{{ button.text }}
</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="$emit('query')"></right-toolbar>
</el-row>
</template>
<el-table ref="tableRef" v-loading="loading" :data="data" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="50" align="center" />
<el-table-column v-for="column in columns" :key="column.prop" v-bind="column">
<template #default="scope" v-if="column.slot">
<slot :name="column.slot" :row="scope.row"></slot>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" v-if="operations.length">
<template #default="scope">
<el-tooltip v-for="op in operations" :key="op.text" :content="op.text" placement="top">
<el-button
link
:type="op.type"
:icon="op.icon"
@click="() => handleOperation(op, scope.row)"
v-if="op.permission && op.permission.length > 0"
v-hasPermi="op.permission"
></el-button>
<el-button
v-else
link
:type="op.type"
:icon="op.icon"
@click="() => handleOperation(op, scope.row)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="$emit('query')"
/>
</el-card>
</template>
<script setup lang="ts">
import { Button, Column, Operation, QueryParams } from '@/api/types'
import { ref } from 'vue'
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const props = defineProps<{
loading: boolean
data: any[]
total: number
queryParams: QueryParams
columns: Column[]
buttons: Button[]
operations: Operation[]
clearSelection: boolean
}>()
const emit = defineEmits<{
(e: 'update:selectedIds', ids: (string | number)[]): void
(e: 'query'): void
(e: 'update:clearSelection', clearSelection: boolean): void
}>()
const showSearch = ref(true)
const selectedIds = ref<(string | number)[]>([])
const handleSelectionChange = (selection: any[]) => {
selectedIds.value = selection.map((item) => item.id)
emit('update:selectedIds', selectedIds.value)
}
const handleOperation = async (op: Operation, row: any) => {
if (op.needConfirm) {
try {
await proxy?.$modal.confirm(`确定要${op.text}吗?`)
op.handler(row)
} catch (error) {
console.log('Operation cancelled')
}
} else {
op.handler(row)
}
}
const handleButtonClick = (handler: () => void) => {
handler()
}
const tableRef = ref()
watch(
() => props.clearSelection,
(newValue) => {
if (newValue) {
tableRef.value?.clearSelection()
selectedIds.value = []
emit('update:selectedIds', [])
emit('update:clearSelection', false)
}
}
)
</script>

View File

@ -0,0 +1,21 @@
<template>
<el-col :span="colSpan">
<el-form-item :label="label" :prop="prop">
<el-tree-select v-bind="$attrs"></el-tree-select>
</el-form-item>
</el-col>
</template>
<script lang="ts" setup>
defineProps({
label: String,
prop: String,
/**
* 每行显示几个, 默认24
*/
colSpan: {
type: Number,
default: 24
}
});
</script>