feat: 页面结构

This commit is contained in:
wangdan-fit2cloud 2023-10-13 16:11:54 +08:00
parent 5511769673
commit 439400d023
26 changed files with 467 additions and 448 deletions

View File

@ -1,17 +1,41 @@
<template>
<component :is="Object.keys(iconMap).includes(iconName) ? iconMap[iconName].iconReader() : iconMap['404'].iconReader()">
<component
v-if="isIconfont"
:is="
Object.keys(iconMap).includes(iconName)
? iconMap[iconName].iconReader()
: iconMap['404'].iconReader()
"
class="app-icon"
>
</component>
<el-icon v-else-if="iconName">
<component :is="iconName" />
</el-icon>
</template>
<script setup lang="ts">
import { iconMap } from "@/components/icons/index"
defineOptions({ name: 'AppIcon' });
withDefaults(defineProps<{
iconName?: string;
}>(), {
iconName: '404'
});
import { computed } from 'vue'
import { iconMap } from '@/components/icons/index'
defineOptions({ name: 'AppIcon' })
const props = withDefaults(
defineProps<{
iconName?: string
}>(),
{
iconName: '404'
}
)
const isIconfont = computed(() => props.iconName?.includes('app-'))
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.app-icon {
line-height: 1em;
svg {
height: 1em;
width: 1em;
}
}
</style>

View File

