feat:组件二次封装

main
向文可 2 years ago
parent 7160ed97ac
commit ca7751ce6a

@ -0,0 +1,44 @@
{
// Place your shop-admin 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"import element plus component": {
"scope": "javascript,typescript",
"prefix": "import ele",
"body": [
"import { ${1:El} } from 'element-plus/es/components/$2/index';",
"import 'element-plus/es/components/$2/style/css';"
],
"description": "按需引用ElementPlus组件及其样式"
},
"vbase extra component": {
"scope": "vue",
"prefix": "vbase extra",
"body": [
"<template>\n<component :is=\"render\" />\n</template>",
"<script setup lang=\"jsx\">",
"import { ${1:El} } from 'element-plus/es/components/$2/index';",
"import 'element-plus/es/components/$2/style/css';",
"const props = defineProps({});",
"const attrs = useAttrs();",
"const slots = useSlots();",
"const render = () => <$1 {...props} {...attrs} v-slots={slots} />;",
"</script>",
"<style lang=\"less\" scoped></style>"
],
"description": "快速二次封装ElementPlus组件"
}
}

@ -15,7 +15,7 @@
const route = useRoute();
const config = reactive({
locale: zh,
size: 'small',
size: 'default',
zIndex: 300,
button: {
autoInsertSpace: true,

@ -10,9 +10,9 @@
default: true,
},
});
const slots = useSlots();
const attrs = useAttrs();
const render = () => <ElCascader {...props} {...attrs} v-slots={slots} />;
const slots = useSlots();
const render = () => <ElCascader {...props} {...attrs} v-slots={slots}></ElCascader>;
</script>
<style lang="less" scoped></style>

