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组件"
|
||||
}
|
||||
}
|
@ -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>
|
Loading…
Reference in new issue