@ -1,8 +1,8 @@
import { h } from 'vue'
export const iconMap: any = {
'404': {
'app-404': {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
return h('i', [
h(
'svg',
{
@ -33,131 +33,62 @@ export const iconMap: any = {
])
}
},
home: {
'app-dataset': {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
return h('i', [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
style: 'height:14px;width:14px',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M362.666667 895.914667V639.850667c0-36.266667 33.109333-63.850667 72.533333-63.850667h153.6c39.253333 0 72.533333 27.648 72.533333 63.850667v256.064h59.904c61.269333 0 110.762667-47.957333 110.762667-106.730667V414.165333L557.162667 139.328a63.808 63.808 0 0 0-90.325334 0L192 414.165333v375.018667c0 58.88 49.386667 106.730667 110.762667 106.730667H362.666667z m42.666666 0h213.333334V639.850667c0-10.709333-12.586667-21.184-29.866667-21.184h-153.6c-17.408 0-29.866667 10.389333-29.866667 21.184v256.064z m469.333334-439.082667v332.352c0 82.645333-68.885333 149.397333-153.429334 149.397333H302.762667C218.133333 938.581333 149.333333 871.936 149.333333 789.184V456.832l-27.584 27.584a21.333333 21.333333 0 1 1-30.165333-30.165333L436.672 109.162667a106.474667 106.474667 0 0 1 150.656 0l345.088 345.088a21.333333 21.333333 0 0 1-30.165333 30.165333L874.666667 456.832z',
fill: '#666666'
})
]
)
])
}
},
app: {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
style: 'height:14px;width:14px',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M906.62890625 212.8203125C906.62890625 161.58007812 865.05664063 120.0078125 813.81640625 120.0078125H645.2421875C594.00195313 120.0078125 552.4296875 161.58007812 552.4296875 212.8203125v168.57421875c0 51.24023438 41.57226563 92.8125 92.8125 92.8125h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V212.8203125z m-56.25 173.93554688c0 17.05078125-28.125 31.20117188-45.26367188 31.20117187H640.3203125c-17.05078125 0-30.76171875-14.0625-30.76171875-31.20117188V207.81054687c0-17.05078125 13.7109375-30.67382813 30.76171875-30.67382812h178.9453125c17.05078125 0 31.02539063 13.62304688 31.02539063 30.67382813v178.94531249z m56.25 251.45507812c0-51.24023438-41.57226563-92.8125-92.8125-92.8125H645.2421875C594.00195313 545.3984375 552.4296875 586.97070313 552.4296875 638.2109375v168.57421875c0 51.24023438 41.57226563 92.8125 92.8125 92.8125h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V638.2109375z m-56.25 173.3203125c0 17.05078125-13.88671875 30.9375-30.9375 30.9375H640.49609375c-17.05078125 0-30.9375-13.88671875-30.9375-30.9375V632.5859375c0-17.05078125 13.88671875-30.9375 30.9375-30.9375h178.9453125c17.05078125 0 30.9375 13.88671875 30.9375 30.9375v178.9453125zM468.0546875 638.2109375c0-51.24023438-41.57226563-92.8125-92.8125-92.8125H206.66796875C155.42773437 545.3984375 113.85546875 586.97070313 113.85546875 638.2109375v168.57421875C113.85546875 858.02539063 155.42773437 899.59765625 206.66796875 899.59765625h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V638.2109375z m-57.12890625 173.3203125c0 17.05078125-13.88671875 30.9375-30.9375 30.9375H201.04296875c-17.05078125 0-30.9375-13.88671875-30.9375-30.9375V632.5859375c0-17.05078125 13.88671875-30.9375 30.9375-30.9375h178.9453125c17.05078125 0 30.9375 13.88671875 30.9375 30.9375v178.9453125z m57.12890625-598.7109375C468.0546875 161.58007812 426.48242187 120.0078125 375.2421875 120.0078125H206.66796875C155.42773437 120.0078125 113.85546875 161.58007812 113.85546875 212.8203125v168.57421875C113.85546875 432.63476562 155.42773437 474.20703125 206.66796875 474.20703125h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V212.8203125z m-57.12890625 174.19921875c0 17.05078125-13.88671875 30.9375-30.9375 30.9375H201.04296875c-17.05078125 0-30.9375-13.88671875-30.9375-30.9375V208.07421875c0-17.05078125 13.88671875-30.9375 30.9375-30.9375h178.9453125c17.05078125 0 30.9375 13.88671875 30.9375 30.9375v178.9453125z',
fill: '#768696'
})
]
)
])
}
},
dataset: {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
style: 'height:14px;width:14px',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M859.5 193H446.939c-1.851-53.25-45.747-96-99.439-96h-183C109.635 97 65 141.635 65 196.5v632c0 54.864 44.635 99.5 99.5 99.5h695c54.864 0 99.5-44.636 99.5-99.5v-536c0-54.865-44.636-99.5-99.5-99.5z m-695-33h183c20.126 0 36.5 16.374 36.5 36.5v28c0 17.397 14.103 31.5 31.5 31.5h444c20.126 0 36.5 16.374 36.5 36.5V321H128V196.5c0-20.126 16.374-36.5 36.5-36.5z m695 705h-695c-20.126 0-36.5-16.374-36.5-36.5V384h768v444.5c0 20.126-16.374 36.5-36.5 36.5z',
fill: '#070102'
fill: 'currentColor'
})
]
)
])
}
},
setting: {
'app-applicaiton': {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
return h('i', [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
style: 'height:14px;width:14px',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M512 328c-100.8 0-184 83.2-184 184S411.2 696 512 696 696 612.8 696 512 612.8 328 512 328z m0 320c-75.2 0-136-60.8-136-136s60.8-136 136-136 136 60.8 136 136-60.8 136-136 136z',
fill: '#070102'
}),
h('path', {
d: 'M857.6 572.8c-20.8-12.8-33.6-35.2-33.6-60.8s12.8-46.4 33.6-60.8c14.4-9.6 20.8-27.2 16-44.8-8-27.2-19.2-52.8-32-76.8-8-14.4-25.6-24-43.2-19.2-24 4.8-48-1.6-65.6-19.2-17.6-17.6-24-41.6-19.2-65.6 3.2-16-4.8-33.6-19.2-43.2-24-14.4-51.2-24-76.8-32-16-4.8-35.2 1.6-44.8 16-12.8 20.8-35.2 33.6-60.8 33.6s-46.4-12.8-60.8-33.6c-9.6-14.4-27.2-20.8-44.8-16-27.2 8-52.8 19.2-76.8 32-14.4 8-24 25.6-19.2 43.2 4.8 24-1.6 49.6-19.2 65.6-17.6 17.6-41.6 24-65.6 19.2-16-3.2-33.6 4.8-43.2 19.2-14.4 24-24 51.2-32 76.8-4.8 16 1.6 35.2 16 44.8 20.8 12.8 33.6 35.2 33.6 60.8s-12.8 46.4-33.6 60.8c-14.4 9.6-20.8 27.2-16 44.8 8 27.2 19.2 52.8 32 76.8 8 14.4 25.6 22.4 43.2 19.2 24-4.8 49.6 1.6 65.6 19.2 17.6 17.6 24 41.6 19.2 65.6-3.2 16 4.8 33.6 19.2 43.2 24 14.4 51.2 24 76.8 32 16 4.8 35.2-1.6 44.8-16 12.8-20.8 35.2-33.6 60.8-33.6s46.4 12.8 60.8 33.6c8 11.2 20.8 17.6 33.6 17.6 3.2 0 8 0 11.2-1.6 27.2-8 52.8-19.2 76.8-32 14.4-8 24-25.6 19.2-43.2-4.8-24 1.6-49.6 19.2-65.6 17.6-17.6 41.6-24 65.6-19.2 16 3.2 33.6-4.8 43.2-19.2 14.4-24 24-51.2 32-76.8 4.8-17.6-1.6-35.2-16-44.8z m-56 92.8c-38.4-6.4-76.8 6.4-104 33.6-27.2 27.2-40 65.6-33.6 104-17.6 9.6-36.8 17.6-56 24-22.4-30.4-57.6-49.6-97.6-49.6-38.4 0-73.6 17.6-97.6 49.6-19.2-6.4-38.4-14.4-56-24 6.4-38.4-6.4-76.8-33.6-104-27.2-27.2-65.6-40-104-33.6-9.6-17.6-17.6-36.8-24-56 30.4-22.4 49.6-57.6 49.6-97.6 0-38.4-17.6-73.6-49.6-97.6 6.4-19.2 14.4-38.4 24-56 38.4 6.4 76.8-6.4 104-33.6 27.2-27.2 40-65.6 33.6-104 17.6-9.6 36.8-17.6 56-24 22.4 30.4 57.6 49.6 97.6 49.6 38.4 0 73.6-17.6 97.6-49.6 19.2 6.4 38.4 14.4 56 24-6.4 38.4 6.4 76.8 33.6 104 27.2 27.2 65.6 40 104 33.6 9.6 17.6 17.6 36.8 24 56-30.4 22.4-49.6 57.6-49.6 97.6 0 38.4 17.6 73.6 49.6 97.6-6.4 19.2-14.4 38.4-24 56z',
fill: '#070102'
d: 'M951.901 244.015l-413.3-238.57a33.606 33.606 0 0 0-33.909 0L91.3 244.016c-10.426 6.12-16.99 17.221-16.99 29.346v477.184c0 12.149 6.447 23.343 16.99 29.37l413.3 238.662c5.213 2.933 11.101 4.515 16.99 4.515 5.794 0 11.775-1.582 16.988-4.515l413.3-238.661c10.427-6.121 16.99-17.222 16.99-29.37V273.36a33.908 33.908 0 0 0-16.966-29.346zM892.23 726.016l-370.618 213.97-370.642-213.97v-427.87L521.588 84.178l370.642 213.97v427.869z m8.797 5.073M285.207 348.393a34.095 34.095 0 0 0-46.336 12.567 33.908 33.908 0 0 0 12.474 46.36l235.94 136.215v269.498a33.745 33.745 0 0 0 33.884 33.885 33.745 33.745 0 0 0 33.886-33.885V543.977L791.9 407.227a34.025 34.025 0 0 0 12.451-46.36 34.025 34.025 0 0 0-46.336-12.474l-236.404 136.54-236.405-136.54z m0 0',
fill: 'currentColor'
})
]
)
])
}
},
password: {
'app-exit': {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
return h('i', [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
style: 'height:14px;width:14px',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M807.28626 393.9647l-59.057047 0 0-78.729086c0-130.193201-105.96438-236.099253-236.229213-236.099253S275.770787 185.042413 275.770787 315.235614l0 78.729086-59.057047 0c-32.616862 0-59.057047 26.425859-59.057047 59.025325L157.656693 885.838314c0 32.598442 26.441209 59.025325 59.057047 59.025325l590.57252 0c32.616862 0 59.057047-26.425859 59.057047-59.025325L866.343307 452.989001C866.343307 420.390559 839.903122 393.9647 807.28626 393.9647zM334.827835 315.235614c0-97.644901 79.473029-177.074951 177.172165-177.074951s177.172165 79.43005 177.172165 177.074951l0 78.729086L334.827835 393.9647 334.827835 315.235614zM807.28626 885.838314 216.71374 885.838314 216.71374 452.989001l590.57252 0L807.28626 885.838314z'
}),
h('path', {
d: 'M512 777.635963c16.302291 0 29.528524-13.219069 29.528524-29.512151L541.528524 590.723969c0-16.293081-13.226233-29.512151-29.528524-29.512151s-29.528524 13.219069-29.528524 29.512151l0 157.399843C482.471476 764.416893 495.697709 777.635963 512 777.635963z'
})
]
)
])
}
},
exit: {
iconReader: () => {
return h('el-icon', { style: 'display:flex' }, [
h(
'svg',
{
viewBox: '0 0 1024 1024',
version: '1.1',
style: 'height:14px;width:14px',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M874.666667 855.744a19.093333 19.093333 0 0 1-19.136 18.922667H168.469333A19.2 19.2 0 0 1 149.333333 855.530667V168.469333A19.2 19.2 0 0 1 168.469333 149.333333h687.061334c10.581333 0 19.136 8.533333 19.136 18.922667V320h42.666666V168.256A61.717333 61.717333 0 0 0 855.530667 106.666667H168.469333A61.866667 61.866667 0 0 0 106.666667 168.469333v687.061334A61.866667 61.866667 0 0 0 168.469333 917.333333h687.061334A61.76 61.76 0 0 0 917.333333 855.744V704h-42.666666v151.744zM851.84 533.333333l-131.797333 131.754667a21.141333 21.141333 0 0 0 0.213333 29.973333 21.141333 21.141333 0 0 0 29.973333 0.192l165.589334-165.589333a20.821333 20.821333 0 0 0 6.122666-14.976 21.44 21.44 0 0 0-6.314666-14.997333l-168.533334-168.533334a21.141333 21.141333 0 0 0-29.952-0.213333 21.141333 21.141333 0 0 0 0.213334 29.973333L847.296 490.666667H469.333333v42.666666h382.506667z'
d: 'M874.666667 855.744a19.093333 19.093333 0 0 1-19.136 18.922667H168.469333A19.2 19.2 0 0 1 149.333333 855.530667V168.469333A19.2 19.2 0 0 1 168.469333 149.333333h687.061334c10.581333 0 19.136 8.533333 19.136 18.922667V320h42.666666V168.256A61.717333 61.717333 0 0 0 855.530667 106.666667H168.469333A61.866667 61.866667 0 0 0 106.666667 168.469333v687.061334A61.866667 61.866667 0 0 0 168.469333 917.333333h687.061334A61.76 61.76 0 0 0 917.333333 855.744V704h-42.666666v151.744zM851.84 533.333333l-131.797333 131.754667a21.141333 21.141333 0 0 0 0.213333 29.973333 21.141333 21.141333 0 0 0 29.973333 0.192l165.589334-165.589333a20.821333 20.821333 0 0 0 6.122666-14.976 21.44 21.44 0 0 0-6.314666-14.997333l-168.533334-168.533334a21.141333 21.141333 0 0 0-29.952-0.213333 21.141333 21.141333 0 0 0 0.213334 29.973333L847.296 490.666667H469.333333v42.666666h382.506667z',
fill: 'currentColor'
})
]
)

View File

@ -0,0 +1,39 @@
<script setup lang="ts">
import { ref, onBeforeUpdate } from 'vue'
import { useRoute } from 'vue-router'
import TopBar from '@/components/layout/top-bar/index.vue'
const route = useRoute()
const cachedViews: any = ref([])
onBeforeUpdate(() => {
let isCached = route.meta?.cache
let name = route.name
if (isCached && name && !cachedViews.value.includes(name)) {
cachedViews.value.push(name)
}
})
</script>
<template>
<div class="app-layout">
<div class="app-header">
<TopBar></TopBar>
</div>
<div class="app-main">
<router-view v-slot="{ Component }">
<transition appear name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</div>
</div>
</template>
<style lang="scss" scoped>
.app-main {
height: calc(100vh - var(--app-header-height));
padding: 0 !important;
}
</style>

View File

@ -1,32 +0,0 @@
<script setup lang="ts">
import TopBar from "@/components/layout/top-bar/index.vue"
</script>
<template>
<div class="common-layout">
<el-container>
<el-header>
<TopBar></TopBar>
</el-header>
<el-main>
<RouterView></RouterView>
</el-main>
</el-container>
</div>
</template>
<style lang="scss" scoped>
.el-header {
--el-header-padding: 0;
--el-header-height: 56px;
padding: var(--el-header-padding);
box-sizing: border-box;
flex-shrink: 0;
height: var(--el-header-height);
}
.el-main {
--el-main-padding: 0;
width: 100vw;
height: calc(100vh - var(--el-header-height, 60px));
}
</style>

View File

@ -1,155 +1,176 @@
<template >
<el-dialog v-model="resetPasswordDialog" title="修改密码">
<el-form class="reset-password-form" ref="resetPasswordFormRef" :model="resetPasswordForm" :rules="rules">
<el-form-item prop="password">
<el-input type="password" size="large" class="input-item" v-model="resetPasswordForm.password"
placeholder="请输入密码">
<template #prepend>
<el-button :icon="Lock" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="re_password">
<el-input type="password" size="large" class="input-item" v-model="resetPasswordForm.re_password"
placeholder="请输入确认密码">
<template #prepend>
<el-button :icon="Lock" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-input size="large" class="input-item" :disabled="true" v-bind:modelValue="userStore.userInfo?.email"
@change="() => { }" placeholder="请输入邮箱">
<template #prepend>
<el-button :icon="UserFilled" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input size="large" class="code-input" v-model="resetPasswordForm.code" placeholder="请输入验证码">
<template #prepend>
<el-button :icon="Key" />
</template>
</el-input>
<el-button size="large" class="send-email-button" @click="sendEmail" :loading="loading">获取验证码</el-button>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="resetPassword">
修改密码
</el-button>
</span>
</template>
</el-dialog>
<template>
<el-dialog v-model="resetPasswordDialog" title="修改密码">
<el-form
class="reset-password-form"
ref="resetPasswordFormRef"
:model="resetPasswordForm"
:rules="rules"
>
<el-form-item prop="password">
<el-input
type="password"
size="large"
class="input-item"
v-model="resetPasswordForm.password"
placeholder="请输入密码"
>
<template #prepend>
<el-button icon="Lock" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="re_password">
<el-input
type="password"
size="large"
class="input-item"
v-model="resetPasswordForm.re_password"
placeholder="请输入确认密码"
>
<template #prepend>
<el-button icon="Lock" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-input
size="large"
class="input-item"
:disabled="true"
v-bind:modelValue="userStore.userInfo?.email"
@change="() => {}"
placeholder="请输入邮箱"
>
<template #prepend>
<el-button icon="UserFilled" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code">
<div class="flex-between w-full">
<el-input
size="large"
class="code-input"
v-model="resetPasswordForm.code"
placeholder="请输入验证码"
>
<template #prepend>
<el-button icon="Key" />
</template>
</el-input>
<el-button
size="large"
class="send-email-button ml-1"
@click="sendEmail"
:loading="loading"
>获取验证码</el-button
>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="resetPassword"> 修改密码 </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from "vue";
import type { ResetCurrentUserPasswordRequest } from "@/api/user/type";
import { ref } from 'vue'
import type { ResetCurrentUserPasswordRequest } from '@/api/user/type'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from "element-plus"
import UserApi from "@/api/user"
import { useUserStore } from '@/stores/user';
import { Lock, UserFilled, Key } from '@element-plus/icons-vue'
import { useRouter } from "vue-router"
const router = useRouter();
import { MsgSuccess } from '@/utils/message'
import UserApi from '@/api/user'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserStore()
const resetPasswordDialog = ref<boolean>(false);
const resetPasswordDialog = ref<boolean>(false)
const resetPasswordForm = ref<ResetCurrentUserPasswordRequest>({
code: "",
password: "",
re_password: ""
});
code: '',
password: '',
re_password: ''
})
const resetPasswordFormRef = ref<FormInstance>();
const resetPasswordFormRef = ref<FormInstance>()
const loading = ref<boolean>(false);
const loading = ref<boolean>(false)
const rules = ref<FormRules<ResetCurrentUserPasswordRequest>>({
code: [
{ required: true, message: '请输入验证码' }
],
password: [
{
required: true,
message: "请输入密码",
trigger: "blur",
},
{
min: 6,
max: 30,
message: "长度在 6 到 30 个字符",
trigger: "blur",
},
],
re_password: [{
required: true,
message: '请输入确认密码',
trigger: 'blur'
code: [{ required: true, message: '请输入验证码' }],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
},
{
min: 6,
max: 30,
message: "长度在 6 到 30 个字符",
trigger: "blur",
min: 6,
max: 30,
message: '长度在 6 到 30 个字符',
trigger: 'blur'
}
],
re_password: [
{
required: true,
message: '请输入确认密码',
trigger: 'blur'
},
{
validator: (rule, value, callback) => {
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
callback(new Error('密码不一致'));
} else {
callback();
}
},
trigger: 'blur'
}]
min: 6,
max: 30,
message: '长度在 6 到 30 个字符',
trigger: 'blur'
},
{
validator: (rule, value, callback) => {
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
callback(new Error('密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
/**
* 发送验证码
*/
const sendEmail = () => {
UserApi.sendEmailToCurrent(loading)
.then(() => {
ElMessage.success("发送验证码成功")
})
UserApi.sendEmailToCurrent(loading).then(() => {
MsgSuccess('发送验证码成功')
})
}
const open = () => {
resetPasswordForm.value = {
code: "",
password: "",
re_password: ""
}
resetPasswordDialog.value = true
resetPasswordForm.value = {
code: '',
password: '',
re_password: ''
}
resetPasswordDialog.value = true
}
const resetPassword = () => {
resetPasswordFormRef.value?.validate().then(() => {
return UserApi.resetCurrentUserPassword(resetPasswordForm.value)
}).then(() => {
return userStore.logout()
}).then(() => {
router.push({ name: 'login' })
resetPasswordFormRef.value
?.validate()
.then(() => {
return UserApi.resetCurrentUserPassword(resetPasswordForm.value)
})
.then(() => {
return userStore.logout()
})
.then(() => {
router.push({ name: 'login' })
})
}
const close = () => { resetPasswordDialog.value = false }
const close = () => {
resetPasswordDialog.value = false
}
defineExpose({ open, close })
</script>
<style lang="scss" scope>
.code-input {
width: 250px;
}
.send-email-button {
margin-left: 12px;
width: 158px;
}
</style>
<style lang="scss" scope></style>

View File

@ -1,50 +1,39 @@
<template >
<el-dropdown trigger="click" size="small" type="primary">
<el-avatar> {{ firstUserName }} </el-avatar>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openResetPassword">
<AppIcon iconName="password"></AppIcon><span style="margin-left:5px">修改密码</span>
</el-dropdown-item>
<el-dropdown-item @click="logout">
<AppIcon iconName="exit"></AppIcon><span style="margin-left:5px">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<ResetPassword ref="resetPasswordRef"></ResetPassword>
<template>
<el-dropdown trigger="click" type="primary">
<el-avatar :size="30"> {{ firstUserName }} </el-avatar>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openResetPassword">
<AppIcon iconName="Lock"></AppIcon><span style="margin-left: 5px">修改密码</span>
</el-dropdown-item>
<el-dropdown-item @click="logout">
<AppIcon iconName="app-exit"></AppIcon><span style="margin-left: 5px">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<ResetPassword ref="resetPasswordRef"></ResetPassword>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { useUserStore } from '@/stores/user';
import { useRouter } from "vue-router";
import AppIcon from "@/components/icons/AppIcon.vue"
import ResetPassword from "@/components/layout/top-bar/components/avatar/ResetPasssword.vue"
import { computed, ref } from 'vue'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
import ResetPassword from '@/components/layout/top-bar/components/avatar/ResetPasssword.vue'
const userStore = useUserStore()
const router = useRouter()
const firstUserName = computed(() => {
return userStore.userInfo?.username?.substring(0, 1)
return userStore.userInfo?.username?.substring(0, 1)
})
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>();
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
const openResetPassword = () => {
resetPasswordRef.value?.open()
resetPasswordRef.value?.open()
}
const logout = () => {
userStore.logout().then(() => {
router.push({ name: "login" })
})
userStore.logout().then(() => {
router.push({ name: 'login' })
})
}
</script>
<style lang="scss" scoped>
.el-avatar {
--el-avatar-size: 30px;
--el-avatar-bg-color: var(--app-base-action-text-color);
cursor: pointer;
}
.el-dropdown-menu--small {
padding: 10px 0;
}
</style>
<style lang="scss" scoped></style>

View File

@ -1,40 +1,46 @@
<template>
<div class="menu-item-container" :class="isActive ? 'active' : ''" @click="router.push({ name: menu.name })">
<div class="icon">
<AppIcon :iconName="menu.meta ? menu.meta.icon as string : '404'"></AppIcon>
</div>
<div class="title">{{ menu.meta?.title }} </div>
<div
class="menu-item-container flex-center h-full"
:class="isActive ? 'active' : ''"
@click="router.push({ name: menu.name })"
>
<div class="icon">
<AppIcon :iconName="menu.meta ? (menu.meta.icon as string) : '404'" />
</div>
<div class="title">{{ menu.meta?.title }}</div>
</div>
</template>
<script setup lang="ts">
import { useRouter, useRoute, type RouteRecordRaw } from 'vue-router'
import { computed } from "vue";
import AppIcon from "@/components/icons/AppIcon.vue"
const router = useRouter();
const route = useRoute();
import { computed } from 'vue'
const router = useRouter()
const route = useRoute()
const props = defineProps<{
menu: RouteRecordRaw
menu: RouteRecordRaw
}>()
const isActive = computed(() => {
return route.name == props.menu.name && route.path == props.menu.path
return route.name == props.menu.name && route.path == props.menu.path
})
</script>
<style lang="scss" scoped>
.menu-item-container {
padding: 0 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
height: 100%;
padding: 0 20px;
cursor: pointer;
.icon {
font-size: 15px;
margin-right: 5px;
margin-top: 2px;
}
&:hover {
color: var(--el-color-primary);
}
}
.active {
background-color: var(--app-base-text-hover-bg-color);
border-bottom: 3px solid var(--app-base-text-hover-color);
height: calc(100% - 3px);
font-weight: 600;
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
border-bottom: 3px solid var(--el-color-primary);
}
</style>
</style>

View File

@ -1,22 +1,25 @@
<template >
<div class="top-menu-container">
<MenuItem :menu="menu" v-hasPermission="menu.meta?.permission" v-for="(menu, index) in topMenuList" :key="index">
</MenuItem>
</div>
<template>
<div class="top-menu-container flex h-full">
<MenuItem
:menu="menu"
v-hasPermission="menu.meta?.permission"
v-for="(menu, index) in topMenuList"
:key="index"
>
</MenuItem>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { getChildRouteListByPathAndName } from "@/router/index"
import MenuItem from "@/components/layout/top-bar/components/top-menu/MenuItem.vue"
import { computed } from 'vue'
import { getChildRouteListByPathAndName } from '@/router/index'
import MenuItem from '@/components/layout/top-bar/components/top-menu/MenuItem.vue'
const topMenuList = computed(() => {
return getChildRouteListByPathAndName("/", "home")
return getChildRouteListByPathAndName('/', 'home')
})
</script>
<style lang="scss" scope>
.top-menu-container {
display: flex;
align-items: center;
height: 100%;
align-items: center;
margin-bottom: -1px;
}
</style>
</style>

View File

@ -1,14 +1,13 @@
<template>
<div class="top-bar-container">
<div class="app-title-container">
<div class="app-title-icon"></div>
<div class="app-title-text">{{ defaultTitle }}</div>
<div class="line"></div>
</div>
<div class="app-top-menu-container">
<div class="top-bar-container flex-between">
<div class="flex-center h-full">
<div class="app-title-container flex-center">
<div class="app-title-icon"></div>
<div class="app-title-text ml-1">{{ defaultTitle }}</div>
</div>
<el-divider direction="vertical" class="line" />
<TopMenu></TopMenu>
</div>
<div class="flex-auto"></div>
<div class="avatar">
<Avatar></Avatar>
</div>
@ -22,31 +21,12 @@ const defaultTitle = import.meta.env.VITE_APP_TITLE
<style lang="scss">
.top-bar-container {
border-bottom: 1px solid rgba(229, 229, 229, 1);
height: calc(100% - 1px);
background-color: var(--app-header-background-color, #fff);
width: 100vw;
display: flex;
.flex-auto {
flex: 1 1 auto;
}
.avatar {
height: 100%;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
}
height: var(--app-header-height);
box-sizing: border-box;
padding: var(--app-header-padding);
.app-title-container {
width: 200px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 30px;
.app-title-icon {
background-image: url('@/assets/logo.png');
background-size: 100% 100%;
@ -55,18 +35,13 @@ const defaultTitle = import.meta.env.VITE_APP_TITLE
}
.app-title-text {
color: var(--app-base-action-text-color);
color: var(--el-color-primary);
font-size: 20px;
font-weight: 600;
align-items: center;
}
.line {
height: 60%;
width: 1px;
margin-left: 20px;
background-color: rgba(229, 229, 229, 1);
}
}
.line {
height: 2em;
}
}
</style>

View File

@ -1,5 +1,5 @@
import type { App } from 'vue'
import { hasPermission } from '@/common/permission'
import { hasPermission } from '@/utils/permission'
const display = async (el: any, binding: any) => {
const has = hasPermission(

View File

@ -1,5 +1,5 @@
import axios, { type AxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
import { MsgError } from '@/utils/message'
import type { NProgress } from 'nprogress'
import type { Ref } from 'vue'
import type { Result } from '@/request/Result'
@ -41,7 +41,7 @@ instance.interceptors.response.use(
(response: any) => {
if (response.data) {
if (response.data.code !== 200 && !(response.data instanceof Blob)) {
ElMessage.error(response.data.message)
MsgError(response.data.message)
}
}
if (response.headers['content-type'] === 'application/octet-stream') {
@ -51,7 +51,7 @@ instance.interceptors.response.use(
},
(err: any) => {
if (err.code === 'ECONNABORTED') {
ElMessage.error(err.message)
MsgError(err.message)
console.error(err)
}
if (err.response?.status === 401) {
@ -59,7 +59,7 @@ instance.interceptors.response.use(
}
if (err.response?.status === 403) {
ElMessage.error(
MsgError(
err.response.data && err.response.data.message ? err.response.data.message : '没有权限访问'
)
}

View File

@ -1,4 +1,4 @@
import { hasPermission } from '@/common/permission/index'
import { hasPermission } from '@/utils/permission/index'
import {
createRouter,
createWebHistory,
@ -8,7 +8,7 @@ import {
} from 'vue-router'
import { useUserStore } from '@/stores/user'
import { store } from '@/stores'
import { routes } from '@/router/data'
import { routes } from '@/router/routes'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes

View File

@ -0,0 +1,8 @@
const applicationRouter = {
path: '/app',
name: 'app',
meta: { icon: 'app-applicaiton', title: '应用', permission: 'APPLICATION:READ' },
component: () => import('@/views/app/index.vue')
}
export default applicationRouter

View File

@ -0,0 +1,8 @@
const datasetRouter = {
path: '/dataset',
name: 'dataset',
meta: { icon: 'app-dataset', title: '数据集', permission: 'DATASET:READ' },
component: () => import('@/views/dataset/index.vue')
}
export default datasetRouter

View File

@ -0,0 +1,8 @@
const settingRouter = {
path: '/setting',
name: 'setting',
meta: { icon: 'Setting', title: '系统设置', permission: 'SETTING:READ' },
component: () => import('@/views/setting/index.vue')
}
export default settingRouter

View File

@ -1,35 +1,22 @@
import type { RouteRecordRaw } from 'vue-router'
import { Role } from '@/common/permission/type'
import { Role } from '@/utils/permission/type'
const modules: any = import.meta.glob('./modules/*.ts', { eager: true })
const rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)]
export const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: () => import('@/components/layout/home-layout/index.vue'),
component: () => import('@/components/layout/app-layout/index.vue'),
children: [
{
path: '/first',
name: 'first',
meta: { icon: 'app', title: '首页' },
meta: { icon: 'House', title: '首页' },
component: () => import('@/views/first/index.vue')
},
{
path: '/app',
name: 'app',
meta: { icon: 'app', title: '应用', permission: 'APPLICATION:READ' },
component: () => import('@/views/app/index.vue')
},
{
path: '/dataset',
name: 'dataset',
meta: { icon: 'dataset', title: '数据集', permission: 'DATASET:READ' },
component: () => import('@/views/dataset/index.vue')
},
{
path: '/setting',
name: 'setting',
meta: { icon: 'setting', title: '系统设置', permission: 'SETTING:READ' },
component: () => import('@/views/setting/index.vue')
}
...rolesRoutes
]
},
{

View File

@ -9,15 +9,11 @@ html {
}
body {
font-family:
Helvetica,
PingFang SC,
Arial,
sans-serif;
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
'微软雅黑', Arial, sans-serif;
height: 100%;
margin: 0;
padding: 0;
@ -95,6 +91,10 @@ ul {
margin-bottom: 20px;
}
.flex {
display: flex;
}
.flex-center {
display: flex;
align-items: center;

View File

@ -1,3 +1,8 @@
.el-avatar {
--el-avatar-bg-color: var(--el-color-primary);
--el-avatar-size-small: 33px;
cursor: pointer;
}
.el-popper {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
@ -5,24 +10,22 @@
.el-form {
--el-form-inline-content-width: 100%;
}
// 抽屉样式整体修改
.el-drawer{
.el-drawer__header{
padding: 0;
margin: 0 24px;
height: 56px;
border-bottom: 1px solid #D5D6D8;
.el-drawer__title {
.el-drawer {
.el-drawer__header {
padding: 0;
margin: 0 24px;
height: 56px;
border-bottom: 1px solid #d5d6d8;
.el-drawer__title {
color: #1f2329;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
}
.el-drawer__body{
--el-drawer-padding-primary:24px
.el-drawer__body {
--el-drawer-padding-primary: 24px;
}
}
}

View File

@ -1,11 +1,14 @@
:root{
--app-base-text-color:rgba(31, 35, 41, 1);
--app-base-text-font-size:14px;
--app-base-text-hover-color:rgba(51, 112, 255, 1);
--app-base-text-hover-bg-color:rgba(51, 112, 255, 0.1);
--app-base-action-text-color:var(--app-base-text-hover-color );
/** header 组件 */
--app-header-height: 56px;
--app-header-padding: 0 20px;
--app-header-bg-color: #252b3c;
:root {
--el-color-primary: rgba(51, 112, 255, 1);
--app-base-text-color: rgba(31, 35, 41, 1);
--app-base-text-font-size: 14px;
--app-base-text-hover-color: rgba(51, 112, 255, 1);
--app-base-text-hover-bg-color: rgba(51, 112, 255, 0.1);
--app-base-action-text-color: var(--app-base-text-hover-color);
/** header 组件 */
--app-header-height: 56px;
--app-header-padding: 0 20px;
--app-header-bg-color: #252b3c;
/** top-bar 组件 */
}

45
ui/src/utils/message.ts Normal file
View File

@ -0,0 +1,45 @@
import { ElMessageBox, ElMessage } from 'element-plus'
export const MsgSuccess = (message: string) => {
ElMessage.success({
message: message,
type: 'success',
showClose: true,
duration: 1500
})
}
export const MsgInfo = (message: string) => {
ElMessage.info({
message: message,
type: 'info',
showClose: true,
duration: 1500
})
}
export const MsgWarning = (message: string) => {
ElMessage.warning({
message: message,
type: 'warning',
showClose: true,
duration: 1500
})
}
export const MsgError = (message: string) => {
ElMessage.error({
message: message,
type: 'error',
showClose: true,
duration: 1500
})
}
export const MsgConfirm = (message: string, options = {}) => {
const defaultOptions: Object = {
type: 'warning',
...options
}
return ElMessageBox.confirm(message, '确认', defaultOptions)
}

View File

@ -1,6 +1,6 @@
import { store } from '@/stores'
import { useUserStore } from '@/stores/user'
import { Role, Permission, ComplexPermission } from '@/common/permission/type'
import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
/**
*
* @param permission

View File

@ -1,16 +1,19 @@
<template >
<div>
<el-button v-hasPermission="'USER:DELETE'">用户删除权限</el-button>
<el-button v-hasPermission="'USER:READ'">用户只读权限</el-button>
<el-button v-hasPermission="new Role('USER')">普通用户角色</el-button>
<el-button v-hasPermission="[new Role('ADMIN'), new Role('USER')]">普通用户或者管理员</el-button>
<el-button
v-hasPermission="{ permission: ['USER:READ', new Role('USER')], compare: 'AND' }">普通角色并且用户只读权限</el-button>
<template>
<div>
<el-button v-hasPermission="'USER:DELETE'">用户删除权限</el-button>
<el-button v-hasPermission="'USER:READ'">用户只读权限</el-button>
<el-button v-hasPermission="new Role('USER')">普通用户角色</el-button>
<el-button v-hasPermission="[new Role('ADMIN'), new Role('USER')]"
>普通用户或者管理员</el-button
>
<el-button v-hasPermission="{ permission: ['USER:READ', new Role('USER')], compare: 'AND' }"
>普通角色并且用户只读权限</el-button
>
首页
</div>
首页
</div>
</template>
<script setup lang="ts">
import { Role } from "@/common/permission/type"
import { Role } from '@/utils/permission/type'
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@ -16,7 +16,7 @@
placeholder="请输入邮箱"
>
<template #prepend>
<el-button :icon="UserFilled" />
<el-button icon="UserFilled" />
</template>
</el-input>
</el-form-item>
@ -29,7 +29,7 @@
placeholder="请输入验证码"
>
<template #prepend>
<el-button :icon="Key" />
<el-button icon="Key" />
</template>
</el-input>
<el-button
@ -61,12 +61,11 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { UserFilled, Key } from '@element-plus/icons-vue'
import type { CheckCodeRequest } from '@/api/user/type'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import UserApi from '@/api/user/index'
import { ElMessage } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
const router = useRouter()
const CheckEmailForm = ref<CheckCodeRequest>({
@ -108,7 +107,7 @@ const sendEmail = () => {
resetPasswordFormRef.value?.validateField('email', (v: boolean) => {
if (v) {
UserApi.sendEmit(CheckEmailForm.value.email, 'reset_password', loading).then(() => {
ElMessage.success('发送验证码成功')
MsgSuccess('发送验证码成功')
})
}
})

View File

@ -100,7 +100,7 @@ import type { RegisterRequest } from '@/api/user/type'
import { UserFilled, Lock, Message, Key } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import UserApi from '@/api/user/index'
import { ElMessage } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import type { FormInstance, FormRules } from 'element-plus'
const router = useRouter()
@ -192,7 +192,7 @@ const sendEmail = () => {
registerFormRef.value?.validateField('email', (v: boolean) => {
if (v) {
UserApi.sendEmit(registerForm.value.email, 'register', sendEmailLoading).then(() => {
ElMessage.success('发送验证码成功')
MsgSuccess('发送验证码成功')
})
}
})

View File

@ -18,7 +18,7 @@
show-password
>
<template #prepend>
<el-button :icon="Lock" />
<el-button icon="Lock" />
</template>
</el-input>
</el-form-item>
@ -32,7 +32,7 @@
show-password
>
<template #prepend>
<el-button :icon="Lock" />
<el-button icon="Lock" />
</template>
</el-input>
</el-form-item>
@ -57,9 +57,8 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { ResetPasswordRequest } from '@/api/user/type'
import { Lock } from '@element-plus/icons-vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import type { FormInstance, FormRules } from 'element-plus'
import UserApi from '@/api/user/index'
const router = useRouter()
@ -127,7 +126,7 @@ const resetPassword = () => {
?.validate()
.then(() => UserApi.resetPassword(resetPasswordForm.value, loading))
.then(() => {
ElMessage.success('修改密码成功')
MsgSuccess('修改密码成功')
router.push({ name: 'login' })
})
}