@ -2,8 +2,10 @@
<component :is="render" />
</template>
<script setup lang="jsx">
import { ElScrollbar } from 'element-plus/es/components/scrollbar/index';
import { ElDialog } from 'element-plus/es/components/dialog/index';
import 'element-plus/es/components/dialog/style/css';
import 'element-plus/es/components/scrollbar/style/css';
const props = defineProps({
destroyOnClose: {
type: Boolean,
@ -32,6 +34,13 @@
};
const dialogSlots = {
...slots,
default() {
return (
<ElScrollbar max-height={unref(fullscreen) ? '100%' : '60vh'}>
<div>{slots.default?.()}</div>
</ElScrollbar>
);
},
title() {
return (
<div class="el-dialog__header-wrapper">
@ -52,6 +61,9 @@
</script>
<style lang="less">
.el-dialog {
display: flex;
flex-direction: column;
overflow: hidden;
&.is-fullscreen {
display: flex;
flex-direction: column;
@ -76,6 +88,14 @@
}
.el-dialog__body {
padding: @layout-space-large;
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
.el-scrollbar {
width: 100%;
height: 100%;
}
}
.el-dialog__headerbtn {
top: calc(@layout-space-large + 5px);

@ -13,10 +13,6 @@
type: String,
default: 'ArrowDown',
},
size: {
type: String,
default: 'middle',
},
splitButton: {
type: Boolean,
default: false,

@ -0,0 +1,87 @@
<template>
<component :is="render" />
</template>
<script setup lang="jsx">
import { ElImage } from 'element-plus/es/components/image/index';
import 'element-plus/es/components/image/style/css';
const props = defineProps({
src: {
type: String,
required: true,
},
alt: {
type: String,
required: true,
},
width: {
type: String,
default: 'auto',
},
height: {
type: String,
default: 'auto',
},
fit: {
type: String,
default: 'contain',
},
zIndex: {
type: Number,
default: 9999,
},
hideOnClickModal: {
type: Boolean,
default: true,
},
lazy: {
type: Boolean,
default: true,
},
previewSrcList: {
type: Array,
default: () => [],
},
previewTeleported: {
type: Boolean,
default: true,
},
});
const slots = useSlots();
const attrs = useAttrs();
const imageSlots = {
placeholder: () => (
<div class="image-slot">
<ElIcon name="Picture" size="20" />
</div>
),
error: () => (
<div class="image-slot">
<ElIcon name="file-damage-fill" size="20" />
</div>
),
...slots,
};
if (props.previewSrcList?.length === 0) {
props.previewSrcList.push(props.src);
}
const width = computed(() => props.width);
const height = computed(() => props.height);
const render = () => <ElImage {...props} {...attrs} v-slots={imageSlots} />;
</script>
<style lang="less" scoped>
.el-image {
width: v-bind(width);
height: v-bind(height);
:deep(.image-slot) {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: var(--el-text-color-secondary);
font-size: 30px;
}
}
</style>

@ -0,0 +1,53 @@
<template>
<component :is="render" />
</template>
<script setup lang="jsx">
import { ElInput } from 'element-plus/es/components/input/index';
import 'element-plus/es/components/input/style/css';
const props = defineProps({
clearable: {
type: Boolean,
default: true,
},
showWordLimit: {
type: Boolean,
default: true,
},
placeholder: {
type: String,
default: '请输入',
},
type: {
type: String,
default: 'text',
},
showPassword: {
type: Boolean,
default(props) {
return props.type === 'password';
},
},
showWordLimit: {
type: Boolean,
default: true,
},
rows: {
type: Number,
default: 3,
},
autosize: {
type: [Boolean, Object],
default() {
return { minRows: 5, maxRows: 10 };
},
},
resize: {
type: String,
default: 'none',
},
});
const attrs = useAttrs();
const slots = useSlots();
const render = () => <ElInput {...props} {...attrs} v-slots={slots}></ElInput>;
</script>
<style lang="less" scoped></style>

@ -0,0 +1,61 @@
<template>
<component :is="render" />
</template>
<script setup lang="jsx">
import { ElRadioGroup, ElRadioButton, ElRadio } from 'element-plus/es/components/radio/index';
import 'element-plus/es/components/radio/style/css';
const props = defineProps({
opts: {
type: Array,
required: true,
},
config: {
type: Object,
default() {
return {
label: 'label',
value: 'value',
disabled: 'disabled',
};
},
},
button: {
type: Boolean,
default: false,
},
});
const attrs = useAttrs();
const slots = useSlots();
let config = {
label: 'label',
value: 'value',
disabled: 'disabled',
...props.config,
};
function handleItemDisabled(item, index) {
let res = false;
if (config.disabled instanceof Function) {
res = config.disabled(item, index);
} else {
res = !!item[config.disabled];
}
return res;
}
const Component = props.button ? ElRadioButton : ElRadio;
const render = () => (
<ElRadioGroup
{...props}
{...attrs}
v-slots={{
default: () =>
props.opts.map((item, index) => (
<Component label={item[config.value]} disabled={handleItemDisabled(item, index)}>
{item[config.label]}
</Component>
)),
...slots,
}}
/>
);
</script>
<style lang="less" scoped></style>

@ -0,0 +1,202 @@
<template>
<component :is="render" />
</template>
<script setup lang="jsx">
import config from '@/configs';
import { ElMessage } from '@/plugins/element-plus';
import { ElUpload } from 'element-plus/es/components/upload/index';
import 'element-plus/es/components/upload/style/css';
import { ElImage } from 'element-plus/es/components/image/index';
import 'element-plus/es/components/image/style/css';
const store = useStore();
const props = defineProps({
action: {
type: String,
default: config.baseURL + '/edu-oss/oss/fileUpload',
},
data: {
type: Object,
default() {
return { service: 'msb-edu-course' };
},
},
headers: {
type: Object,
default() {
return {};
},
},
drag: {
type: Boolean,
default: true,
},
multiple: {
type: Boolean,
},
limit: {
type: Number,
default: 1,
},
size: {
type: Number,
default: 1024 * 1024 * 20,
},
accept: {
type: String,
default: '*.*',
},
});
const attrs = useAttrs();
const emits = defineEmits(['update:modelValue']);
props.headers['Authorization'] = 'Bearer ' + store.state.local.token;
let imgList = ref([]);
const refsUpload = ref(null);
watch(
() => imgList,
() => {
if (props.limit === 1) {
emits('update:modelValue', unref(imgList)[0]?.response.data);
} else {
emits('update:modelValue', unref(imgList));
}
},
{ deep: true }
);
const handleSuccess = (res, file, list) => {
console.info('[upload] success', list);
imgList.value = list;
};
const handleRemove = (file, list) => {
console.info('[upload] remove', list);
imgList.value = list;
};
const handleExceed = (list) => {
console.info('[upload] exceed', list);
ElMessage.error('超出最大上传数量');
};
const handleBeforeUpload = (file) => {
console.info('[upload] upload', file);
if (file.size >= props.size) {
ElMessage.error('超出文件大小限制');
return false;
}
};
const fmtSize = computed(() => {
const units = ['byte', 'KB', 'MB', 'GB', 'TB'];
let res = props.size,
unit = 0;
while (res >= 800) {
res /= 1024;
unit++;
}
return res + units[unit];
});
watch(
() => attrs.modelValue,
(value) => {
if (props.limit === 1 && value) {
imgList.value = [
{
name: value,
response: {
data: value,
},
},
];
} else {
imgList.value = value || [];
}
},
{ immediate: true, deep: true }
);
const handleDeleteImage = (index) => {
if (unref(refsUpload)) {
unref(refsUpload).handleRemove(imgList[index]);
} else {
unref(imgList).splice(index, 1);
}
};
const render = () => (
<div class="upload-box">
<div class="upload-image">
{unref(imgList).map((item, index) => (
<div class="img-li">
<ElImage src={item?.response?.data} alt={item.name} />
<div class="img-li-cover" onClick={() => handleDeleteImage(index)}>
<ElIcon class="upload-del-icon" name="delete-bin-fill" size="20" />
</div>
</div>
))}
{props.limit != unref(imgList).length ? (
<ElUpload
ref={refsUpload}
{...props}
{...attrs}
before-upload={handleBeforeUpload}
on-exceed={handleExceed}
on-remove={handleRemove}
on-success={handleSuccess}
show-file-list={false}
>
<ElIcon class="el-icon--upload" name="add-fill" size="20" />
</ElUpload>
) : (
''
)}
</div>
<div class="el-upload__tip">支持小于 {unref(fmtSize)} 文件</div>
</div>
);
</script>
<style lang="less" scoped>
.upload-box {
:deep(.upload-image) {
display: flex;
flex-wrap: wrap;
.img-li {
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 150px;
height: 150px;
margin-right: 20px;
overflow: hidden;
.img-li-cover {
display: none;
}
&:hover {
.img-li-cover {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
.upload-del-icon {
color: #fff;
}
}
}
}
}
}
:deep(.el-upload) {
width: 150px;
height: 150px;
.el-upload-dragger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.el-icon--upload {
margin: 0;
}
}
}
</style>

@ -4,7 +4,16 @@
<el-select :opts="opts"></el-select>
<el-cascader :options="opts" @change="handleAdd"></el-cascader>
<el-checkbox-group :opts="opts"></el-checkbox-group>
<el-radio-group :opts="opts" v-model="form.msg"></el-radio-group>
<el-dropdown :opts="opts2"></el-dropdown>
<el-input v-model="form.msg"></el-input>
<el-image
:preview-src-list="null"
src="http://ksimage.mashibing.com/504cb56ac7f44d2bbe65ade478e1f3d4.jpg"
alt="测试"
></el-image>
{{ imgList }}
<el-upload-image v-model="imgList" />
</el-dialog>
<h1>{{ $route.name }}</h1>
<h1>
@ -56,6 +65,7 @@
},
},
]);
const imgList = ref('http://ksimage.mashibing.com/504cb56ac7f44d2bbe65ade478e1f3d4.jpg');
</script>
<style lang="less" scoped>

Loading…
Cancel
Save