style: 全局代码格式化
Former-commit-id: bb50c8419b8fcdeb48c93fce9f399d901e8f5a52
This commit is contained in:
@@ -1,101 +1,107 @@
|
||||
<template>
|
||||
<el-breadcrumb
|
||||
class="app-breadcrumb"
|
||||
separator-class="el-icon-arrow-right"
|
||||
>
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
:key="item.path"
|
||||
>
|
||||
<span
|
||||
v-if="item.redirect === 'noredirect' || index === breadcrumbs.length-1"
|
||||
class="no-redirect"
|
||||
>{{ generateTitle(item.meta.title) }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">
|
||||
{{ generateTitle(item.meta.title) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
||||
<span
|
||||
v-if="
|
||||
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
|
||||
"
|
||||
class="no-redirect"
|
||||
>{{ generateTitle(item.meta.title) }}</span
|
||||
>
|
||||
<a v-else @click.prevent="handleLink(item)">
|
||||
{{ generateTitle(item.meta.title) }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onBeforeMount, ref, watch} from 'vue'
|
||||
import {useRoute, RouteLocationMatched} from 'vue-router'
|
||||
import {compile} from 'path-to-regexp'
|
||||
import router from '@/router'
|
||||
import {generateTitle} from '@/utils/i18n'
|
||||
import { onBeforeMount, ref, watch } from 'vue';
|
||||
import { useRoute, RouteLocationMatched } from 'vue-router';
|
||||
import { compile } from 'path-to-regexp';
|
||||
import router from '@/router';
|
||||
import { generateTitle } from '@/utils/i18n';
|
||||
|
||||
const currentRoute = useRoute()
|
||||
const currentRoute = useRoute();
|
||||
const pathCompile = (path: string) => {
|
||||
const {params} = currentRoute
|
||||
const toPath = compile(path)
|
||||
return toPath(params)
|
||||
}
|
||||
const { params } = currentRoute;
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
|
||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>)
|
||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>);
|
||||
|
||||
function getBreadcrumb() {
|
||||
let matched = currentRoute.matched.filter((item) => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
if (!isDashboard(first)) {
|
||||
matched = [{path: '/dashboard', meta: {title: 'dashboard'}} as any].concat(matched)
|
||||
}
|
||||
breadcrumbs.value = matched.filter((item) => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false
|
||||
})
|
||||
let matched = currentRoute.matched.filter(
|
||||
item => item.meta && item.meta.title
|
||||
);
|
||||
const first = matched[0];
|
||||
if (!isDashboard(first)) {
|
||||
matched = [
|
||||
{ path: '/dashboard', meta: { title: 'dashboard' } } as any
|
||||
].concat(matched);
|
||||
}
|
||||
breadcrumbs.value = matched.filter(item => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
||||
});
|
||||
}
|
||||
|
||||
function isDashboard(route: RouteLocationMatched) {
|
||||
const name = route && route.name
|
||||
if (!name) {
|
||||
return false
|
||||
}
|
||||
return name.toString().trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
|
||||
const name = route && route.name;
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
name.toString().trim().toLocaleLowerCase() ===
|
||||
'Dashboard'.toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
function handleLink(item: any) {
|
||||
const {redirect, path} = item
|
||||
if (redirect) {
|
||||
router.push(redirect).catch((err) => {
|
||||
console.warn(err)
|
||||
})
|
||||
return
|
||||
}
|
||||
router.push(pathCompile(path)).catch((err) => {
|
||||
console.warn(err)
|
||||
})
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect).catch(err => {
|
||||
console.warn(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
router.push(pathCompile(path)).catch(err => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
watch(() => currentRoute.path, (path) => {
|
||||
if (path.startsWith('/redirect/')) {
|
||||
return
|
||||
}
|
||||
getBreadcrumb()
|
||||
})
|
||||
watch(
|
||||
() => currentRoute.path,
|
||||
path => {
|
||||
if (path.startsWith('/redirect/')) {
|
||||
return;
|
||||
}
|
||||
getBreadcrumb();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getBreadcrumb()
|
||||
})
|
||||
|
||||
getBreadcrumb();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-breadcrumb__inner,
|
||||
.el-breadcrumb__inner a {
|
||||
font-weight: 400 !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,54 +1,59 @@
|
||||
<template>
|
||||
<a href="https://github.com/hxrui" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 250 250"
|
||||
style="fill:#40c9c6; color:#fff;"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
||||
<path
|
||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||
fill="currentColor"
|
||||
style="transform-origin: 130px 106px;"
|
||||
class="octo-arm"
|
||||
/>
|
||||
<path
|
||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
||||
fill="currentColor"
|
||||
class="octo-body"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/hxrui"
|
||||
target="_blank"
|
||||
class="github-corner"
|
||||
aria-label="View source on Github"
|
||||
>
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 250 250"
|
||||
style="fill: #40c9c6; color: #fff"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
||||
<path
|
||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||
fill="currentColor"
|
||||
style="transform-origin: 130px 106px"
|
||||
class="octo-arm"
|
||||
/>
|
||||
<path
|
||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
||||
fill="currentColor"
|
||||
class="octo-body"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg)
|
||||
}
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg)
|
||||
}
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:500px) {
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none
|
||||
}
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none;
|
||||
}
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
<template>
|
||||
<div style="padding: 0 15px;" @click="toggleClick">
|
||||
<svg
|
||||
:class="{'is-active':isActive}"
|
||||
class="hamburger"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="padding: 0 15px" @click="toggleClick">
|
||||
<svg
|
||||
:class="{ 'is-active': isActive }"
|
||||
class="hamburger"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
<path
|
||||
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
<template>
|
||||
<div class="icon-select">
|
||||
<el-input v-model="iconName" clearable placeholder="请输入图标名称" @clear="filterIcons"
|
||||
@input="filterIcons">
|
||||
<template #suffix><i class="el-icon-search el-input__icon" /></template>
|
||||
</el-input>
|
||||
<div class="icon-select__list">
|
||||
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
|
||||
<svg-icon color="#999" :icon-class="item" style="height: 30px;width: 16px;margin-right: 5px" />
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-select">
|
||||
<el-input
|
||||
v-model="iconName"
|
||||
clearable
|
||||
placeholder="请输入图标名称"
|
||||
@clear="filterIcons"
|
||||
@input="filterIcons"
|
||||
>
|
||||
<template #suffix><i class="el-icon-search el-input__icon" /></template>
|
||||
</el-input>
|
||||
<div class="icon-select__list">
|
||||
<div
|
||||
v-for="(item, index) in iconList"
|
||||
:key="index"
|
||||
@click="selectedIcon(item)"
|
||||
>
|
||||
<svg-icon
|
||||
color="#999"
|
||||
:icon-class="item"
|
||||
style="height: 30px; width: 16px; margin-right: 5px"
|
||||
/>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref } from 'vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const icons = [] as string[]
|
||||
const icons = [] as string[];
|
||||
const modules = import.meta.glob('../../assets/icons/*.svg');
|
||||
for (const path in modules) {
|
||||
const p = path.split('assets/icons/')[1].split('.svg')[0];
|
||||
icons.push(p);
|
||||
const p = path.split('assets/icons/')[1].split('.svg')[0];
|
||||
icons.push(p);
|
||||
}
|
||||
const iconList = ref(icons);
|
||||
|
||||
@@ -30,51 +43,51 @@ const iconName = ref('');
|
||||
const emit = defineEmits(['selected']);
|
||||
|
||||
function filterIcons() {
|
||||
iconList.value = icons
|
||||
if (iconName.value) {
|
||||
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
|
||||
}
|
||||
iconList.value = icons;
|
||||
if (iconName.value) {
|
||||
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1);
|
||||
}
|
||||
}
|
||||
|
||||
function selectedIcon(name: string) {
|
||||
emit('selected', name)
|
||||
document.body.click()
|
||||
emit('selected', name);
|
||||
document.body.click();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
iconName.value = ''
|
||||
iconList.value = icons
|
||||
iconName.value = '';
|
||||
iconList.value = icons;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reset
|
||||
})
|
||||
reset
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.icon-select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
&__list {
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
&__list {
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
|
||||
div {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin-bottom: -5px;
|
||||
cursor: pointer;
|
||||
width: 33%;
|
||||
float: left;
|
||||
}
|
||||
div {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
margin-bottom: -5px;
|
||||
cursor: pointer;
|
||||
width: 33%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
<template>
|
||||
<el-dropdown class="lang-select" trigger="click" @command="handleSetLanguage">
|
||||
<div class="lang-select__icon">
|
||||
<svg-icon class-name="international-icon" icon-class="language"/>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :disabled="language==='zh-cn'" command="zh-cn">
|
||||
中文
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="language==='en'" command="en">
|
||||
English
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dropdown class="lang-select" trigger="click" @command="handleSetLanguage">
|
||||
<div class="lang-select__icon">
|
||||
<svg-icon class-name="international-icon" icon-class="language" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :disabled="language === 'zh-cn'" command="zh-cn">
|
||||
中文
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="language === 'en'" command="en">
|
||||
English
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import useStore from '@/store';
|
||||
|
||||
import {computed} from "vue";
|
||||
import useStore from "@/store";
|
||||
const { app } = useStore();
|
||||
const language = computed(() => app.language);
|
||||
|
||||
const {app}=useStore()
|
||||
const language = computed(() => app.language)
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
|
||||
const {locale} = useI18n()
|
||||
const { locale } = useI18n();
|
||||
|
||||
function handleSetLanguage(lang: string) {
|
||||
locale.value = lang
|
||||
app.setLanguage(lang)
|
||||
if (lang == 'en') {
|
||||
ElMessage.success('Switch Language Successful!')
|
||||
} else {
|
||||
ElMessage.success('切换语言成功!')
|
||||
}
|
||||
locale.value = lang;
|
||||
app.setLanguage(lang);
|
||||
if (lang == 'en') {
|
||||
ElMessage.success('Switch Language Successful!');
|
||||
} else {
|
||||
ElMessage.success('切换语言成功!');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.lang-select__icon {
|
||||
line-height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,94 +1,101 @@
|
||||
<template>
|
||||
<div :class="{ 'hidden': hidden }" class="pagination-container">
|
||||
<el-pagination :background="background" v-model:current-page="currentPage" v-model:page-size="pageSize"
|
||||
:layout="layout" :page-sizes="pageSizes" :total="total" @size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
</div>
|
||||
<div :class="{ hidden: hidden }" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType } from "vue";
|
||||
import { scrollTo } from '@/utils/scroll-to'
|
||||
import { computed, PropType } from 'vue';
|
||||
import { scrollTo } from '@/utils/scroll-to';
|
||||
|
||||
const props = defineProps({
|
||||
total: {
|
||||
required: true,
|
||||
type: Number as PropType<number>,
|
||||
default: 0
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array as PropType<number[]>,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
total: {
|
||||
required: true,
|
||||
type: Number as PropType<number>,
|
||||
default: 0
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array as PropType<number[]>,
|
||||
default() {
|
||||
return [10, 20, 30, 50];
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:page", "update:limit", "pagination"]);
|
||||
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
|
||||
|
||||
const currentPage = computed<number | undefined>({
|
||||
get: () => props.page,
|
||||
set: (value) => {
|
||||
emit('update:page', value)
|
||||
}
|
||||
})
|
||||
get: () => props.page,
|
||||
set: value => {
|
||||
emit('update:page', value);
|
||||
}
|
||||
});
|
||||
|
||||
const pageSize = computed<number | undefined>({
|
||||
get() {
|
||||
return props.limit
|
||||
},
|
||||
set(val) {
|
||||
emit('update:limit', val)
|
||||
}
|
||||
})
|
||||
get() {
|
||||
return props.limit;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:limit', val);
|
||||
}
|
||||
});
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
emit('pagination', { page: currentPage, limit: val })
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
emit('pagination', { page: currentPage, limit: val });
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val
|
||||
emit('pagination', { page: val, limit: props.limit })
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
currentPage.value = val;
|
||||
emit('pagination', { page: val, limit: props.limit });
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,154 +1,163 @@
|
||||
<template>
|
||||
<div ref="rightPanel" :class="{ show: show }" class="rightPanel-container">
|
||||
<div class="rightPanel-background" />
|
||||
<div class="rightPanel">
|
||||
<div class="handle-button" :style="{ 'top': buttonTop + 'px', 'background-color': theme }" @click="show = !show">
|
||||
<Close style="width: 1em; height: 1em;vertical-align: middle " v-show="show" />
|
||||
<Setting style="width:1em; height:1em;vertical-align: middle " v-show="!show" />
|
||||
</div>
|
||||
<div class="rightPanel-items">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="rightPanel" :class="{ show: show }" class="rightPanel-container">
|
||||
<div class="rightPanel-background" />
|
||||
<div class="rightPanel">
|
||||
<div
|
||||
class="handle-button"
|
||||
:style="{ top: buttonTop + 'px', 'background-color': theme }"
|
||||
@click="show = !show"
|
||||
>
|
||||
<Close
|
||||
style="width: 1em; height: 1em; vertical-align: middle"
|
||||
v-show="show"
|
||||
/>
|
||||
<Setting
|
||||
style="width: 1em; height: 1em; vertical-align: middle"
|
||||
v-show="!show"
|
||||
/>
|
||||
</div>
|
||||
<div class="rightPanel-items">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { addClass, removeClass } from '@/utils/index'
|
||||
import { addClass, removeClass } from '@/utils/index';
|
||||
|
||||
import useStore from "@/store";
|
||||
import useStore from '@/store';
|
||||
|
||||
// 图标依赖
|
||||
import { Close, Setting } from '@element-plus/icons-vue'
|
||||
import { ElColorPicker } from "element-plus";
|
||||
import { Close, Setting } from '@element-plus/icons-vue';
|
||||
import { ElColorPicker } from 'element-plus';
|
||||
|
||||
const { setting } = useStore()
|
||||
const { setting } = useStore();
|
||||
|
||||
const theme = computed(() => setting.theme)
|
||||
const show = ref(false)
|
||||
const theme = computed(() => setting.theme);
|
||||
const show = ref(false);
|
||||
|
||||
defineProps({
|
||||
buttonTop: {
|
||||
default: 250,
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
buttonTop: {
|
||||
default: 250,
|
||||
type: Number
|
||||
}
|
||||
});
|
||||
|
||||
watch(show, (value) => {
|
||||
if (value) {
|
||||
addEventClick()
|
||||
}
|
||||
if (value) {
|
||||
addClass(document.body, 'showRightPanel')
|
||||
} else {
|
||||
removeClass(document.body, 'showRightPanel')
|
||||
}
|
||||
})
|
||||
watch(show, value => {
|
||||
if (value) {
|
||||
addEventClick();
|
||||
}
|
||||
if (value) {
|
||||
addClass(document.body, 'showRightPanel');
|
||||
} else {
|
||||
removeClass(document.body, 'showRightPanel');
|
||||
}
|
||||
});
|
||||
|
||||
function addEventClick() {
|
||||
window.addEventListener('click', closeSidebar)
|
||||
window.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
function closeSidebar(evt: any) {
|
||||
// 主题选择点击不关闭
|
||||
let parent = evt.target.closest('.theme-picker-dropdown');
|
||||
if (parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 主题选择点击不关闭
|
||||
let parent = evt.target.closest('.theme-picker-dropdown')
|
||||
if (parent) {
|
||||
return
|
||||
}
|
||||
|
||||
parent = evt.target.closest('.rightPanel')
|
||||
if (!parent) {
|
||||
show.value = false
|
||||
window.removeEventListener('click', closeSidebar)
|
||||
}
|
||||
parent = evt.target.closest('.rightPanel');
|
||||
if (!parent) {
|
||||
show.value = false;
|
||||
window.removeEventListener('click', closeSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
const rightPanel = ref(ElColorPicker)
|
||||
const rightPanel = ref(ElColorPicker);
|
||||
|
||||
function insertToBody() {
|
||||
const elx = rightPanel.value as any
|
||||
const body = document.querySelector('body') as any
|
||||
body.insertBefore(elx, body.firstChild)
|
||||
const elx = rightPanel.value as any;
|
||||
const body = document.querySelector('body') as any;
|
||||
body.insertBefore(elx, body.firstChild);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
insertToBody()
|
||||
})
|
||||
insertToBody();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const elx = rightPanel.value as any
|
||||
elx.remove()
|
||||
})
|
||||
const elx = rightPanel.value as any;
|
||||
elx.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.showRightPanel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rightPanel-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
|
||||
background: rgba(0, 0, 0, .2);
|
||||
z-index: -1;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
|
||||
transition: all .25s cubic-bezier(.7, .3, .1, 1);
|
||||
transform: translate(100%);
|
||||
background: #fff;
|
||||
z-index: 40000;
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
transform: translate(100%);
|
||||
background: #fff;
|
||||
z-index: 40000;
|
||||
}
|
||||
|
||||
.show {
|
||||
transition: all .3s cubic-bezier(.7, .3, .1, 1);
|
||||
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
|
||||
.rightPanel-background {
|
||||
z-index: 20000;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.rightPanel-background {
|
||||
z-index: 20000;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
transform: translate(0);
|
||||
}
|
||||
.rightPanel {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
|
||||
.handle-button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
z-index: 0;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
z-index: 0;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
line-height: 48px;
|
||||
}
|
||||
i {
|
||||
font-size: 24px;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
|
||||
</div>
|
||||
<div>
|
||||
<svg-icon
|
||||
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
|
||||
@click="toggle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
<template>
|
||||
<el-dropdown class="size-select" trigger="click" @command="handleSetSize">
|
||||
<div class="size-select__icon">
|
||||
<svg-icon class-name="size-icon" icon-class="size" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="(size || 'default') == item.value"
|
||||
:command="item.value">
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dropdown class="size-select" trigger="click" @command="handleSetSize">
|
||||
<div class="size-select__icon">
|
||||
<svg-icon class-name="size-icon" icon-class="size" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item of sizeOptions"
|
||||
:key="item.value"
|
||||
:disabled="(size || 'default') == item.value"
|
||||
:command="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ref, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import useStore from "@/store";
|
||||
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||
import useStore from '@/store';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const { app } = useStore();
|
||||
const size = computed(() => app.size);
|
||||
|
||||
const sizeOptions = ref([
|
||||
{ label: "默认", value: "default" },
|
||||
{ label: "大型", value: "large" },
|
||||
{ label: "小型", value: "small" },
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: '大型', value: 'large' },
|
||||
{ label: '小型', value: 'small' }
|
||||
]);
|
||||
|
||||
function handleSetSize(size: string) {
|
||||
app.setSize(size);
|
||||
ElMessage.success("切换布局大小成功");
|
||||
app.setSize(size);
|
||||
ElMessage.success('切换布局大小成功');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.size-select__icon {
|
||||
line-height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" class="svg-icon">
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
<svg aria-hidden="true" class="svg-icon">
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props=defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon',
|
||||
},
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon'
|
||||
},
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
overflow: hidden;
|
||||
fill: currentColor;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
overflow: hidden;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,56 +1,67 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d' ]"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="[
|
||||
'#409EFF',
|
||||
'#1890ff',
|
||||
'#304156',
|
||||
'#212121',
|
||||
'#11a983',
|
||||
'#13c2c2',
|
||||
'#6959CD',
|
||||
'#f5222d'
|
||||
]"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, watch} from "vue";
|
||||
import useStore from "@/store";
|
||||
import {localStorage} from "@/utils/storage";
|
||||
import { computed, watch } from 'vue';
|
||||
import useStore from '@/store';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
|
||||
// 参考连接:https://juejin.cn/post/7024025899813044232#heading-1
|
||||
import {mix} from "@/utils";
|
||||
import { mix } from '@/utils';
|
||||
// 白色混合色
|
||||
const mixWhite = "#ffffff";
|
||||
const mixWhite = '#ffffff';
|
||||
// 黑色混合色
|
||||
const mixBlack = "#000000";
|
||||
const mixBlack = '#000000';
|
||||
|
||||
const node = document.documentElement;
|
||||
|
||||
const {setting} =useStore()
|
||||
const theme = computed(() => setting.theme)
|
||||
const { setting } = useStore();
|
||||
const theme = computed(() => setting.theme);
|
||||
|
||||
watch(theme, (color: string) => {
|
||||
node.style.setProperty("--el-color-primary", color);
|
||||
localStorage.set("theme", color)
|
||||
node.style.setProperty('--el-color-primary', color);
|
||||
localStorage.set('theme', color);
|
||||
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
node.style.setProperty(`--el-color-primary-light-${i}`, mix(color, mixWhite, i * 0.1));
|
||||
}
|
||||
node.style.setProperty("--el-color-primary-dark", mix(color, mixBlack, 0.1));
|
||||
|
||||
localStorage.set("style", node.style.cssText);
|
||||
})
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
node.style.setProperty(
|
||||
`--el-color-primary-light-${i}`,
|
||||
mix(color, mixWhite, i * 0.1)
|
||||
);
|
||||
}
|
||||
node.style.setProperty('--el-color-primary-dark', mix(color, mixBlack, 0.1));
|
||||
|
||||
localStorage.set('style', node.style.cssText);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.theme-message,
|
||||
.theme-picker-dropdown {
|
||||
z-index: 99999 !important;
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.theme-picker .el-color-picker__trigger {
|
||||
height: 26px !important;
|
||||
width: 26px !important;
|
||||
padding: 2px;
|
||||
height: 26px !important;
|
||||
width: 26px !important;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 上传组件 -->
|
||||
<el-upload
|
||||
ref="singleUploadRef"
|
||||
action=""
|
||||
class="single-uploader"
|
||||
:show-file-list="false"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:http-request="uploadImage"
|
||||
>
|
||||
<img v-if="imgUrl" :src="imgUrl" class="single-uploader__image" />
|
||||
<div>
|
||||
<!-- 上传组件 -->
|
||||
<el-upload
|
||||
ref="singleUploadRef"
|
||||
action=""
|
||||
class="single-uploader"
|
||||
:show-file-list="false"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:http-request="uploadImage"
|
||||
>
|
||||
<img v-if="imgUrl" :src="imgUrl" class="single-uploader__image" />
|
||||
|
||||
<el-icon v-else class="single-uploader__plus">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<el-icon v-else class="single-uploader__plus">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
|
||||
<!-- 删除图标 -->
|
||||
<el-icon
|
||||
v-if="props.showClose && imgUrl"
|
||||
class="single-uploader__remove"
|
||||
@click.stop="handleRemove(imgUrl)"
|
||||
>
|
||||
<Close />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</div>
|
||||
<!-- 删除图标 -->
|
||||
<el-icon
|
||||
v-if="props.showClose && imgUrl"
|
||||
class="single-uploader__remove"
|
||||
@click.stop="handleRemove(imgUrl)"
|
||||
>
|
||||
<Close />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { Plus, Close } from "@element-plus/icons-vue";
|
||||
import { computed } from 'vue';
|
||||
import { Plus, Close } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElMessage,
|
||||
ElUpload,
|
||||
UploadRawFile,
|
||||
UploadRequestOptions,
|
||||
} from "element-plus";
|
||||
import { uploadFile, deleteFile } from "@/api/system/file";
|
||||
ElMessage,
|
||||
ElUpload,
|
||||
UploadRawFile,
|
||||
UploadRequestOptions
|
||||
} from 'element-plus';
|
||||
import { uploadFile, deleteFile } from '@/api/system/file';
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
/**
|
||||
* 是否显示右上角的删除图片按钮
|
||||
*/
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 是否显示右上角的删除图片按钮
|
||||
*/
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const imgUrl = computed<string | undefined>({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(val) {
|
||||
// imgUrl改变时触发修改父组件绑定的v-model的值
|
||||
emit("update:modelValue", val);
|
||||
},
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(val) {
|
||||
// imgUrl改变时触发修改父组件绑定的v-model的值
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -70,8 +70,8 @@ const imgUrl = computed<string | undefined>({
|
||||
* @param params
|
||||
*/
|
||||
async function uploadImage(options: UploadRequestOptions): Promise<any> {
|
||||
const response = await uploadFile(options.file);
|
||||
imgUrl.value = response.data;
|
||||
const response = await uploadFile(options.file);
|
||||
imgUrl.value = response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,60 +80,60 @@ async function uploadImage(options: UploadRequestOptions): Promise<any> {
|
||||
* @param fileUrl
|
||||
*/
|
||||
function handleRemove(fileUrl?: string) {
|
||||
if (fileUrl) {
|
||||
deleteFile(fileUrl);
|
||||
imgUrl.value = undefined; // 这里会触发imgUrl的computed的set方法
|
||||
}
|
||||
if (fileUrl) {
|
||||
deleteFile(fileUrl);
|
||||
imgUrl.value = undefined; // 这里会触发imgUrl的computed的set方法
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 在 before-upload 钩子中限制用户上传文件的格式和大小
|
||||
*/
|
||||
function handleBeforeUpload(file: UploadRawFile) {
|
||||
// const isJPG = file.type === "image/jpeg";
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
// const isJPG = file.type === "image/jpeg";
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
|
||||
if (!isLt2M) {
|
||||
ElMessage.warning("上传图片不能大于2M");
|
||||
}
|
||||
return true;
|
||||
if (!isLt2M) {
|
||||
ElMessage.warning('上传图片不能大于2M');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.single-uploader {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
text-align: center;
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
text-align: center;
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 146px;
|
||||
height: 146px;
|
||||
display: block;
|
||||
}
|
||||
&__image {
|
||||
width: 146px;
|
||||
height: 146px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__plus {
|
||||
width: 146px;
|
||||
height: 157px;
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
text-align: center;
|
||||
}
|
||||
&__plus {
|
||||
width: 146px;
|
||||
height: 157px;
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
font-size: 12px;
|
||||
color: #ff4d51 !important;
|
||||
margin-top: 0px !important;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
color: #409eff;
|
||||
}
|
||||
&__remove {
|
||||
font-size: 12px;
|
||||
color: #ff4d51 !important;
|
||||
margin-top: 0px !important;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,70 +1,79 @@
|
||||
<template>
|
||||
<div style="border: 1px solid #ccc">
|
||||
<!-- 工具栏 -->
|
||||
<Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" style="border-bottom: 1px solid #ccc" :mode="mode" />
|
||||
<!-- 编辑器 -->
|
||||
<Editor :defaultConfig="editorConfig" v-model="defaultHtml" @onChange="handleChange"
|
||||
style="height: 500px; overflow-y: hidden;" :mode="mode" @onCreated="handleCreated" />
|
||||
</div>
|
||||
<div style="border: 1px solid #ccc">
|
||||
<!-- 工具栏 -->
|
||||
<Toolbar
|
||||
:editor="editorRef"
|
||||
:defaultConfig="toolbarConfig"
|
||||
style="border-bottom: 1px solid #ccc"
|
||||
:mode="mode"
|
||||
/>
|
||||
<!-- 编辑器 -->
|
||||
<Editor
|
||||
:defaultConfig="editorConfig"
|
||||
v-model="defaultHtml"
|
||||
@onChange="handleChange"
|
||||
style="height: 500px; overflow-y: hidden"
|
||||
:mode="mode"
|
||||
@onCreated="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, shallowRef, reactive, toRefs} from 'vue'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
|
||||
|
||||
// API 引用
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { uploadFile } from '@/api/system/file';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String],
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
modelValue: {
|
||||
type: [String],
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef()
|
||||
const editorRef = shallowRef();
|
||||
|
||||
const state = reactive({
|
||||
toolbarConfig: {},
|
||||
editorConfig: {
|
||||
placeholder: '请输入内容...',
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
// 自定义图片上传
|
||||
async customUpload(file: any, insertFn: any) {
|
||||
uploadFile(file).then(response => {
|
||||
const url = response.data
|
||||
insertFn(url)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultHtml: props.modelValue,
|
||||
mode: 'default'
|
||||
})
|
||||
toolbarConfig: {},
|
||||
editorConfig: {
|
||||
placeholder: '请输入内容...',
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
// 自定义图片上传
|
||||
async customUpload(file: any, insertFn: any) {
|
||||
uploadFile(file).then(response => {
|
||||
const url = response.data;
|
||||
insertFn(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultHtml: props.modelValue,
|
||||
mode: 'default'
|
||||
});
|
||||
|
||||
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state)
|
||||
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state);
|
||||
|
||||
const handleCreated = (editor: any) => {
|
||||
editorRef.value = editor // 记录 editor 实例,重要!
|
||||
}
|
||||
editorRef.value = editor; // 记录 editor 实例,重要!
|
||||
};
|
||||
|
||||
function handleChange(editor: any) {
|
||||
emit('update:modelValue', editor.getHtml())
|
||||
emit('update:modelValue', editor.getHtml());
|
||||
}
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
editor.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style src="@wangeditor/editor/dist/css/style.css">
|
||||
</style>
|
||||
<style src="@wangeditor/editor/dist/css/style.css"></style>
|
||||
|
||||
Reference in New Issue
Block a user