首次提交
This commit is contained in:
25
chengduTestPlant/src/layout/404.vue
Normal file
25
chengduTestPlant/src/layout/404.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="page max-7xl mx-auto text-center">
|
||||
<div class="bg mx-auto">
|
||||
<img src="@/assets/404.svg" />
|
||||
<div class="mt-2">{{ $t("sys.notFoundPage") }}</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<a-button type="primary" @click="$router.push({ name: 'dashboard' })">{{ $t("sys.goHome") }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -200px;
|
||||
margin-left: -195px;
|
||||
}
|
||||
.bg,
|
||||
.bg img {
|
||||
width: 390px;
|
||||
}
|
||||
</style>
|
||||
78
chengduTestPlant/src/layout/components/banner/index.vue
Normal file
78
chengduTestPlant/src/layout/components/banner/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-layout class="layout flex flex-col h-full">
|
||||
<a-layout-header class="ma-ui-header flex justify-between h-50 layout-banner-header operation-area">
|
||||
<div class="flex justify-between md:justify-center logo">
|
||||
<a-avatar class="mt-1 ml-2 md:ml-0" :size="40"
|
||||
><img :src="`${$url}logo.svg`" class="bg-white"
|
||||
/></a-avatar>
|
||||
<span class="ml-2 text-xl mt-2.5 hidden md:block">{{ $title }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between w-full layout-banner">
|
||||
<children-banner v-model="userStore.routers" />
|
||||
<ma-operation />
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<ma-tags class="hidden lg:flex ma-ui-tags" />
|
||||
<ma-worker-area />
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from "vue"
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
import { useRoute } from "vue-router"
|
||||
import MaOperation from "../ma-operation.vue"
|
||||
import MaWorkerArea from "../ma-workerArea.vue"
|
||||
import MaTags from "../ma-tags.vue"
|
||||
import ChildrenBanner from "../components/children-banner.vue"
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const MaMenuRef = ref(null)
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const actives = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
actives.value = [route.name]
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(v) => {
|
||||
actives.value = [v.name]
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tags {
|
||||
margin-top: -1px;
|
||||
}
|
||||
:deep(.arco-menu-collapse-button) {
|
||||
right: 10px;
|
||||
}
|
||||
:deep(.layout-banner .arco-menu-horizontal .arco-menu-inner) {
|
||||
align-items: none;
|
||||
padding: 8px 10px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
:deep(.sys-menus .arco-menu-icon svg) {
|
||||
display: inline;
|
||||
vertical-align: none;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
:deep(.sys-menus .arco-menu-icon .icon) {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
</style>
|
||||
27
chengduTestPlant/src/layout/components/classic/index.vue
Normal file
27
chengduTestPlant/src/layout/components/classic/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-layout class="layout flex justify-between h-full">
|
||||
<ma-classic-slider class="ma-ui-slider" />
|
||||
|
||||
<a-layout-content class="flex flex-col">
|
||||
<ma-classic-header class="ma-ui-header" />
|
||||
<ma-worker-area />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
|
||||
import MaClassicSlider from "./ma-classic-slider.vue"
|
||||
import MaClassicHeader from "./ma-classic-header.vue"
|
||||
import MaWorkerArea from "../ma-workerArea.vue"
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-layout-header class="layout-classic-header flex flex-col operation-area">
|
||||
<div class="flex justify-between layout-classic-header-container">
|
||||
<a-avatar class="mt-1 ml-2 inline lg:hidden" style="width: 45px" :size="40"
|
||||
><img :src="`${$url}logo.svg`" class="bg-white"
|
||||
/></a-avatar>
|
||||
<ma-breadcrumb />
|
||||
<ma-operation />
|
||||
</div>
|
||||
<ma-tags class="hidden lg:flex" />
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MaBreadcrumb from "../ma-breadcrumb.vue"
|
||||
import MaOperation from "../ma-operation.vue"
|
||||
import MaTags from "../ma-tags.vue"
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-layout-sider
|
||||
class="layout-classic-sider h-full flex flex-col hidden lg:block"
|
||||
:style="`width: ${appStore.menuCollapse ? '48px' : appStore.menuWidth + 'px'};`"
|
||||
>
|
||||
<div class="flex justify-center logo">
|
||||
<a-avatar class="mt-1" :size="40"><img :src="`${$url}logo.svg`" class="bg-white" /></a-avatar>
|
||||
<span class="ml-2 text-xl mt-2.5" v-if="!appStore.menuCollapse">{{ $title }}</span>
|
||||
</div>
|
||||
<ma-menu ref="MaMenuRef" height="calc(100% - 51px)" :class="`${appStore.menuCollapse ? 'ml-1.5' : ''};`" />
|
||||
</a-layout-sider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue"
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
import MaMenu from "../ma-menu.vue"
|
||||
|
||||
const MaMenuRef = ref(null)
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout((_) => {
|
||||
MaMenuRef.value.menus = userStore.routers
|
||||
}, 50)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.logo {
|
||||
height: 51px;
|
||||
border-bottom: 1px solid var(--color-border-1);
|
||||
}
|
||||
</style>
|
||||
37
chengduTestPlant/src/layout/components/columns/index.vue
Normal file
37
chengduTestPlant/src/layout/components/columns/index.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<a-layout-content class="layout flex justify-between">
|
||||
<div id="layout-columns-left-panel" class="ma-ui-menu layout-columns-left-panel hidden lg:flex justify-between">
|
||||
<ma-columns-menu />
|
||||
</div>
|
||||
|
||||
<div class="layout-columns-right-panel flex flex-col" :style="`width: calc(100% - ${containerWidth}px)`">
|
||||
<ma-columns-header class="ma-ui-header" />
|
||||
<ma-worker-area />
|
||||
</div>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue"
|
||||
import ResizeObserver from "resize-observer-polyfill"
|
||||
import MaColumnsHeader from "./ma-columns-header.vue"
|
||||
import MaColumnsMenu from "./ma-columns-menu.vue"
|
||||
|
||||
import MaWorkerArea from "../ma-workerArea.vue"
|
||||
|
||||
const containerWidth = ref(0)
|
||||
|
||||
onMounted(() => {
|
||||
const dom = document.getElementById("layout-columns-left-panel")
|
||||
const robserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
// 可以通过 判断 entry.target得知当前改变的 Element,分别进行处理。
|
||||
switch (entry.target) {
|
||||
case dom:
|
||||
containerWidth.value = entry.contentRect.width
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
robserver.observe(dom)
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
|
||||
<template>
|
||||
<a-layout-header class="layout-header flex flex-col operation-area">
|
||||
<div class="flex justify-between" style="height: 50px">
|
||||
<a-avatar class="mt-1 ml-2 inline lg:hidden" style="width: 45px" :size="40"
|
||||
><img :src="`${$url}logo.svg`" class="bg-white"
|
||||
/></a-avatar>
|
||||
<ma-breadcrumb />
|
||||
<ma-operation />
|
||||
</div>
|
||||
<ma-tags class="hidden lg:flex" />
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MaBreadcrumb from "../ma-breadcrumb.vue"
|
||||
import MaOperation from "../ma-operation.vue"
|
||||
import MaTags from "../ma-tags.vue"
|
||||
</script>
|
||||
@@ -0,0 +1,129 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sider customer-scrollbar flex flex-col items-center bg-gray-800 dark:border-blackgray-5">
|
||||
<a-avatar class="mt-2" :size="40"><img :src="`${$url}logo.svg`" class="bg-white" /></a-avatar>
|
||||
<ul class="mt-1 parent-menu-container">
|
||||
<template v-for="(bigMenu, index) in userStore.routers" :key="index">
|
||||
<li :class="`${classStyle}`" @click="loadMenu(bigMenu, index)">
|
||||
<component v-if="bigMenu.meta.icon" :is="bigMenu.meta.icon" class="text-xl mt-1" />
|
||||
<span class="mt-0.5" :style="appStore.language === 'en' ? 'font-size: 10px' : ''">{{
|
||||
appStore.i18n
|
||||
? $t(`menus.${bigMenu.name}`).indexOf(".") > 0
|
||||
? bigMenu.meta.title
|
||||
: $t(`menus.${bigMenu.name}`)
|
||||
: bigMenu.meta.title
|
||||
}}</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="layout-menu shadow flex flex-col" v-show="showMenu">
|
||||
<div class="menu-title flex items-center" v-show="!appStore.menuCollapse">{{ title }}</div>
|
||||
<a-layout-sider
|
||||
:style="`width: ${appStore.menuCollapse ? '50px' : appStore.menuWidth + 'px'};
|
||||
height: ${appStore.menuCollapse ? '100%' : 'calc(100% - 51px)'};`"
|
||||
>
|
||||
<ma-menu ref="MaMenuRef" :class="appStore.menuCollapse ? 'ml-0.5' : ''" />
|
||||
</a-layout-sider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import MaMenu from "../ma-menu.vue"
|
||||
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const MaMenuRef = ref(null)
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const showMenu = ref(false)
|
||||
|
||||
const title = ref("")
|
||||
const classStyle = ref(
|
||||
"flex flex-col parent-menu items-center rounded mt-1 text-gray-200 hover:bg-gray-700 dark:hover:text-gray-50 dark:hover:bg-blackgray-1"
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
initMenu()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(v) => {
|
||||
initMenu()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const initMenu = () => {
|
||||
let current
|
||||
if (route.matched[1]?.meta?.breadcrumb) {
|
||||
current = route.matched[1].meta.breadcrumb[0].name
|
||||
} else {
|
||||
current = "home"
|
||||
}
|
||||
if (userStore.routers && userStore.routers.length > 0) {
|
||||
userStore.routers.map((item, index) => {
|
||||
if (item.name == current) loadMenu(item, index)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadMenu = (bigMenu, index) => {
|
||||
if (bigMenu.meta.type === "L") {
|
||||
window.open(bigMenu.path)
|
||||
return
|
||||
}
|
||||
if (bigMenu.children.length > 0) {
|
||||
MaMenuRef.value.loadChildMenu(bigMenu)
|
||||
showMenu.value = true
|
||||
} else {
|
||||
showMenu.value = false
|
||||
router.push(bigMenu.path)
|
||||
}
|
||||
title.value = MaMenuRef.value?.title
|
||||
document.querySelectorAll(".parent-menu").forEach((item, id) => {
|
||||
index !== id ? item.classList.remove("active") : item.classList.add("active")
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.parent-menu-container {
|
||||
width: 62px;
|
||||
}
|
||||
.parent-menu {
|
||||
width: 62px;
|
||||
padding: 5px;
|
||||
height: 57px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
fill: #fff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.parent-menu.active {
|
||||
background: rgb(var(--primary-6));
|
||||
color: #fff;
|
||||
}
|
||||
:deep(.arco-menu-vertical .arco-menu-inner) {
|
||||
padding: 4px;
|
||||
}
|
||||
:deep(.arco-menu-vertical .arco-menu-item) {
|
||||
padding: 0px 9px;
|
||||
line-height: 36px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-layout-content class="sys-menus">
|
||||
<a-menu
|
||||
ref="MaMenuRef"
|
||||
mode="horizontal"
|
||||
class="layout-banner-menu hidden lg:flex"
|
||||
:popup-max-height="360"
|
||||
:selected-keys="actives"
|
||||
>
|
||||
<template v-for="menu in modelValue" :key="menu.id">
|
||||
<template v-if="!menu.meta.hidden">
|
||||
<template v-if="!menu.children || menu.children.length === 0">
|
||||
<!-- 没有子菜单的进入 -->
|
||||
<a-menu-item :key="menu.name" @click="routerPush(menu)">
|
||||
<template #icon v-if="menu.meta.icon">
|
||||
<component
|
||||
:is="menu.meta.icon"
|
||||
:class="menu.meta.icon.indexOf('ma') > 0 ? 'icon' : ''"
|
||||
/>
|
||||
</template>
|
||||
{{
|
||||
appStore.i18n
|
||||
? $t(`menus.${menu.name}`).indexOf(".") > 0
|
||||
? menu.meta.title
|
||||
: $t(`menus.${menu.name}`)
|
||||
: menu.meta.title
|
||||
}}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<!-- 有子菜单的进入 -->
|
||||
<template v-else>
|
||||
<SubMenu :menu-info="menu" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</a-menu>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from "vue"
|
||||
import { useTagStore, useAppStore } from "@/store"
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
import SubMenu from "./sub-menu.vue"
|
||||
defineProps({ modelValue: Array })
|
||||
const router = useRouter()
|
||||
const emits = defineEmits(["go"])
|
||||
const appStore = useAppStore()
|
||||
const tagStore = useTagStore()
|
||||
const route = useRoute()
|
||||
const actives = ref([])
|
||||
onMounted(() => {
|
||||
actives.value = [route.name]
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(v) => {
|
||||
actives.value = [v.name]
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
const routerPush = (menu) => {
|
||||
if (menu.meta && menu.meta.type === "L") {
|
||||
window.open(menu.path)
|
||||
} else {
|
||||
router.push(menu.path)
|
||||
tagStore.addTag({ name: menu.name, title: menu.meta.title, path: menu.path })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sys-menus .icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.arco-menu-selected .icon {
|
||||
fill: rgb(var(--primary-6));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
|
||||
<template>
|
||||
<a-layout-content class="sys-menus">
|
||||
<template v-for="menu in modelValue" :key="menu.id">
|
||||
<template v-if="!menu.meta.hidden">
|
||||
<a-menu-item
|
||||
v-if="!menu.children || menu.children.length === 0"
|
||||
:key="menu.name"
|
||||
@click="routerPush(menu)"
|
||||
>
|
||||
<template #icon v-if="menu.meta.icon">
|
||||
<component :is="menu.meta.icon" :class="menu.meta.icon.indexOf('ma') > 0 ? 'icon' : ''" />
|
||||
</template>
|
||||
{{
|
||||
appStore.i18n
|
||||
? $t(`menus.${menu.name}`).indexOf(".") > 0
|
||||
? menu.meta.title
|
||||
: $t(`menus.${menu.name}`)
|
||||
: menu.meta.title
|
||||
}}
|
||||
</a-menu-item>
|
||||
<a-sub-menu v-else :key="menu.name">
|
||||
<template #icon v-if="menu.meta.icon">
|
||||
<component :is="menu.meta.icon" :class="menu.meta.icon.indexOf('ma') > 0 ? 'icon' : ''" />
|
||||
</template>
|
||||
<template #title @click="routerPush(menu.path)">
|
||||
{{
|
||||
appStore.i18n
|
||||
? $t(`menus.${menu.name}`).indexOf(".") > 0
|
||||
? menu.meta.title
|
||||
: $t(`menus.${menu.name}`)
|
||||
: menu.meta.title
|
||||
}}
|
||||
</template>
|
||||
<template v-if="menu.children">
|
||||
<children-menu v-model="menu.children" />
|
||||
</template>
|
||||
</a-sub-menu>
|
||||
</template>
|
||||
</template>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useTagStore, useAppStore } from "@/store"
|
||||
import { useRouter } from "vue-router"
|
||||
|
||||
defineProps({ modelValue: Array })
|
||||
|
||||
const router = useRouter()
|
||||
const emits = defineEmits(["go"])
|
||||
const appStore = useAppStore()
|
||||
const tagStore = useTagStore()
|
||||
|
||||
const routerPush = (menu) => {
|
||||
if (menu.meta && menu.meta.type === "L") {
|
||||
window.open(menu.path)
|
||||
} else {
|
||||
router.push(menu.path)
|
||||
tagStore.addTag({ name: menu.name, title: menu.meta.title, path: menu.path })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sys-menus .icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
.arco-menu-selected .icon {
|
||||
fill: rgb(var(--primary-6));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="w-full h-full" v-show="$route.meta.type === 'I'">
|
||||
<iframe
|
||||
v-for="item in iframeStore.iframes"
|
||||
v-show="item.meta.url === $route.meta.url"
|
||||
:src="item.meta.url"
|
||||
:key="item.name"
|
||||
frameborder="0"
|
||||
class="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch } from "vue"
|
||||
import { useIframeStore } from "@/store"
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
const iframeStore = useIframeStore()
|
||||
const route = useRoute()
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(value) => {
|
||||
pushRoute(value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const pushRoute = (r) => {
|
||||
if (r.meta.type === "I") {
|
||||
iframeStore.addIframe(r)
|
||||
}
|
||||
}
|
||||
|
||||
pushRoute(route)
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="mgs-nfc rounded p-2 shadow-lg">
|
||||
<a-tabs default-active-key="message" type="rounded">
|
||||
<a-tab-pane key="message">
|
||||
<template #title>
|
||||
{{ $t("sys.operationMessage.message") }}
|
||||
<a-badge
|
||||
:count="5"
|
||||
dot
|
||||
:dotStyle="{ width: '5px', height: '5px', top: '-8px' }"
|
||||
v-if="messageStore.messageList.length > 0"
|
||||
>
|
||||
</a-badge>
|
||||
</template>
|
||||
<a-list :max-height="230" class="h-full" v-if="messageStore.messageList.length > 0">
|
||||
<a-list-item
|
||||
v-for="item in messageStore.messageList"
|
||||
:key="item.id"
|
||||
class="cursor-pointer"
|
||||
@click="viewMessage(item)"
|
||||
>
|
||||
<a-list-item-meta :title="item.title">
|
||||
<template #description>
|
||||
<div class="flex justify-between" style="font-size: 13px">
|
||||
<span>发送人:{{ item.send_user.nickname }}</span>
|
||||
<span>时间:{{ item.created_at.substr(0, 10) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar shape="square">
|
||||
<img
|
||||
alt="avatar"
|
||||
:src="`${item.send_user.avatar ? item.send_user.avatar : $url + 'avatar.jpg'}`"
|
||||
/>
|
||||
</a-avatar>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
<a-empty v-else class="h-full" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="todo">
|
||||
<template #title>{{ $t("sys.operationMessage.todo") }}</template>
|
||||
<a-empty class="h-full" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
<a-modal v-model:visible="detailVisible" fullscreen :footer="false">
|
||||
<template #title>消息详情</template>
|
||||
<a-typography :style="{ marginTop: '-30px' }">
|
||||
<a-typography-title class="text-center">
|
||||
{{ row?.title }}
|
||||
</a-typography-title>
|
||||
<a-typography-paragraph class="text-right" style="font-size: 13px; color: var(--color-text-3)">
|
||||
<a-space size="large">
|
||||
<span>创建时间:{{ row?.created_at }}</span>
|
||||
</a-space>
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph>
|
||||
<div v-html="row?.content"></div>
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
import { useMessageStore } from "@/store"
|
||||
import queueMessage from "@/api/system/queueMessage"
|
||||
const messageStore = useMessageStore()
|
||||
|
||||
const row = ref({})
|
||||
const detailVisible = ref(false)
|
||||
|
||||
const viewMessage = async (record) => {
|
||||
row.value = record
|
||||
await queueMessage.updateReadStatus({ ids: [record.id] })
|
||||
messageStore.messageList.find((item, idx) => {
|
||||
if (item && record.id == item.id) messageStore.messageList.splice(idx, 1)
|
||||
})
|
||||
detailVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.mgs-nfc {
|
||||
width: 400px;
|
||||
background: var(--color-bg-1);
|
||||
height: 360px;
|
||||
border: 1px solid var(--color-border-1);
|
||||
margin-right: 10px;
|
||||
margin-top: 9px;
|
||||
position: relative;
|
||||
}
|
||||
:deep(.arco-list-item-meta-content) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
11
chengduTestPlant/src/layout/components/components/search.vue
Normal file
11
chengduTestPlant/src/layout/components/components/search.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
|
||||
<template></template>
|
||||
63
chengduTestPlant/src/layout/components/components/skin.vue
Normal file
63
chengduTestPlant/src/layout/components/components/skin.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-modal v-model:visible="visible" width="600px" @cancel="close" :footer="false">
|
||||
<template #title>{{ $t("sys.changeSkin") }}</template>
|
||||
<div class="flex flex-col">
|
||||
<a-card
|
||||
v-for="(item, index) in skinList"
|
||||
:key="item.name"
|
||||
:class="index === 0 ? '' : 'mt-3'"
|
||||
:body-style="{ width: '100%', display: 'flex', justifyContent: 'space-between', padding: '10px' }"
|
||||
>
|
||||
<a-row class="w-full flex items-center">
|
||||
<a-col :span="3" class="flex flex-col text-center">
|
||||
<div class="leading-6">{{ $t(`skin.${item.name}`) }}</div>
|
||||
</a-col>
|
||||
<a-col :span="6" class="flex flex-col text-center">
|
||||
<a-image :src="$url + item.thumb" class="rounded border" />
|
||||
</a-col>
|
||||
<a-col :span="12" class="flex items-center pl-3 pr-3">
|
||||
{{ $t(`skin.${item.name}Desc`) }}
|
||||
</a-col>
|
||||
<a-col :span="3" class="flex items-center justify-end">
|
||||
<a-button
|
||||
:type="appStore.skin === item.name ? 'primary' : 'secondary'"
|
||||
:disabled="appStore.skin === item.name"
|
||||
@click="useSkin(item.name)"
|
||||
>
|
||||
{{ appStore.skin === item.name ? $t("skin.activated") : $t("skin.use") }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from "vue"
|
||||
import { useAppStore } from "@/store"
|
||||
import skins from "@/config/skins"
|
||||
|
||||
const visible = ref(false)
|
||||
const appStore = useAppStore()
|
||||
|
||||
const open = () => (visible.value = true)
|
||||
const close = () => (visible.value = false)
|
||||
|
||||
const useSkin = (name) => appStore.useSkin(name)
|
||||
|
||||
const skinList = reactive(skins)
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-sub-menu :key="menuInfo.name">
|
||||
<template #title>
|
||||
{{ $t(`menus.${menuInfo.name}`).indexOf(".") > 0 ? menuInfo.meta.title : $t(`menus.${menuInfo.name}`) }}
|
||||
</template>
|
||||
<template #icon v-if="menuInfo.meta.icon">
|
||||
<component :is="menuInfo.meta.icon" :class="menuInfo.meta.icon.indexOf('ma') > 0 ? 'icon' : ''" />
|
||||
</template>
|
||||
<template v-for="item in menuInfo.children" :key="item.id">
|
||||
<template v-if="!item.children || item.children.length === 0">
|
||||
<a-menu-item :key="item.name" @click="routerPush(item)">
|
||||
<template #icon v-if="item.meta.icon">
|
||||
<component :is="item.meta.icon" :class="menuInfo.meta.icon.indexOf('ma') > 0 ? 'icon' : ''" />
|
||||
</template>
|
||||
{{ $t(`menus.${item.name}`).indexOf(".") > 0 ? item.meta.title : $t(`menus.${item.name}`) }}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<SubMenu :menu-info="item" />
|
||||
</template>
|
||||
</template>
|
||||
</a-sub-menu>
|
||||
</template>
|
||||
|
||||
<script setup name="SubMenu">
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
import { useTagStore } from "@/store"
|
||||
defineProps({ menuInfo: Object })
|
||||
const emits = defineEmits(["go"])
|
||||
const router = useRouter()
|
||||
const tagStore = useTagStore()
|
||||
const routerPush = (menu) => {
|
||||
if (menu.meta && menu.meta.type === "L") {
|
||||
window.open(menu.path)
|
||||
} else {
|
||||
router.push(menu.path)
|
||||
tagStore.addTag({ name: menu.name, title: menu.meta.title, path: menu.path })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.arco-trigger-menu-icon .icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
[mine-skin="mine"] .arco-menu-selected .icon {
|
||||
fill: rgb(var(--primary-6));
|
||||
}
|
||||
</style>
|
||||
29
chengduTestPlant/src/layout/components/ma-breadcrumb.vue
Normal file
29
chengduTestPlant/src/layout/components/ma-breadcrumb.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="ml-2 mt-3.5 hidden lg:block">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item class="cursor-pointer" @click="router.push('/dashboard')">
|
||||
{{ $t("menus.dashboard") }}
|
||||
</a-breadcrumb-item>
|
||||
<template v-for="(r, index) in route.matched" :key="index">
|
||||
<a-breadcrumb-item v-if="index > 0 && !['/', '/home', '/dashboard'].includes(r.path)">
|
||||
{{
|
||||
appStore.i18n
|
||||
? $t("menus." + r.name).indexOf(".") > 0
|
||||
? r.meta.title
|
||||
: $t("menus." + r.name)
|
||||
: r.meta.title
|
||||
}}
|
||||
</a-breadcrumb-item>
|
||||
</template>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { useAppStore } from "@/store"
|
||||
|
||||
const appStore = useAppStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
</script>
|
||||
27
chengduTestPlant/src/layout/components/ma-buttonMenu.vue
Normal file
27
chengduTestPlant/src/layout/components/ma-buttonMenu.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="block lg:hidden button-menu">
|
||||
<a-trigger :trigger="['click']" clickToClose position="top" v-model:popupVisible="popupVisible">
|
||||
<div :class="`button-trigger ${popupVisible ? 'button-trigger-active' : ''}`">
|
||||
<icon-close v-if="popupVisible" />
|
||||
<icon-menu v-else />
|
||||
</div>
|
||||
<template #content>
|
||||
<a-menu mode="popButton" showCollapseButton :popup-max-height="360">
|
||||
<children-menu v-model="userStore.routers" />
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
|
||||
import ChildrenMenu from "./components/children-menu.vue"
|
||||
|
||||
const userStore = useUserStore()
|
||||
const popupVisible = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
103
chengduTestPlant/src/layout/components/ma-menu.vue
Normal file
103
chengduTestPlant/src/layout/components/ma-menu.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<a-menu
|
||||
class="ma-menu"
|
||||
:style="{ width: appStore.menuWidth + 'px', height: props.height }"
|
||||
breakpoint="md"
|
||||
v-model:open-keys="openKeys"
|
||||
v-model:selected-keys="actives"
|
||||
:accordion="true"
|
||||
:collapsed-width="45"
|
||||
show-collapse-button
|
||||
:collapsed="appStore.menuCollapse"
|
||||
@collapse="onCollapse"
|
||||
:popup-max-height="360"
|
||||
auto-scroll-into-view
|
||||
>
|
||||
<children-menu v-model="menus" />
|
||||
</a-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from "vue"
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
import { useI18n } from "vue-i18n"
|
||||
|
||||
import ChildrenMenu from "./components/children-menu.vue"
|
||||
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const menus = ref([])
|
||||
const actives = ref([])
|
||||
const openKeys = ref([])
|
||||
const title = ref("")
|
||||
|
||||
onMounted(() => {
|
||||
actives.value = [route.name]
|
||||
findTopMenuName()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(v) => {
|
||||
actives.value = [v.name]
|
||||
findTopMenuName()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const loadChildMenu = (obj) => {
|
||||
if (obj.children && obj.children.length > 0) {
|
||||
menus.value = obj.children
|
||||
title.value = t("menus." + obj.name).indexOf(".") > 0 ? obj.meta.title : t("menus." + obj.name)
|
||||
}
|
||||
}
|
||||
|
||||
const findTopMenuName = () => {
|
||||
if (route.matched[1] && route.matched[1].meta && !route.matched[1].meta.breadcrumb) {
|
||||
openKeys.value = []
|
||||
route.matched.map((item, index) => {
|
||||
if (route.matched[0].name === "layout") {
|
||||
openKeys.value.push("home")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
openKeys.value = []
|
||||
if (route.matched[1] && route.matched[1].meta) {
|
||||
route.matched[1].meta.breadcrumb.map((item) => {
|
||||
openKeys.value.push(item.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onCollapse = (val) => {
|
||||
appStore.toggleMenu(val)
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
height: { type: String, default: "100%" }
|
||||
})
|
||||
|
||||
defineExpose({ loadChildMenu, title, actives, menus, openKeys, findTopMenuName })
|
||||
</script>
|
||||
<style scoped>
|
||||
:deep(.arco-menu-vertical .arco-menu-inner) {
|
||||
padding: 0;
|
||||
}
|
||||
:deep(.arco-menu-collapse-button) {
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
}
|
||||
:deep(.arco-menu-inner ::-webkit-scrollbar-thumb) {
|
||||
border: 4px solid transparent;
|
||||
background-clip: padding-box;
|
||||
border-radius: 7px;
|
||||
background-color: var(--color-text-4);
|
||||
}
|
||||
</style>
|
||||
151
chengduTestPlant/src/layout/components/ma-operation.vue
Normal file
151
chengduTestPlant/src/layout/components/ma-operation.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="mr-2 flex justify-end lg:justify-between w-full lg:w-auto">
|
||||
<a-space class="mr-0 lg:mr-5" size="medium">
|
||||
<a-tooltip :content="$t('sys.search')">
|
||||
<a-button :shape="'circle'" @click="() => (appStore.searchOpen = true)" class="hidden lg:inline">
|
||||
<template #icon>
|
||||
<icon-search />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- <a-tooltip content="锁屏">-->
|
||||
<!-- <a-button :shape="'circle'" class="hidden lg:inline">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <icon-lock />-->
|
||||
<!-- </template>-->
|
||||
<!-- </a-button>-->
|
||||
<!-- </a-tooltip>-->
|
||||
|
||||
<a-tooltip :content="isFullScreen ? $t('sys.closeFullScreen') : $t('sys.fullScreen')">
|
||||
<a-button :shape="'circle'" class="hidden lg:inline" @click="screen">
|
||||
<template #icon>
|
||||
<icon-fullscreen-exit v-if="isFullScreen" />
|
||||
<icon-fullscreen v-else />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-trigger trigger="click">
|
||||
<a-button :shape="'circle'">
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="5"
|
||||
dot
|
||||
:dotStyle="{ width: '5px', height: '5px' }"
|
||||
v-if="messageStore.messageList.length > 0"
|
||||
>
|
||||
<icon-notification />
|
||||
</a-badge>
|
||||
<icon-notification v-else />
|
||||
</template>
|
||||
</a-button>
|
||||
|
||||
<template #content>
|
||||
<message-notification />
|
||||
</template>
|
||||
</a-trigger>
|
||||
|
||||
<a-tooltip :content="$t('sys.pageSetting')">
|
||||
<a-button :shape="'circle'" @click="() => (appStore.settingOpen = true)" class="hidden lg:inline">
|
||||
<template #icon>
|
||||
<icon-settings />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<a-dropdown @select="handleSelect" trigger="hover">
|
||||
<a-avatar class="bg-blue-500 text-3xl avatar" style="top: -1px">
|
||||
<img :src="userStore.user && userStore.user.avatar ? userStore.user.avatar : $url + 'avatar.jpg'" />
|
||||
</a-avatar>
|
||||
|
||||
<template #content>
|
||||
<a-doption value="userCenter"><icon-user /> {{ $t("sys.userCenter") }}</a-doption>
|
||||
<a-doption value="clearCache"><icon-delete /> {{ $t("sys.clearCache") }}</a-doption>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
<a-doption value="logout"><icon-poweroff /> {{ $t("sys.logout") }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-modal v-model:visible="showLogoutModal" @ok="handleLogout" @cancel="handleLogoutCancel">
|
||||
<template #title>{{ $t("sys.logoutAlert") }}</template>
|
||||
<div>{{ $t("sys.logoutMessage") }}</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
import { useAppStore, useUserStore, useMessageStore } from "@/store"
|
||||
import tool from "@/utils/tool"
|
||||
import MessageNotification from "./components/message-notification.vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useI18n } from "vue-i18n"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import WsMessage from "@/ws-serve/message"
|
||||
import { info } from "@/utils/common"
|
||||
import commonApi from "@/api/common"
|
||||
|
||||
const { t } = useI18n()
|
||||
const messageStore = useMessageStore()
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const setting = ref(null)
|
||||
const router = useRouter()
|
||||
const isFullScreen = ref(false)
|
||||
const showLogoutModal = ref(false)
|
||||
const isDev = ref(import.meta.env.DEV)
|
||||
|
||||
const handleSelect = async (name) => {
|
||||
if (name === "userCenter") {
|
||||
router.push({ name: "userCenter" })
|
||||
}
|
||||
if (name === "clearCache") {
|
||||
const res = await commonApi.clearAllCache()
|
||||
tool.local.remove("dictData")
|
||||
res.success && Message.success(res.message)
|
||||
}
|
||||
if (name === "logout") {
|
||||
showLogoutModal.value = true
|
||||
document.querySelector("#app").style.filter = "grayscale(1)"
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await userStore.logout()
|
||||
document.querySelector("#app").style.filter = "grayscale(0)"
|
||||
window.location.href = "/"
|
||||
}
|
||||
|
||||
const handleLogoutCancel = () => {
|
||||
document.querySelector("#app").style.filter = "grayscale(0)"
|
||||
}
|
||||
|
||||
const screen = () => {
|
||||
tool.screen(document.documentElement)
|
||||
isFullScreen.value = !isFullScreen.value
|
||||
}
|
||||
|
||||
const Wsm = new WsMessage()
|
||||
Wsm.connection()
|
||||
Wsm.getMessage()
|
||||
|
||||
Wsm.ws.on("ev_new_message", (msg, data) => {
|
||||
if (data.length > messageStore.messageList.length) {
|
||||
info("新消息提示", "您有新的消息,请注意查收!")
|
||||
}
|
||||
messageStore.messageList = data
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
:deep(.arco-avatar-text) {
|
||||
top: 1px;
|
||||
}
|
||||
:deep(.arco-divider-horizontal) {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
margin-top: 6px;
|
||||
}
|
||||
</style>
|
||||
427
chengduTestPlant/src/layout/components/ma-tags.vue
Normal file
427
chengduTestPlant/src/layout/components/ma-tags.vue
Normal file
@@ -0,0 +1,427 @@
|
||||
<template>
|
||||
<div class="flex justify-between tags-container" ref="containerDom" v-if="appStore.tag">
|
||||
<div class="menu-tags-wrapper" ref="scrollbarDom" :class="{ 'tag-pn': tagShowPrevNext }">
|
||||
<div class="tags" ref="tags">
|
||||
<a
|
||||
v-for="tag in tagStore.tags"
|
||||
:key="tag.path"
|
||||
@contextmenu.prevent="openContextMenu($event, tag)"
|
||||
:class="route.fullPath == tag.path ? 'active' : ''"
|
||||
@click="tagJump(tag)"
|
||||
>
|
||||
{{
|
||||
tag.customTitle
|
||||
? tag.customTitle
|
||||
: appStore.i18n
|
||||
? $t("menus." + tag.name).indexOf(".") > 0
|
||||
? tag.title
|
||||
: $t("menus." + tag.name)
|
||||
: tag.title
|
||||
}}
|
||||
<icon-close class="tag-icon" v-if="!tag.affix" @click.stop="closeTag(tag)" />
|
||||
</a>
|
||||
</div>
|
||||
<span class="ma-tag-prev" v-if="tagShowPrevNext">
|
||||
<IconLeft :size="20" class="tag-scroll-icon" @click="handleScroll(-500)" />
|
||||
</span>
|
||||
<span class="ma-tag-next" v-if="tagShowPrevNext">
|
||||
<IconRight :size="20" class="tag-scroll-icon" @click="handleScroll(500)" />
|
||||
</span>
|
||||
</div>
|
||||
<a-trigger class="ma-tags-more-dropdown" :popup-translate="[-65, -6]" :show-arrow="true" trigger="hover">
|
||||
<span class="ma-tags-more">
|
||||
<span class="ma-tags-more-icon">
|
||||
<i class="ma-box ma-box-t"></i>
|
||||
<i class="ma-box ma-box-b"></i>
|
||||
</span>
|
||||
</span>
|
||||
<template #content>
|
||||
<ul class="ma-tags-more-contextmenu">
|
||||
<li @click="tagToolRefreshTag">
|
||||
<icon-refresh />
|
||||
{{ $t("sys.tags.refresh") }}
|
||||
</li>
|
||||
<a-divider class="dropdown-divider" />
|
||||
<li @click="tagToolCloseCurrentTag">
|
||||
<icon-close-circle />
|
||||
{{ $t("sys.tags.closeTag") }}
|
||||
</li>
|
||||
<li @click="tagToolCloseOtherTag">
|
||||
<icon-close-circle-fill />
|
||||
{{ $t("sys.tags.closeOtherTag") }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</a-trigger>
|
||||
<ul class="tags-contextmenu" v-if="contextMenuVisible" :style="{ left: left + 'px', top: top + 'px' }">
|
||||
<li @click="contextMenuRefreshTag">
|
||||
<icon-refresh />
|
||||
{{ $t("sys.tags.refresh") }}
|
||||
</li>
|
||||
<li @click="contextMenuMaxSizeTag">
|
||||
<icon-fullscreen />
|
||||
{{ $t("sys.tags.fullscreen") }}
|
||||
</li>
|
||||
<a-divider />
|
||||
<li @click="contextMenuCloseTag" :class="contextMenuItem.affix ? 'disabled' : ''">
|
||||
<icon-close-circle />
|
||||
{{ $t("sys.tags.closeTag") }}
|
||||
</li>
|
||||
<li @click="contextMenuCloseOtherTag">
|
||||
<icon-close-circle-fill />
|
||||
{{ $t("sys.tags.closeOtherTag") }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, nextTick } from "vue"
|
||||
import { useAppStore, useTagStore } from "@/store"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { addTag, closeTag, refreshTag } from "@/utils/common"
|
||||
import Sortable from "sortablejs"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { IconFaceFrownFill } from "@arco-design/web-vue/dist/arco-vue-icon"
|
||||
import tool from "@/utils/tool"
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const tagStore = useTagStore()
|
||||
const tags = ref(null)
|
||||
const tagShowPrevNext = ref(false)
|
||||
const contextMenuVisible = ref(false)
|
||||
const contextMenuItem = ref(null)
|
||||
const left = ref(0)
|
||||
const top = ref(0)
|
||||
const notAddTagList = ["login"]
|
||||
watch(
|
||||
() => appStore.tag,
|
||||
(r) => {
|
||||
nextTick(() => {
|
||||
tagShowPrevNext.value = tags.value.scrollWidth > tags.value.offsetWidth
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
watch(
|
||||
() => tagStore.tags,
|
||||
(r) => {
|
||||
nextTick(() => {
|
||||
tagShowPrevNext.value = tags.value.scrollWidth > tags.value.offsetWidth
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
watch(
|
||||
() => route,
|
||||
(r) => {
|
||||
if (!notAddTagList.includes(r.name)) {
|
||||
addTag({
|
||||
name: r.name,
|
||||
path: r.fullPath,
|
||||
affix: r.meta.affix,
|
||||
title: r.meta.title
|
||||
})
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
if (tags.value && tags.value.scrollWidth > tags.value.clientWidth) {
|
||||
//确保当前标签在可视范围内
|
||||
tags.value.querySelector(".active").scrollIntoView()
|
||||
}
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(contextMenuVisible, (value) => {
|
||||
const handler = (e) => {
|
||||
const dom = document.querySelector(".tags-contextmenu")
|
||||
if (dom && !dom.contains(e.target)) {
|
||||
closeContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
value
|
||||
? document.body.addEventListener("click", (e) => handler(e))
|
||||
: document.body.removeEventListener("click", (e) => handler(e))
|
||||
})
|
||||
|
||||
const tagJump = (tag) => {
|
||||
router.push({ path: tag.path, query: tool.getRequestParams(tag.path) })
|
||||
}
|
||||
|
||||
const openContextMenu = (e, tag) => {
|
||||
contextMenuItem.value = tag
|
||||
contextMenuVisible.value = true
|
||||
left.value = e.clientX + 1
|
||||
top.value = e.clientY + 1
|
||||
|
||||
nextTick(() => {
|
||||
const dom = document.querySelector(".tags-contextmenu")
|
||||
if (document.body.offsetWidth - e.clientX < dom.offsetWidth) {
|
||||
left.value = document.body.offsetWidth - dom.offsetWidth + 1
|
||||
top.value = e.clientY + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const closeContextMenu = () => {
|
||||
contextMenuItem.value = null
|
||||
contextMenuVisible.value = false
|
||||
}
|
||||
|
||||
const contextMenuMaxSizeTag = () => {
|
||||
const tag = contextMenuItem.value
|
||||
contextMenuVisible.value = false
|
||||
if (route.fullPath != tag.fullPath) {
|
||||
router.push({ path: tag.path, query: tool.getRequestParams(tag.path) })
|
||||
}
|
||||
document.getElementById("app").classList.add("max-size")
|
||||
}
|
||||
|
||||
const contextMenuRefreshTag = () => {
|
||||
const tag = contextMenuItem.value
|
||||
contextMenuVisible.value = false
|
||||
if (route.fullPath != tag.fullPath) {
|
||||
router.push({ path: tag.path, query: tool.getRequestParams(tag.path) })
|
||||
}
|
||||
refreshTag()
|
||||
}
|
||||
|
||||
const tagToolRefreshTag = () => {
|
||||
refreshTag()
|
||||
}
|
||||
const tagToolCloseCurrentTag = () => {
|
||||
const list = [...tagStore.tags]
|
||||
list.forEach((tag) => {
|
||||
if (tag.affix || route.path == tag.path) {
|
||||
closeTag(tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
const contextMenuCloseTag = () => {
|
||||
if (!contextMenuItem.value.affix) {
|
||||
closeTag(contextMenuItem.value)
|
||||
contextMenuVisible.value = false
|
||||
}
|
||||
}
|
||||
const tagToolCloseOtherTag = () => {
|
||||
const list = [...tagStore.tags]
|
||||
list.forEach((tag) => {
|
||||
if (tag.affix || route.path == tag.path) {
|
||||
return true
|
||||
} else {
|
||||
closeTag(tag)
|
||||
}
|
||||
})
|
||||
contextMenuVisible.value = false
|
||||
}
|
||||
const contextMenuCloseOtherTag = () => {
|
||||
const currentTag = contextMenuItem.value
|
||||
if (route.path != currentTag.path) {
|
||||
router.push({ path: currentTag.path })
|
||||
}
|
||||
const list = [...tagStore.tags]
|
||||
list.forEach((tag) => {
|
||||
if (tag.affix || currentTag.path == tag.path) {
|
||||
return true
|
||||
} else {
|
||||
closeTag(tag)
|
||||
}
|
||||
})
|
||||
contextMenuVisible.value = false
|
||||
}
|
||||
|
||||
const scrollHandler = (event) => {
|
||||
const detail = event.wheelDelta || event.detail
|
||||
const moveForwardStep = 1
|
||||
const moveBackStep = -1
|
||||
let step = 0
|
||||
if (detail == 3 || (detail < 0 && detail != -3)) {
|
||||
step = moveForwardStep * 50
|
||||
} else {
|
||||
step = moveBackStep * 50
|
||||
}
|
||||
tags.value.scrollLeft += step
|
||||
}
|
||||
const handleScroll = (offset) => {
|
||||
const distance = tags.value.scrollLeft
|
||||
const total = distance + offset
|
||||
const step = offset / 50
|
||||
moveSlow(distance, total, step)
|
||||
}
|
||||
const moveSlow = (distance, total, step) => {
|
||||
if (step > 0) {
|
||||
//往左滚
|
||||
if (distance < total) {
|
||||
distance += step
|
||||
tags.value.scrollLeft = distance
|
||||
window.requestAnimationFrame(() => {
|
||||
moveSlow(distance, total, step)
|
||||
})
|
||||
} else {
|
||||
tags.value.scrollLeft = total
|
||||
}
|
||||
} else {
|
||||
//往右滚
|
||||
if (distance > total) {
|
||||
distance += step
|
||||
tags.value.scrollLeft = distance
|
||||
window.requestAnimationFrame(() => {
|
||||
moveSlow(distance, total, step)
|
||||
})
|
||||
} else {
|
||||
tags.value.scrollLeft = total
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
if (tags.value) {
|
||||
Sortable.create(tags.value, { draggable: "a", animation: 300 })
|
||||
tags.value.addEventListener("mousewheel", scrollHandler, { passive: true }) ||
|
||||
tags.value.addEventListener("DOMMouseScroll", scrollHandler, { passive: true })
|
||||
nextTick(() => {
|
||||
tagShowPrevNext.value = tags.value.scrollWidth > tags.value.offsetWidth
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tag-pn {
|
||||
padding: 0 15px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.menu-tags-wrapper {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
|
||||
.ma-tag-next,
|
||||
.ma-tag-prev {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
height: 34px;
|
||||
|
||||
.tag-scroll-icon {
|
||||
cursor: pointer;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.tag-scroll-icon:hover {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
}
|
||||
|
||||
.ma-tag-prev {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.ma-tag-next {
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.tags a {
|
||||
margin-left: 4px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ma-tags-more {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
margin-left: 5px;
|
||||
top: -1px;
|
||||
|
||||
.ma-tags-more-icon {
|
||||
display: inline-block;
|
||||
color: var(--color-text-2);
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease-out;
|
||||
|
||||
.ma-box {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 14px;
|
||||
height: 8px;
|
||||
|
||||
.ma-box-t:before {
|
||||
transition: transform 0.3s ease-out 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.ma-box:before {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
content: "";
|
||||
background: var(--color-text-3);
|
||||
}
|
||||
|
||||
.ma-box:after {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
content: "";
|
||||
background: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ma-tags-more:hover .ma-tags-more-icon .ma-box:before {
|
||||
background: rgb(var(--primary-6));
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.ma-tags-more:hover .ma-tags-more-icon .ma-box:after {
|
||||
background: rgb(var(--primary-6));
|
||||
}
|
||||
|
||||
.ma-tags-more:hover .ma-tags-more-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ma-tags-more-contextmenu {
|
||||
border: 1px solid var(--color-border-2);
|
||||
padding: 5px 0;
|
||||
z-index: 100;
|
||||
width: 170px;
|
||||
background-color: var(--color-bg-5);
|
||||
border-radius: 4px;
|
||||
|
||||
li {
|
||||
padding: 7px 15px;
|
||||
color: var(--color-text-2);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.arco-divider-horizontal {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
chengduTestPlant/src/layout/components/ma-workerArea.vue
Normal file
30
chengduTestPlant/src/layout/components/ma-workerArea.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
<template>
|
||||
<a-layout-content class="work-area customer-scrollbar relative">
|
||||
<div class="h-full" :class="{ 'p-3': $route.path.indexOf('maIframe') === -1 }">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="appStore.animation" mode="out-in">
|
||||
<keep-alive :include="keepStore.keepAlives">
|
||||
<component :is="Component" :key="$route.fullPath" v-if="keepStore.show" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
<iframe-view />
|
||||
</div>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAppStore, useKeepAliveStore } from "@/store"
|
||||
import IframeView from "./components/iframe-view.vue"
|
||||
const appStore = useAppStore()
|
||||
const keepStore = useKeepAliveStore()
|
||||
</script>
|
||||
3
chengduTestPlant/src/layout/empty.vue
Normal file
3
chengduTestPlant/src/layout/empty.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
89
chengduTestPlant/src/layout/form.vue
Normal file
89
chengduTestPlant/src/layout/form.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="ma-content-block p-4">
|
||||
<a-page-header
|
||||
:style="{ background: 'var(--color-bg-2)' }"
|
||||
:title="pageTitle"
|
||||
:subtitle="`数据${opName}页`"
|
||||
@back="pageBack"
|
||||
>
|
||||
<ma-form v-model="form" :columns="formConfig?.formColumns ?? []" ref="maFormRef" @onSubmit="submitForm" />
|
||||
</a-page-header>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
import { useFormStore, useTagStore } from "@/store/index"
|
||||
import { useRoute } from "vue-router"
|
||||
import { isEmpty, isFunction } from "lodash"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { closeTag } from "@/utils/common"
|
||||
|
||||
const route = useRoute()
|
||||
const formStore = useFormStore()
|
||||
const tagStore = useTagStore()
|
||||
const maFormRef = ref()
|
||||
const tagId = ref(route.query?.tagId ?? "")
|
||||
const op = ref(route.query?.op ?? "")
|
||||
const key = ref(route.query?.key ?? "")
|
||||
|
||||
if (isEmpty(tagId.value)) {
|
||||
Message.error("缺少tagId参数")
|
||||
closeTag({ path: route.fullPath })
|
||||
}
|
||||
|
||||
if (isEmpty(op.value)) {
|
||||
Message.error("缺少op参数")
|
||||
closeTag({ path: route.fullPath })
|
||||
}
|
||||
|
||||
if (op.value === "edit" && isEmpty(key.value)) {
|
||||
Message.error("缺少key参数")
|
||||
closeTag({ path: route.fullPath })
|
||||
}
|
||||
|
||||
if (!formStore.formList[tagId.value]) {
|
||||
Message.error("缺少Form配置")
|
||||
closeTag({ path: route.fullPath })
|
||||
}
|
||||
|
||||
const formConfig = formStore.formList[tagId.value]?.config
|
||||
const form = ref(
|
||||
op.value === "add"
|
||||
? formStore.formList[tagId.value]?.addData
|
||||
: formStore.formList[tagId.value]["editData"][key.value]
|
||||
)
|
||||
const options = formConfig?.options
|
||||
const opName = ref(op.value === "add" ? "新增" : "编辑")
|
||||
const pageTitle = ref(opName.value + (formConfig?.options?.formOption?.tagName ?? "未命名"))
|
||||
tagStore.updateTagTitle(route.fullPath, ` ${pageTitle.value} ${op.value === "edit" ? " | " + key.value : ""} `)
|
||||
|
||||
const pageBack = () => {
|
||||
window.history.back(-1)
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
const formData = maFormRef.value.getFormData()
|
||||
if (await maFormRef.value.validateForm()) {
|
||||
return false
|
||||
}
|
||||
|
||||
let response
|
||||
if (op.value === "add") {
|
||||
isFunction(options.beforeAdd) && (await options.beforeAdd(formData))
|
||||
response = await options.add.api(formData)
|
||||
isFunction(options.afterAdd) && (await options.afterAdd(response, formData))
|
||||
} else {
|
||||
isFunction(options.beforeEdit) && (await options.beforeEdit(formData))
|
||||
response = await options.edit.api(formData[options.pk], formData)
|
||||
isFunction(options.afterEdit) && (await options.afterEdit(response, formData))
|
||||
}
|
||||
if (response.success) {
|
||||
await maFormRef.value.resetForm()
|
||||
Message.success(response.message || `${opName.value}成功!`)
|
||||
closeTag({ path: route.fullPath })
|
||||
formStore.crudList[options.id] = true
|
||||
} else if (response.success === false && (typeof response.code === "undefined" || response.code !== 200)) {
|
||||
Message.error(response.message || `${actionTitle.value}失败!`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
65
chengduTestPlant/src/layout/index.vue
Normal file
65
chengduTestPlant/src/layout/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<a-layout-content class="h-full main-container">
|
||||
<columns-layout v-if="appStore.layout === 'columns'" />
|
||||
<classic-layout v-if="appStore.layout === 'classic'" />
|
||||
<banner-layout v-if="appStore.layout === 'banner'" />
|
||||
|
||||
<setting ref="settingRef" />
|
||||
|
||||
<transition name="ma-slide-down" mode="out-in">
|
||||
<system-search ref="systemSearchRef" v-show="appStore.searchOpen" />
|
||||
</transition>
|
||||
|
||||
<ma-button-menu />
|
||||
|
||||
<div class="max-size-exit" @click="tagExitMaxSize"><icon-close /></div>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue"
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
|
||||
import ColumnsLayout from "./components/columns/index.vue"
|
||||
import ClassicLayout from "./components/classic/index.vue"
|
||||
import BannerLayout from "./components/banner/index.vue"
|
||||
import Setting from "./setting.vue"
|
||||
import SystemSearch from "./search.vue"
|
||||
import MaButtonMenu from "./components/ma-buttonMenu.vue"
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const settingRef = ref()
|
||||
const systemSearchRef = ref()
|
||||
|
||||
watch(
|
||||
() => appStore.settingOpen,
|
||||
(vl) => {
|
||||
if (vl === true) {
|
||||
settingRef.value.open()
|
||||
appStore.settingOpen = false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const tagExitMaxSize = () => {
|
||||
document.getElementById("app").classList.remove("max-size")
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("keydown", (e) => {
|
||||
const keyCode = e.keyCode ?? e.which ?? e.charCode
|
||||
const altKey = e.altKey ?? e.metaKey
|
||||
if (altKey && keyCode === 83) {
|
||||
appStore.searchOpen = true
|
||||
return
|
||||
}
|
||||
|
||||
if (keyCode === 27) {
|
||||
appStore.searchOpen = false
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
||||
244
chengduTestPlant/src/layout/search.vue
Normal file
244
chengduTestPlant/src/layout/search.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="sys-search-container">
|
||||
<div class="ssc-bg">
|
||||
<div class="w-6/12 mx-auto center-box">
|
||||
<div class="mt-10"><img :src="`${$url}logo.svg`" width="100" class="mx-auto" /></div>
|
||||
<div class="mt-10">
|
||||
<a-input
|
||||
size="large"
|
||||
ref="searchInputRef"
|
||||
placeholder="搜索页面,支持名称、标识以及URL的模糊查询"
|
||||
@input="searchPage"
|
||||
>
|
||||
<template #prefix><icon-search /></template>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<a-space size="large" class="flex justify-center">
|
||||
<a-space><a-tag>ALT+S</a-tag><a-tag>唤醒搜索面板</a-tag></a-space>
|
||||
<a-space
|
||||
><a-tag><icon-caret-up /></a-tag><a-tag><icon-caret-down /></a-tag
|
||||
><a-tag>切换搜索结果</a-tag></a-space
|
||||
>
|
||||
<a-space><a-tag>Enter</a-tag><a-tag>进入页面</a-tag></a-space>
|
||||
<a-space><a-tag>Esc</a-tag><a-tag>关闭搜索面板</a-tag></a-space>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<ul class="mt-10 results shadow-lg customer-scrollbar">
|
||||
<template v-for="res in resultList">
|
||||
<li
|
||||
class="flex items-center"
|
||||
v-if="res && res.path.indexOf(':') === -1 && res.components && res?.meta?.type === 'M'"
|
||||
@click="gotoPage(res)"
|
||||
>
|
||||
<div class="icon-box flex justify-center items-center">
|
||||
<component
|
||||
v-if="res.meta.icon"
|
||||
:is="res.meta.icon"
|
||||
:class="res.meta.icon.indexOf('ma') > 0 ? 'icon' : ''"
|
||||
/>
|
||||
<icon-menu v-else />
|
||||
</div>
|
||||
<div class="ml-5 leading-6">
|
||||
<div class="title">{{ res.meta.title }}</div>
|
||||
<div class="path">{{ res.path }}</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useAppStore } from "@/store"
|
||||
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
const searchInputRef = ref()
|
||||
const resultList = ref(router.getRoutes())
|
||||
|
||||
const searchPage = (value) => {
|
||||
resultList.value = router.getRoutes().filter((item) => {
|
||||
if (item.path && item.path.indexOf(value) > -1) {
|
||||
return true
|
||||
}
|
||||
if (item.name && item.name.indexOf(value) > -1) {
|
||||
return true
|
||||
}
|
||||
if (item.meta && item.meta.title && item.meta.title.indexOf(value) > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
const gotoPage = (res) => {
|
||||
appStore.searchOpen = false
|
||||
router.push(res.path)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchInputRef.value.focus()
|
||||
document.addEventListener("keydown", (e) => {
|
||||
const keyCode = e.keyCode ?? e.which ?? e.charCode
|
||||
const active = document.querySelector(".active-search-li")
|
||||
|
||||
const getActiveItemInfo = () => {
|
||||
const li = document.querySelectorAll(".results li")
|
||||
let activeItem = { idx: 0, path: "/" }
|
||||
li.forEach((item, index) => {
|
||||
if (item.className.split(" ").includes("active-search-li")) {
|
||||
activeItem.path = item.querySelector(".path").innerHTML
|
||||
activeItem.idx = index
|
||||
return
|
||||
}
|
||||
})
|
||||
return activeItem
|
||||
}
|
||||
|
||||
const add = (index) => {
|
||||
document.querySelectorAll(".results li")[index].classList.add("active-search-li")
|
||||
}
|
||||
const remove = (index) => {
|
||||
document.querySelectorAll(".results li")[index].classList.remove("active-search-li")
|
||||
}
|
||||
|
||||
if (appStore.searchOpen) {
|
||||
// down
|
||||
if (keyCode === 40) {
|
||||
if (!active) {
|
||||
add(0)
|
||||
return
|
||||
} else {
|
||||
const li = document.querySelectorAll(".results li")
|
||||
let item = getActiveItemInfo(),
|
||||
nextIndex = item.idx + 1
|
||||
if (nextIndex >= li.length) {
|
||||
nextIndex = 0
|
||||
}
|
||||
remove(item.idx)
|
||||
add(nextIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// up
|
||||
if (keyCode === 38) {
|
||||
if (!active) {
|
||||
add(document.querySelectorAll(".results li").length - 1)
|
||||
return
|
||||
} else {
|
||||
const li = document.querySelectorAll(".results li")
|
||||
let item = getActiveItemInfo(),
|
||||
prevIndex = item.idx - 1
|
||||
if (prevIndex < 0) {
|
||||
prevIndex = li.length - 1
|
||||
}
|
||||
remove(item.idx)
|
||||
add(prevIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCode === 13) {
|
||||
const item = getActiveItemInfo()
|
||||
remove(item.idx)
|
||||
item.path !== "/" && gotoPage(item)
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
const dom = document.querySelector(".results")
|
||||
if (dom && dom.scrollTop !== false) {
|
||||
dom.scrollTop =
|
||||
(getActiveItemInfo()["idx"] + 1) * 80 - document.querySelectorAll(".results li").length * 10
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.sys-search-container {
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
& .ssc-bg {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(100, 100, 100, 0.2);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
& .center-box {
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
& .results {
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 6px;
|
||||
height: calc(100% - 250px);
|
||||
overflow-y: auto;
|
||||
|
||||
& li {
|
||||
border-bottom: 1px solid var(--color-border-1);
|
||||
cursor: pointer;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.path {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
li:hover,
|
||||
.active-search-li {
|
||||
background-color: var(--color-neutral-1);
|
||||
.arco-icon,
|
||||
.icon {
|
||||
transition: all 0.25s;
|
||||
width: 1.8em !important;
|
||||
height: 1.8em !important;
|
||||
}
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
.icon {
|
||||
fill: rgb(var(--primary-6));
|
||||
}
|
||||
.title {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
.path {
|
||||
color: rgb(var(--primary-3));
|
||||
}
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-right: 1px solid var(--color-border-1);
|
||||
}
|
||||
}
|
||||
|
||||
.arco-icon,
|
||||
.icon {
|
||||
width: 1.5em !important;
|
||||
height: 1.5em !important;
|
||||
}
|
||||
.arco-menu-selected .icon {
|
||||
fill: rgb(var(--primary-6));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
191
chengduTestPlant/src/layout/setting.vue
Normal file
191
chengduTestPlant/src/layout/setting.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<!--
|
||||
- MineAdmin is committed to providing solutions for quickly building web applications
|
||||
- Please view the LICENSE file that was distributed with this source code,
|
||||
- For the full copyright and license information.
|
||||
- Thank you very much for using MineAdmin.
|
||||
-
|
||||
- @Author X.Mo<root@imoi.cn>
|
||||
- @Link https://gitee.com/xmo/mineadmin-vue
|
||||
-->
|
||||
|
||||
<template>
|
||||
<a-drawer
|
||||
class="backend-setting"
|
||||
v-model:visible="visible"
|
||||
:on-before-ok="save"
|
||||
width="350px"
|
||||
:ok-text="$t('sys.saveToBackend')"
|
||||
@cancel="close"
|
||||
unmountOnClose
|
||||
>
|
||||
<template #title>{{ $t("sys.backendSettingTitle") }}</template>
|
||||
<a-form :model="form" :auto-label-width="true">
|
||||
<a-row class="flex justify-center mb-5">
|
||||
<a-divider orientation="center"
|
||||
><span class="title">{{ $t("sys.systemPrimaryColor") }}</span></a-divider
|
||||
>
|
||||
<ColorPicker
|
||||
theme="dark"
|
||||
:color="appStore.color"
|
||||
:sucker-hide="true"
|
||||
:colors-default="defaultColorList"
|
||||
@changeColor="changeColor"
|
||||
style="width: 218px"
|
||||
/>
|
||||
</a-row>
|
||||
<a-divider orientation="center"
|
||||
><span class="title">{{ $t("sys.personalizedConfig") }}</span></a-divider
|
||||
>
|
||||
<a-form-item :label="$t('sys.skin')" :help="$t('sys.skinHelp')">
|
||||
{{ currentSkin }}
|
||||
<a-button type="primary" status="success" size="mini" class="ml-2" @click="skin.open()">
|
||||
{{ $t("sys.changeSkin") }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('sys.layouts')" :help="$t('sys.layoutsHelp')">
|
||||
<a-select v-model="form.layout" @change="handleLayout">
|
||||
<a-option value="classic">{{ $t("sys.layout.classic") }}</a-option>
|
||||
<a-option value="columns">{{ $t("sys.layout.columns") }}</a-option>
|
||||
<a-option value="banner">{{ $t("sys.layout.banner") }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('sys.i18n')" :help="$t('sys.i18nHelp')">
|
||||
<a-switch v-model="form.i18n" @change="handleI18n" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('sys.language')" :help="$t('sys.languageHelp')" v-if="form.i18n">
|
||||
<a-select v-model="form.language" @change="handleLanguage">
|
||||
<a-option value="zh_CN">{{ $t("sys.chinese") }}</a-option>
|
||||
<a-option value="en">{{ $t("sys.english") }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('sys.animation')" :help="$t('sys.animationHelp')">
|
||||
<a-select v-model="form.animation" @change="handleAnimation">
|
||||
<a-option value="ma-fade">{{ $t("sys.animate.fade") }}</a-option>
|
||||
<a-option value="ma-slide-left">{{ $t("sys.animate.sliderLeft") }}</a-option>
|
||||
<a-option value="ma-slide-right">{{ $t("sys.animate.sliderRight") }}</a-option>
|
||||
<a-option value="ma-slide-down">{{ $t("sys.animate.sliderDown") }}</a-option>
|
||||
<a-option value="ma-slide-up">{{ $t("sys.animate.sliderUp") }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('sys.dark')" :help="$t('sys.darkHelp')" v-if="currentSkin === 'Mine'">
|
||||
<a-switch v-model="form.mode" @change="handleSettingMode" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('sys.tag')" :help="$t('sys.tagHelp')">
|
||||
<a-switch v-model="form.tag" @change="handleSettingTag" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="form.layout !== 'banner'" :label="$t('sys.menuFold')" :help="$t('sys.menuFoldHelp')">
|
||||
<a-switch v-model="form.menuCollapse" @change="handleMenuCollapse" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="form.layout !== 'banner'" :label="$t('sys.menuWidth')" :help="$t('sys.menuWidthHelp')">
|
||||
<a-input-number v-model="form.menuWidth" mode="button" @change="handleMenuWidth" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
|
||||
<Skin ref="skin" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from "vue"
|
||||
import { useAppStore, useUserStore } from "@/store"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import user from "@/api/system/user"
|
||||
import Skin from "./components/components/skin.vue"
|
||||
import skins from "@/config/skins"
|
||||
import { useI18n } from "vue-i18n"
|
||||
import { ColorPicker } from "vue-color-kit"
|
||||
import "vue-color-kit/dist/vue-color-kit.css"
|
||||
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const skin = ref(null)
|
||||
const visible = ref(false)
|
||||
const okLoading = ref(false)
|
||||
const currentSkin = ref("")
|
||||
const form = reactive({
|
||||
mode: appStore.mode === "dark",
|
||||
tag: appStore.tag,
|
||||
menuCollapse: appStore.menuCollapse,
|
||||
menuWidth: appStore.menuWidth,
|
||||
layout: appStore.layout,
|
||||
language: appStore.language,
|
||||
animation: appStore.animation,
|
||||
i18n: appStore.i18n
|
||||
})
|
||||
|
||||
const defaultColorList = reactive([
|
||||
"#165DFF",
|
||||
"#F53F3F",
|
||||
"#F77234",
|
||||
"#F7BA1E",
|
||||
"#00B42A",
|
||||
"#14C9C9",
|
||||
"#3491FA",
|
||||
"#722ED1",
|
||||
"#F5319D",
|
||||
"#D91AD9",
|
||||
"#34C759",
|
||||
"#43a047",
|
||||
"#7cb342",
|
||||
"#c0ca33",
|
||||
"#86909c",
|
||||
"#6d4c41"
|
||||
])
|
||||
const changeColor = (color) => {
|
||||
appStore.changeColor(color.hex)
|
||||
}
|
||||
|
||||
skins.map((item) => {
|
||||
if (item.name === appStore.skin) currentSkin.value = t("skin." + item.name)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => appStore.skin,
|
||||
(v) => {
|
||||
skins.map((item) => {
|
||||
if (item.name === v) currentSkin.value = t("skin." + item.name)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const open = () => (visible.value = true)
|
||||
const close = () => (visible.value = false)
|
||||
|
||||
const handleLayout = (val) => appStore.changeLayout(val)
|
||||
const handleI18n = (val) => appStore.toggleI18n(val)
|
||||
const handleLanguage = (val) => appStore.changeLanguage(val)
|
||||
const handleAnimation = (val) => appStore.changeAnimation(val)
|
||||
const handleSettingMode = (val) => appStore.toggleMode(val ? "dark" : "light")
|
||||
const handleSettingTag = (val) => appStore.toggleTag(val)
|
||||
const handleMenuCollapse = (val) => appStore.toggleMenu(val)
|
||||
const handleMenuWidth = (val) => appStore.changeMenuWidth(val)
|
||||
|
||||
watch(
|
||||
() => appStore.menuCollapse,
|
||||
(val) => (form.menuCollapse = val)
|
||||
)
|
||||
|
||||
const save = async (done) => {
|
||||
const data = {
|
||||
mode: appStore.mode,
|
||||
tag: appStore.tag,
|
||||
menuCollapse: appStore.menuCollapse,
|
||||
menuWidth: appStore.menuWidth,
|
||||
layout: appStore.layout,
|
||||
skin: appStore.skin,
|
||||
i18n: appStore.i18n,
|
||||
language: appStore.language,
|
||||
animation: appStore.animation,
|
||||
color: appStore.color
|
||||
}
|
||||
|
||||
user.updateInfo({ id: userStore.user.id, backend_setting: data }).then((res) => {
|
||||
res.success && Message.success(res.message)
|
||||
})
|
||||
done(true)
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
Reference in New Issue
Block a user