项目首次提交
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import "@/assets/css/app.less";
|
||||
import "@/assets/theme/index.less";
|
||||
import "@/assets/theme/mobile.less";
|
||||
import FullscreenLayout from "@/layout/fullscreen-layout.vue";
|
||||
import Layout from "@/layout/index.vue";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import { defineComponent, onMounted, reactive, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import app from "./constants/app";
|
||||
import { EPageLayoutEnum, EThemeColor, EThemeSetting } from "./constants/enum";
|
||||
import { IObject } from "./types/interface";
|
||||
import { getThemeConfigCache, setThemeColor, updateTheme } from "./utils/theme";
|
||||
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
components: { Layout, FullscreenLayout, [ElConfigProvider.name]: ElConfigProvider },
|
||||
setup() {
|
||||
const store = useAppStore();
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
layout: location.href.includes("pop=true") ? EPageLayoutEnum.fullscreen : EPageLayoutEnum.page
|
||||
});
|
||||
onMounted(() => {
|
||||
//读取主题色缓存
|
||||
const themeCache = getThemeConfigCache();
|
||||
const themeColor = themeCache[EThemeSetting.ThemeColor];
|
||||
setThemeColor(EThemeColor.ThemeColor, themeColor);
|
||||
updateTheme(themeColor);
|
||||
});
|
||||
watch(
|
||||
() => [route.path, route.query, route.fullPath],
|
||||
([path, query, fullPath]) => {
|
||||
store.updateState({ activeTabName: fullPath });
|
||||
state.layout = app.fullscreenPages.includes(path as string) || (query as IObject)["pop"] ? EPageLayoutEnum.fullscreen : EPageLayoutEnum.page;
|
||||
}
|
||||
);
|
||||
return {
|
||||
store,
|
||||
state,
|
||||
pageTag: EPageLayoutEnum.page
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<el-config-provider>
|
||||
<div v-if="!store.state.appIsRender" v-loading="true" :element-loading-fullscreen="true" :element-loading-lock="true" style="width: 100vw; height: 100vh; position: absolute; top: 0; left: 0; z-index: 99999; background: #fff"></div>
|
||||
<template v-if="store.state.appIsReady">
|
||||
<layout v-if="state.layout === pageTag"> </layout>
|
||||
<fullscreen-layout v-else></fullscreen-layout>
|
||||
</template>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
@@ -0,0 +1,567 @@
|
||||
@import "../theme/base.less";
|
||||
|
||||
*,
|
||||
:after,
|
||||
:before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #595959;
|
||||
font-size: 14px;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"微软雅黑", Arial, sans-serif;
|
||||
color: #595959;
|
||||
background: #f0f2f5;
|
||||
|
||||
//字体
|
||||
.text {
|
||||
&-2 {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @--color-primary;
|
||||
text-decoration: none;
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: @--color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
cursor: pointer;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
vertical-align: text-bottom;
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
.icon-svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: currentColor;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.el-badge__content {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
padding: 0 5px;
|
||||
border: none;
|
||||
background: #ff4d4f !important;
|
||||
}
|
||||
|
||||
.ele-badge-static {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.ele-badge-static .el-badge__content {
|
||||
position: static;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
//alert
|
||||
.ele-alert-border.is-light.el-alert--warning {
|
||||
border: 1px solid #faad144d !important;
|
||||
}
|
||||
|
||||
.el-alert--warning.is-light {
|
||||
background-color: #fff7e8 !important;
|
||||
color: #faad14 !important;
|
||||
}
|
||||
.ele-alert-border.is-light .el-alert__title {
|
||||
color: #262626 !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.el-alert__content {
|
||||
padding: 0;
|
||||
}
|
||||
//menu
|
||||
.el-menu-item a,
|
||||
.el-menu-item span,
|
||||
.el-sub-menu > .el-sub-menu__title a,
|
||||
.el-sub-menu > .el-sub-menu__title span {
|
||||
color: @dark-text;
|
||||
text-decoration: none;
|
||||
margin-left: 5px;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
}
|
||||
.rr-sidebar-menu.el-menu--horizontal > .el-menu-item {
|
||||
padding: 0 12px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
.rr-sidebar-menu-pop-dark,
|
||||
.rr-sidebar-menu-pop-light {
|
||||
box-shadow: none !important;
|
||||
border-width: 0 !important;
|
||||
}
|
||||
.el-sub-menu__icon-arrow {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
//pop
|
||||
.el-popper.is-dark a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.el-popover.el-popper {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
//表格
|
||||
.el-table thead {
|
||||
color: #303133 !important;
|
||||
th {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
}
|
||||
.el-table__fixed-right::before {
|
||||
background: transparent !important; //element-plus表格高度动态计算bug,强制下划线不显示颜色
|
||||
}
|
||||
|
||||
.el-form--inline .el-form-item{
|
||||
margin-right: 16px !important;
|
||||
}
|
||||
|
||||
//分页
|
||||
.el-pagination {
|
||||
margin-top: 15px !important;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
//tinymce
|
||||
.tox-tinymce-aux {
|
||||
z-index: 3000 !important;
|
||||
}
|
||||
|
||||
//弹窗popover
|
||||
.popover-pop {
|
||||
padding: 10px 0 5px 5px !important;
|
||||
&-body {
|
||||
max-height: 255px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
//弹窗
|
||||
.rr-dialog {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.rr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
&-loading {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
//全屏页面渲染
|
||||
&-fullscreen {
|
||||
width: 100vw;
|
||||
|
||||
&.new-pop-window > div {
|
||||
padding: 15px;
|
||||
margin: 15px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
z-index: 1200;
|
||||
}
|
||||
|
||||
&-drawer {
|
||||
.el-drawer__header {
|
||||
color: #595959;
|
||||
font-size: 15px;
|
||||
margin-bottom: 0;
|
||||
padding: 13px 16px;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
}
|
||||
.el-drawer__body {
|
||||
padding: 15px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
//顶部
|
||||
&-header {
|
||||
background: #fff;
|
||||
padding: 0 !important;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 200;
|
||||
&-ctx {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
&-logo {
|
||||
display: flex;
|
||||
color: #ffffffe6;
|
||||
background-color: #191a23;
|
||||
font-size: 19px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1.5px;
|
||||
width: 230px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
justify-content: center;
|
||||
font-family: Avenir, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue,
|
||||
Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
|
||||
Noto Color Emoji;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
transition: width 0.3s;
|
||||
padding: 0 15px;
|
||||
|
||||
&-img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
&-wrap {
|
||||
display: flex;
|
||||
&.enabled-logo {
|
||||
&-false {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-line {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
}
|
||||
&-text {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei,
|
||||
"微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
//左侧侧边栏
|
||||
&-sidebar {
|
||||
margin-top: 50px;
|
||||
width: 230px !important;
|
||||
min-height: calc(100vh - 50px);
|
||||
overflow-x: hidden !important;
|
||||
transition: width 0.3s;
|
||||
z-index: 120;
|
||||
scrollbar-width: none;
|
||||
|
||||
&-menu {
|
||||
transition: width 0.3s;
|
||||
overflow: hidden;
|
||||
&.el-menu--horizontal {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.el-menu-item {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
width: 230px !important;
|
||||
border-right: 0 !important;
|
||||
&-item {
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
&-item,
|
||||
.el-sub-menu__title {
|
||||
background: transparent !important;
|
||||
&:focus {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
&-item,
|
||||
.el-sub-menu__title,
|
||||
&-item-group__title {
|
||||
font-size: 14px;
|
||||
}
|
||||
.el-sub-menu {
|
||||
.el-sub-menu__title {
|
||||
i {
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
margin: 0;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
}
|
||||
.el-sub-menu {
|
||||
.el-menu-item {
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item [class^="el-icon"],
|
||||
.el-sub-menu > .el-sub-menu__title [class^="el-icon"] {
|
||||
font-size: 17px;
|
||||
margin-right: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.el-menu-item a,
|
||||
.el-menu-item span,
|
||||
.el-sub-menu > .el-sub-menu__title a,
|
||||
.el-sub-menu > .el-sub-menu__title span {
|
||||
margin-left: 10px;
|
||||
> a {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//页面内容区域外层
|
||||
&-view {
|
||||
flex: 1;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
padding: 0 !important;
|
||||
border-top: 1px solid #f4f4f4 !important;
|
||||
&-container {
|
||||
margin-top: 50px;
|
||||
}
|
||||
&-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
&-ctx {
|
||||
margin-top: 39px;
|
||||
padding: 15px !important;
|
||||
flex: 1;
|
||||
//页面内容区域
|
||||
&-card {
|
||||
min-height: calc(100% - 5px);
|
||||
border-width: 0 !important;
|
||||
}
|
||||
}
|
||||
//页面内容顶部tab标签栏
|
||||
&-tab {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 39px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
&:hover {
|
||||
background: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-wrap {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 230px;
|
||||
right: 0;
|
||||
display: flex;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
z-index: 100;
|
||||
transition: left 0.3s;
|
||||
}
|
||||
|
||||
&-ops {
|
||||
width: 40px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-left: 1px solid #f4f4f4;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
color: #8c8c8c !important;
|
||||
font-weight: 400 !important;
|
||||
font-size: 16px !important;
|
||||
margin-right: 5px; //element-plus el-dropdown自动定位bug bottom-end指令不生效,临时采用偏移5px
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
&-prev,
|
||||
&-next {
|
||||
.el-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.el-tabs__item {
|
||||
padding: 0 15px !important;
|
||||
border-right: 1px solid #f4f4f4;
|
||||
user-select: none;
|
||||
color: #8c8c8c;
|
||||
&:hover {
|
||||
color: #262626;
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
.is-icon-close {
|
||||
transition: none !important;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
&::before {
|
||||
content: "";
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
background-color: #ddd;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: @primary-bg-light;
|
||||
background-color: @primary-bg-light !important;
|
||||
&:before {
|
||||
background-color: @primary-bg-light;
|
||||
}
|
||||
}
|
||||
&:nth-child(2) {
|
||||
&::before {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap {
|
||||
padding: 0px 39px 0 40px !important;
|
||||
&::before,
|
||||
&::after {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
color: #8c8c8c;
|
||||
transition: background-color 0.2s;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-family: element-icons !important;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&::before {
|
||||
content: url('data:image/svg+xml;charset=utf-8,<svg width="16" height="16" color="rgb(140 140 140)" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-042ca774=""><path fill="currentColor" d="M609.408 149.376L277.76 489.6a32 32 0 000 44.672l331.648 340.352a29.12 29.12 0 0041.728 0 30.592 30.592 0 000-42.752L339.264 511.936l311.872-319.872a30.592 30.592 0 000-42.688 29.12 29.12 0 00-41.728 0z"></path></svg>');
|
||||
border-right: 1px solid #f4f4f4;
|
||||
}
|
||||
&::after {
|
||||
content: url('data:image/svg+xml;charset=utf-8,<svg width="16" height="16" color="rgb(140 140 140)" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-042ca774=""><path fill="currentColor" d="M340.864 149.312a30.592 30.592 0 000 42.752L652.736 512 340.864 831.872a30.592 30.592 0 000 42.752 29.12 29.12 0 0041.728 0L714.24 534.336a32 32 0 000-44.672L382.592 149.376a29.12 29.12 0 00-41.728 0z"></path></svg>');
|
||||
right: 0;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
height: auto;
|
||||
background-color: transparent;
|
||||
border-left: 1px solid #f4f4f4;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
color: #8c8c8c;
|
||||
transition: background-color 0.2s;
|
||||
z-index: 10;
|
||||
|
||||
i {
|
||||
vertical-align: middle;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-prev {
|
||||
border-right: 1px solid #f4f4f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ql-toolbar.ql-snow{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.el-form--inline {
|
||||
.el-form-item {
|
||||
& > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
.rr-header-ctx {
|
||||
[class^="el-icon"] {
|
||||
font-size: 18px;
|
||||
width: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
.rr-header-right {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
> div {
|
||||
height: 100%;
|
||||
}
|
||||
&-items {
|
||||
display: flex;
|
||||
padding: 0 8px 0 0;
|
||||
|
||||
> div {
|
||||
padding: 0 12px;
|
||||
height: 50px;
|
||||
line-height: 56px;
|
||||
cursor: pointer;
|
||||
}
|
||||
&-icon {
|
||||
height: 50px;
|
||||
line-height: 56px;
|
||||
display: inline-block;
|
||||
}
|
||||
.el-badge {
|
||||
line-height: normal;
|
||||
}
|
||||
.el-dropdown {
|
||||
vertical-align: inherit;
|
||||
.el-icon {
|
||||
.icon {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-left {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-br {
|
||||
padding: 0 10px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
flex: 1;
|
||||
.el-breadcrumb {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.el-breadcrumb__inner,
|
||||
.el-breadcrumb__inner a,
|
||||
.el-breadcrumb__item:last-child .el-breadcrumb__inner,
|
||||
.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover,
|
||||
.el-breadcrumb__item:last-child .el-breadcrumb__inner a,
|
||||
.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
.el-breadcrumb__item {
|
||||
float: none !important;
|
||||
display: inline-block;
|
||||
}
|
||||
.el-breadcrumb__inner.is-link {
|
||||
color: #595959;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-space__item {
|
||||
&:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rr-sidebar-menu.el-menu--horizontal {
|
||||
display: flex;
|
||||
span {
|
||||
width: inherit;
|
||||
}
|
||||
.el-sub-menu {
|
||||
.el-sub-menu__icon-arrow {
|
||||
margin-left: 3px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.el-sub-menu__title {
|
||||
padding: 0 10px 0 12px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
.rr-setting {
|
||||
padding: 20px;
|
||||
.el-divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
&-wrap {
|
||||
.el-drawer__header {
|
||||
color: #595959;
|
||||
font-size: 15px;
|
||||
margin-bottom: 0;
|
||||
padding: 13px 16px;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
}
|
||||
.el-drawer__body {
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
&-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
// 主题
|
||||
.rr-theme {
|
||||
.card {
|
||||
width: 50px;
|
||||
height: 35px;
|
||||
border-radius: 3px;
|
||||
margin: 0 20px 20px 0;
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
//侧边栏
|
||||
&.side {
|
||||
&::before {
|
||||
content: "";
|
||||
width: 15px;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
&.dark {
|
||||
&::before {
|
||||
background-color: #2e3549;
|
||||
}
|
||||
}
|
||||
}
|
||||
//顶栏
|
||||
&.header {
|
||||
&::before {
|
||||
content: "";
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: #fff;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
&.light {
|
||||
&::before {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: #fff;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
&.dark {
|
||||
&::before {
|
||||
background-color: #2e3549;
|
||||
}
|
||||
}
|
||||
&.primary {
|
||||
&::before {
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mix {
|
||||
background-color: #2e3549;
|
||||
&.dark {
|
||||
&::before {
|
||||
background-color: #f0f2f5;
|
||||
width: 35px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.side,
|
||||
&.header,
|
||||
&.mix {
|
||||
&.active {
|
||||
&::after {
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: #1ea4ff;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -15px;
|
||||
margin-left: -3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//主色调
|
||||
.color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 8px 8px 0 0;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
box-shadow: 0 1px 3px rgba(0 0 0, 0.1);
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
&::after {
|
||||
content: url('data:image/svg+xml;charset=utf-8,<svg width="14" height="14" color="rgb(255 255 255)" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-042ca774=""><path fill="currentColor" d="M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z"></path></svg>');
|
||||
font-family: element-icons !important;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -7px 0 0 -7px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.rr-theme,
|
||||
.rr-other {
|
||||
width: 100%;
|
||||
> .el-space__item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.rr-switch {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641477417057" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6229" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 887.466667C303.786667 887.466667 136.533333 720.213333 136.533333 512S303.786667 136.533333 512 136.533333s375.466667 167.253333 375.466667 375.466667-167.253333 375.466667-375.466667 375.466667z m0-716.8C324.266667 170.666667 170.666667 324.266667 170.666667 512s153.6 341.333333 341.333333 341.333333 341.333333-153.6 341.333333-341.333333S699.733333 170.666667 512 170.666667z" fill="" p-id="6230"></path><path d="M512 1024C228.693333 1024 0 795.306667 0 512S228.693333 0 512 0s512 228.693333 512 512-228.693333 512-512 512z m0-989.866667C249.173333 34.133333 34.133333 249.173333 34.133333 512s215.04 477.866667 477.866667 477.866667 477.866667-215.04 477.866667-477.866667S774.826667 34.133333 512 34.133333z" fill="" p-id="6231"></path><path d="M375.466667 409.6m-34.133334 0a34.133333 34.133333 0 1 0 68.266667 0 34.133333 34.133333 0 1 0-68.266667 0Z" fill="" p-id="6232"></path><path d="M648.533333 409.6m-34.133333 0a34.133333 34.133333 0 1 0 68.266667 0 34.133333 34.133333 0 1 0-68.266667 0Z" fill="" p-id="6233"></path><path d="M375.466667 648.533333m-34.133334 0a34.133333 34.133333 0 1 0 68.266667 0 34.133333 34.133333 0 1 0-68.266667 0Z" fill="" p-id="6234"></path><path d="M648.533333 648.533333m-34.133333 0a34.133333 34.133333 0 1 0 68.266667 0 34.133333 34.133333 0 1 0-68.266667 0Z" fill="" p-id="6235"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575982282951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="902" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M828.40625 90.125H195.59375C137.375 90.125 90.125 137.375 90.125 195.59375v632.8125c0 58.21875 47.25 105.46875 105.46875 105.46875h632.8125c58.21875 0 105.46875-47.25 105.46875-105.46875V195.59375c0-58.21875-47.25-105.46875-105.46875-105.46875z m52.734375 738.28125c0 29.16-23.57015625 52.734375-52.734375 52.734375H195.59375c-29.109375 0-52.734375-23.574375-52.734375-52.734375V195.59375c0-29.109375 23.625-52.734375 52.734375-52.734375h632.8125c29.16 0 52.734375 23.625 52.734375 52.734375v632.8125z" p-id="903"></path><path d="M421.52890625 709.55984375a36.28125 36.28125 0 0 1-27.55265625-12.66890625L205.17453125 476.613125a36.28546875 36.28546875 0 0 1 55.10109375-47.22890625l164.986875 192.4846875 342.16171875-298.48078125a36.2896875 36.2896875 0 0 1 47.70984375 54.68765625L445.3859375 700.6203125a36.3234375 36.3234375 0 0 1-23.85703125 8.93953125z" p-id="904"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577252187056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M747.59340925 691.12859384c11.51396329 0.25305413 22.43746719-0.21087818 40.74171707-1.51832482 29.35428085-2.10878421 35.84933734-2.36183835 46.47761114-0.8856895 24.71495444 3.37405491 41.12129828 21.76265671 32.47528161 47.95376084-85.57447632 258.19957947-442.00123984 249.76444099-628.67084683 50.73735554-153.47733892-159.33976008-153.09775772-414.41833795 0.92786545-573.42069196 159.71934128-162.67163983 424.03439521-166.59397897 565.78689185 0.63263534 80.38686649 94.81095318 108.34934958 169.16669549 89.11723508 230.57450162-15.01454608 47.99593598-50.61082928 77.68762207-119.77896259 114.63352789-4.89237973 2.65706845-29.35428085 15.52065436-35.84933652 19.02123633-46.94154346 25.30541465-63.51659033 41.20565021-62.20914449 58.45550757 2.95229856 39.13904114 24.16667102 52.7196135 70.98168823 53.81618115z m44.41100207 50.10472101c-19.82257471 1.43397372-32.05352527 1.940082-45.63409763 1.6448519-70.34905207-1.60267593-115.98314969-30.91478165-121.38163769-101.64341492-3.45840683-46.05585397 24.7571304-73.13264758 89.24376132-107.96976837 6.7902866-3.66928501 31.37871396-16.57504688 36.06021551-19.06341229 57.69634516-30.83042972 85.15271997-53.73183005 94.76877722-84.47790866 12.77923398-40.78389304-9.10994898-98.94417051-79.24812286-181.6507002-121.17075953-142.97559219-350.14258521-139.60153647-489.2380134 2.06660824-134.49827774 138.84237405-134.79350784 362.12048163-0.42175717 501.637667 158.53842169 168.99799328 451.9968783 181.18676788 534.57688175-11.80919339-4.68150156 0.2952301-10.71262573 0.67481131-18.72600705 1.26527069z" p-id="2509"></path><path d="M346.03865637 637.18588562a78.82636652 78.82636652 0 0 0 78.32025825-79.29029883c0-43.69401562-35.005823-79.29029883-78.32025825-79.29029882a78.82636652 78.82636652 0 0 0-78.36243338 79.29029882c0 43.69401562 35.005823 79.29029883 78.36243338 79.29029883z m0-51.7495729a27.07679361 27.07679361 0 0 1-26.5706845-27.54072593c0-15.30977536 11.97789643-27.54072593 26.5706845-27.54072592 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072592a27.07679361 27.07679361 0 0 1-26.57068533 27.54072593zM475.7289063 807.11174353a78.82636652 78.82636652 0 0 0 78.3624334-79.29029882c0-43.69401562-34.96364785-79.29029883-78.32025825-79.29029883a78.82636652 78.82636652 0 0 0-78.32025742 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029882z m0-51.74957208a27.07679361 27.07679361 0 0 1-26.57068532-27.54072674c0-15.30977536 12.06224753-27.54072593 26.57068532-27.54072593 14.59278892 0 26.57068533 12.23095057 26.57068453 27.54072593a27.07679361 27.07679361 0 0 1-26.57068453 27.54072674zM601.24376214 377.21492718a78.82636652 78.82636652 0 0 0 78.32025742-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025742-79.29029882a78.82636652 78.82636652 0 0 0-78.32025823 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025824 79.29029883z m1e-8-51.74957208a27.07679361 27.07679361 0 0 1-26.57068534-27.54072675c0-15.30977536 11.97789643-27.54072593 26.57068534-27.54072591 14.55061295 0 26.57068533 12.23095057 26.57068451 27.54072592a27.07679361 27.07679361 0 0 1-26.57068451 27.54072674zM378.80916809 433.85687983a78.82636652 78.82636652 0 0 0 78.32025824-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025824-79.29029802a78.82636652 78.82636652 0 0 0-78.32025742 79.29029802c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029883z m0-51.74957209a27.07679361 27.07679361 0 0 1-26.57068451-27.54072674c0-15.30977536 11.97789643-27.54072593 26.57068451-27.54072593 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072593a27.07679361 27.07679361 0 0 1-26.57068533 27.54072674z" p-id="2510"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575804206892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3145" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M826.56 470.016c-32.896 0-64.384 12.288-89.984 35.52l0-104.96c0-62.208-50.496-112.832-112.64-113.088L623.936 287.04 519.552 287.104C541.824 262.72 554.56 230.72 554.56 197.12c0-73.536-59.904-133.44-133.504-133.44-73.472 0-133.376 59.904-133.376 133.44 0 32.896 12.224 64.256 35.52 89.984L175.232 287.104l0 0.576C113.728 288.704 64 338.88 64 400.576l0.32 0 0.32 116.48C60.864 544.896 70.592 577.728 100.8 588.48c12.736 4.608 37.632 7.488 60.864-25.28 12.992-18.368 34.24-29.248 56.64-29.248 38.336 0 69.504 31.104 69.504 69.312 0 38.4-31.168 69.504-69.504 69.504-22.656 0-44.032-11.264-57.344-30.4C138.688 610.112 112.576 615.36 102.464 619.136c-29.824 10.752-39.104 43.776-38.144 67.392l0 160.384L64 846.912C64 909.248 114.752 960 177.216 960l446.272 0c62.4 0 113.152-50.752 113.152-113.152l0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.536 0 133.44-59.904 133.44-133.504C960 529.92 900.096 470.016 826.56 470.016zM826.56 672.896c-22.72 0-44.032-11.264-57.344-30.4-22.272-32.384-48.448-27.136-58.56-23.36-29.824 10.752-39.04 43.776-38.08 67.392l0 160.384c0 27.136-22.016 49.152-49.152 49.152L177.216 896.064C150.08 896 128 873.984 128 846.848l0.32 0 0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.6 0 133.504-59.904 133.504-133.504 0-73.472-59.904-133.376-133.504-133.376-32.896 0-64.32 12.288-89.984 35.52l0-104.96L128 400.512c0-27.072 22.08-49.152 49.216-49.152L177.216 351.04 334.656 350.72c3.776 0.512 7.616 0.832 11.52 0.832 24.896 0 50.752-10.816 60.032-37.056 4.544-12.736 7.424-37.568-25.344-60.736C362.624 240.768 351.68 219.52 351.68 197.12c0-38.272 31.104-69.44 69.376-69.44 38.336 0 69.504 31.168 69.504 69.44 0 22.72-11.264 44.032-30.528 57.472C427.968 276.736 433.088 302.784 436.8 313.024c10.752 29.888 43.072 39.232 67.392 38.08l119.232 0 0 0.384c27.136 0 49.152 22.08 49.152 49.152l0.256 116.48c-3.776 27.84 6.016 60.736 36.224 71.488 12.736 4.608 37.632 7.488 60.8-25.28 13.056-18.368 34.24-29.248 56.704-29.248C864.832 534.016 896 565.12 896 603.392 896 641.728 864.832 672.896 826.56 672.896z" p-id="3146"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774833889" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.466667 192.853333h-100.693334V119.466667c0-10.24-6.826667-17.066667-17.066666-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H303.786667V119.466667c0-10.24-6.826667-17.066667-17.066667-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H168.96c-46.08 0-85.333333 37.546667-85.333333 85.333334V836.266667c0 46.08 37.546667 85.333333 85.333333 85.333333H887.466667c46.08 0 85.333333-37.546667 85.333333-85.333333V278.186667c0-47.786667-37.546667-85.333333-85.333333-85.333334z m-718.506667 34.133334h100.693333v66.56c0 10.24 6.826667 17.066667 17.066667 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56h450.56v66.56c0 10.24 6.826667 17.066667 17.066666 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56H887.466667c27.306667 0 51.2 22.186667 51.2 51.2v88.746666H117.76v-88.746666c0-29.013333 22.186667-51.2 51.2-51.2zM887.466667 887.466667H168.96c-27.306667 0-51.2-22.186667-51.2-51.2V401.066667H938.666667V836.266667c0 27.306667-22.186667 51.2-51.2 51.2z" p-id="1377"></path><path d="M858.453333 493.226667H327.68c-10.24 0-17.066667 6.826667-17.066667 17.066666v114.346667h-116.053333c-10.24 0-17.066667 6.826667-17.066667 17.066667v133.12c0 10.24 6.826667 17.066667 17.066667 17.066666H460.8c10.24 0 17.066667-6.826667 17.066667-17.066666v-114.346667h380.586666c10.24 0 17.066667-6.826667 17.066667-17.066667v-133.12c0-10.24-6.826667-17.066667-17.066667-17.066666z m-413.013333 34.133333v97.28h-98.986667v-97.28h98.986667z m-230.4 131.413333h98.986667v98.986667h-98.986667v-98.986667z m131.413333 97.28v-97.28h98.986667v97.28h-98.986667z m133.12-228.693333h97.28v98.986667h-97.28v-98.986667z m131.413334 0h98.986666v98.986667h-98.986666v-98.986667z m230.4 97.28h-98.986667v-98.986667h98.986667v98.986667z" p-id="1378"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577186573535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1068" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M479.85714249 608.42857168h64.28571502c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285751s-12.85714249-32.14285751-32.14285664-32.14285664h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285662s12.85714249 32.14285751 32.14285664 32.14285753z m-2e-8 122.14285665h64.28571504c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285665s-12.85714249-32.14285751-32.14285664-32.14285751h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285751s12.85714249 32.14285751 32.14285664 32.14285664z m353.57142921-559.28571416h-128.57142921v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285664-32.14285753s-32.14285751 12.85714249-32.14285751 32.14285753v32.14285664h-257.14285665v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285752-32.14285753s-32.14285751 12.85714249-32.14285664 32.14285753v32.14285664h-128.57142919c-70.71428585 0-128.57142832 57.85714249-128.57142832 122.14285751v501.42857081c0 70.71428585 57.85714249 128.57142832 128.57142832 122.14285751h642.85714335c70.71428585 0 128.57142832-57.85714249 128.57142833-122.14285751v-501.42857081c0-70.71428585-57.85714249-122.14285753-128.57142833-122.14285751z m64.28571415 623.57142832c0 32.14285751-32.14285751 64.28571415-64.28571416 64.28571504h-642.85714335c-32.14285751 0-64.28571415-25.71428583-64.28571417-64.28571504v-372.85714249h771.42857168v372.85714249z m0-437.14285664h-771.42857168v-64.28571417c0-32.14285751 32.14285751-64.28571415 64.28571417-64.28571415h128.57142919v32.14285664c0 19.28571417 12.85714249 32.14285751 32.14285664 32.14285751s32.14285751-12.85714249 32.14285753-32.14285751v-32.14285664h257.14285665v32.14285664c0 19.28571417 12.85714249 32.14285751 32.1428575 32.14285751s32.14285751-12.85714249 32.14285664-32.14285751v-32.14285664h128.57142921c32.14285751 0 64.28571415 25.71428583 64.28571415 64.28571415v64.28571417z m-610.71428583 372.85714247h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285664s-12.85714249-32.14285751-32.14285753-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285751s12.85714249 32.14285751 32.14285751 32.14285665z m385.71428583-122.14285664h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285751s-12.85714249-32.14285751-32.14285751-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285664s12.85714249 32.14285751 32.14285753 32.14285751z m-385.71428583 0h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285751s-12.85714249-32.14285751-32.14285753-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285664s12.85714249 32.14285751 32.14285751 32.14285751z m385.71428583 122.14285665h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285665s-12.85714249-32.14285751-32.14285751-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285751s12.85714249 32.14285751 32.14285753 32.14285665z" p-id="1069"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1622124729495" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="653" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M854.4 800.9c0.2-0.3 0.5-0.6 0.7-0.9C920.6 722.1 960 621.7 960 512s-39.4-210.1-104.8-288c-0.2-0.3-0.5-0.5-0.7-0.8-1.1-1.3-2.1-2.5-3.2-3.7-0.4-0.5-0.8-0.9-1.2-1.4-1.4-1.6-2.7-3.1-4.1-4.7l-0.1-0.1c-1.5-1.7-3.1-3.4-4.6-5.1l-0.1-0.1c-3.2-3.4-6.4-6.8-9.7-10.1l-0.1-0.1-4.8-4.8-0.3-0.3c-1.5-1.5-3-2.9-4.5-4.3-0.5-0.5-1-1-1.6-1.5-1-1-2-1.9-3-2.8-0.3-0.3-0.7-0.6-1-1C736.4 109.2 629.5 64 512 64s-224.4 45.2-304.3 119.2c-0.3 0.3-0.7 0.6-1 1-1 0.9-2 1.9-3 2.9-0.5 0.5-1 1-1.6 1.5-1.5 1.4-3 2.9-4.5 4.3l-0.3 0.3-4.8 4.8-0.1 0.1c-3.3 3.3-6.5 6.7-9.7 10.1l-0.1 0.1c-1.6 1.7-3.1 3.4-4.6 5.1l-0.1 0.1c-1.4 1.5-2.8 3.1-4.1 4.7-0.4 0.5-0.8 0.9-1.2 1.4-1.1 1.2-2.1 2.5-3.2 3.7-0.2 0.3-0.5 0.5-0.7 0.8C103.4 301.9 64 402.3 64 512s39.4 210.1 104.8 288c0.2 0.3 0.5 0.6 0.7 0.9 1 1.2 2.1 2.5 3.1 3.7 0.4 0.5 0.8 0.9 1.2 1.4 1.4 1.6 2.7 3.1 4.1 4.7 0 0.1 0.1 0.1 0.1 0.2 1.5 1.7 3 3.4 4.6 5l0.1 0.1c3.2 3.4 6.4 6.8 9.6 10.1l0.1 0.1c1.6 1.6 3.1 3.2 4.7 4.7l0.3 0.3c3.3 3.3 6.7 6.5 10.1 9.6 80.1 74 187 119.2 304.5 119.2s224.4-45.2 304.3-119.2c3.4-3.1 6.7-6.3 10-9.6l0.3-0.3c1.6-1.6 3.2-3.1 4.7-4.7l0.1-0.1c3.3-3.3 6.5-6.7 9.6-10.1l0.1-0.1c1.5-1.7 3.1-3.3 4.6-5 0-0.1 0.1-0.1 0.1-0.2 1.4-1.5 2.8-3.1 4.1-4.7 0.4-0.5 0.8-0.9 1.2-1.4 1.2-1.3 2.3-2.5 3.3-3.7z m4.1-142.6c-13.8 32.6-32 62.8-54.2 90.2-24.9-21.5-52.2-40.3-81.5-55.9 11.6-46.9 18.8-98.4 20.7-152.6H887c-3 40.9-12.6 80.6-28.5 118.3zM887 484H743.5c-1.9-54.2-9.1-105.7-20.7-152.6 29.3-15.6 56.6-34.4 81.5-55.9 22.2 27.4 40.4 57.6 54.2 90.2C874.4 403.4 884 443.1 887 484zM658.3 165.5c39.7 16.8 75.8 40 107.6 69.2-18.5 15.8-38.4 29.7-59.4 41.8-15.7-45-35.8-84.1-59.2-115.4 3.7 1.4 7.4 2.9 11 4.4z m-90.6 700.6c-9.2 7.2-18.4 12.7-27.7 16.4V697c39.9 2.8 78.6 11.6 115.7 26.2-8.3 24.6-17.9 47.3-29 67.8-17.4 32.4-37.8 58.3-59 75.1z m59-633.1c11 20.6 20.7 43.3 29 67.8-37.1 14.6-75.8 23.4-115.7 26.2V141.6c9.2 3.7 18.5 9.1 27.7 16.4 21.2 16.7 41.6 42.6 59 75zM540 640.9V540h147.5c-1.6 44.2-7.1 87.1-16.3 127.8l-0.3 1.2c-41.1-15.6-85.1-25.3-130.9-28.1z m0-156.9V383.1c45.8-2.8 89.8-12.5 130.9-28.1l0.3 1.2c9.2 40.7 14.7 83.5 16.3 127.8H540z m-56 56v100.9c-45.8 2.8-89.8 12.5-130.9 28.1l-0.3-1.2c-9.2-40.7-14.7-83.5-16.3-127.8H484z m-147.5-56c1.6-44.2 7.1-87.1 16.3-127.8l0.3-1.2c41.1 15.6 85 25.3 130.9 28.1V484H336.5zM484 697v185.4c-9.2-3.7-18.5-9.1-27.7-16.4-21.2-16.7-41.7-42.7-59.1-75.1-11-20.6-20.7-43.3-29-67.8 37.2-14.6 75.9-23.3 115.8-26.1z m0-370c-39.9-2.8-78.6-11.6-115.7-26.2 8.3-24.6 17.9-47.3 29-67.8 17.4-32.4 37.8-58.4 59.1-75.1 9.2-7.2 18.4-12.7 27.7-16.4V327zM365.7 165.5c3.7-1.5 7.3-3 11-4.4-23.4 31.3-43.5 70.4-59.2 115.4-21-12-40.9-26-59.4-41.8 31.8-29.2 67.9-52.4 107.6-69.2zM165.5 365.7c13.8-32.6 32-62.8 54.2-90.2 24.9 21.5 52.2 40.3 81.5 55.9-11.6 46.9-18.8 98.4-20.7 152.6H137c3-40.9 12.6-80.6 28.5-118.3zM137 540h143.5c1.9 54.2 9.1 105.7 20.7 152.6-29.3 15.6-56.6 34.4-81.5 55.9-22.2-27.4-40.4-57.6-54.2-90.2C149.6 620.6 140 580.9 137 540z m228.7 318.5c-39.7-16.8-75.8-40-107.6-69.2 18.5-15.8 38.4-29.7 59.4-41.8 15.7 45 35.8 84.1 59.2 115.4-3.7-1.4-7.4-2.9-11-4.4z m292.6 0c-3.7 1.5-7.3 3-11 4.4 23.4-31.3 43.5-70.4 59.2-115.4 21 12 40.9 26 59.4 41.8-31.8 29.2-67.9 52.4-107.6 69.2z" p-id="654"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1622128472748" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1971" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M128 128h256v64H192v192H128V128zM896 640v256h-256v-64h192v-192h64zM384 686.496L174.496 896 128 849.504 337.504 640H192v-64h256v256h-64v-145.504zM849.504 128L640 337.504V192h-64v256h256v-64h-145.504L896 174.496 849.504 128z" p-id="1972"></path></svg>
|
||||
|
After Width: | Height: | Size: 626 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1622128325471" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1563" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M608 416h288c35.36 0 64 28.48 64 64v416c0 35.36-28.48 64-64 64H480c-35.36 0-64-28.48-64-64v-288H128c-35.36 0-64-28.48-64-64V128c0-35.36 28.48-64 64-64h416c35.36 0 64 28.48 64 64v288z m0 64v64c0 35.36-28.48 64-64 64h-64v256.032c0 17.664 14.304 31.968 31.968 31.968H864a31.968 31.968 0 0 0 31.968-31.968V512a31.968 31.968 0 0 0-31.968-31.968H608zM128 159.968V512c0 17.664 14.304 31.968 31.968 31.968H512a31.968 31.968 0 0 0 31.968-31.968V160A31.968 31.968 0 0 0 512.032 128H160A31.968 31.968 0 0 0 128 159.968z m64 244.288V243.36h112.736V176h46.752c6.4 0.928 9.632 1.824 9.632 2.752a10.56 10.56 0 0 1-1.376 4.128c-2.752 7.328-4.128 16.032-4.128 26.112v34.368h119.648v156.768h-50.88v-20.64h-68.768v118.272H306.112v-118.272H238.752v24.768H192z m46.72-122.368v60.48h67.392V281.92H238.752z m185.664 60.48V281.92h-68.768v60.48h68.768z m203.84 488H576L668.128 576h64.64l89.344 254.4h-54.976l-19.264-53.664h-100.384l-19.232 53.632z m33.024-96.256h72.864l-34.368-108.608h-1.376l-37.12 108.608zM896 320h-64a128 128 0 0 0-128-128V128a192 192 0 0 1 192 192zM128 704h64a128 128 0 0 0 128 128v64a192 192 0 0 1-192-192z" p-id="1564"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695003505593" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4091" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" p-id="4092"></path></svg>
|
||||
|
After Width: | Height: | Size: 995 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1622128506901" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2107" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zM400 646c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zM904 160H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM904 792H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519c4.5-3.5 4.5-10.3 0-13.9L142.4 381.9c-5.8-4.6-14.4-0.5-14.4 6.9v246.3c0 7.4 8.5 11.6 14.4 7z" p-id="2108"></path></svg>
|
||||
|
After Width: | Height: | Size: 874 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802859706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="3103"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="3104"></path></svg>
|
||||
|
After Width: | Height: | Size: 744 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1622128442421" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1835" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M426.666667 512a85.333333 85.333333 0 1 1 170.709333 0.042667A85.333333 85.333333 0 0 1 426.666667 512z m0 298.666667a85.333333 85.333333 0 1 1 170.709333 0.042666A85.333333 85.333333 0 0 1 426.666667 810.666667z m0-597.333334a85.333333 85.333333 0 1 1 170.709333 0.042667A85.333333 85.333333 0 0 1 426.666667 213.333333z" p-id="1836"></path></svg>
|
||||
|
After Width: | Height: | Size: 725 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802851180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2867" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M279.272727 791.272727h512a46.545455 46.545455 0 0 1 0 93.090909H279.272727a46.545455 46.545455 0 0 1 0-93.090909z m33.838546-617.984V651.636364H193.722182V395.170909c0-37.003636-0.884364-59.298909-2.653091-66.746182a24.948364 24.948364 0 0 0-14.615273-16.989091c-8.005818-3.863273-25.786182-5.771636-53.341091-5.771636h-11.822545v-55.854545c57.716364-12.381091 101.562182-37.888 131.490909-76.520728h70.283636z m303.709091 396.8V651.636364H354.164364v-68.235637c77.777455-127.255273 124.043636-206.010182 138.705454-236.218182 14.661818-30.254545 22.016-53.853091 22.016-70.74909 0-13.032727-2.234182-22.714182-6.656-29.137455-4.421818-6.376727-11.170909-9.588364-20.247273-9.588364a22.248727 22.248727 0 0 0-20.200727 10.612364c-4.468364 7.121455-6.656 21.178182-6.656 42.263273v45.521454H354.164364v-17.454545c0-26.763636 1.396364-47.941818 4.142545-63.348364 2.746182-15.499636 9.541818-30.72 20.386909-45.661091 10.798545-14.987636 24.901818-26.298182 42.216727-33.978182 17.361455-7.68 38.167273-11.543273 62.37091-11.543272 47.476364 0 83.316364 11.776 107.706181 35.328 24.296727 23.552 36.445091 53.341091 36.445091 89.367272 0 27.368727-6.842182 56.32-20.48 86.853819-13.730909 30.533818-54.039273 95.325091-121.018182 194.420363h130.885819z m270.615272-189.393454c18.152727 6.097455 31.650909 16.104727 40.494546 29.975272 8.843636 13.917091 13.312 46.452364 13.312 97.652364 0 38.027636-4.328727 67.490909-13.032727 88.529455-8.657455 20.945455-23.598545 36.910545-44.869819 47.848727-21.271273 10.938182-48.593455 16.384-81.873454 16.384-37.794909 0-67.490909-6.330182-89.088-19.083636-21.550545-12.660364-35.746909-28.253091-42.542546-46.638546-6.795636-18.432-10.193455-50.362182-10.193454-95.883636v-37.841455h119.389091v77.730909c0 20.666182 1.210182 33.838545 3.723636 39.424 2.420364 5.585455 7.912727 8.424727 16.337455 8.424728 9.309091 0 15.36-3.537455 18.338909-10.612364 2.932364-7.121455 4.421818-25.6 4.421818-55.575273v-33.047273c0-18.338909-2.048-31.744-6.190546-40.215272a30.72 30.72 0 0 0-18.338909-16.709818c-8.052364-2.653091-23.738182-4.189091-46.964363-4.561455V357.050182c28.392727 0 45.893818-1.070545 52.596363-3.258182a22.946909 22.946909 0 0 0 14.475637-14.149818c2.932364-7.307636 4.421818-18.711273 4.421818-34.257455v-26.624c0-16.756364-1.722182-27.741091-5.12-33.047272-3.490909-5.352727-8.843636-8.005818-16.151273-8.005819-8.285091 0-13.963636 2.792727-16.989091 8.378182-3.025455 5.632-4.561455 17.640727-4.561454 35.933091v39.284364h-119.389091v-40.773818c0-45.661091 10.472727-76.567273 31.325091-92.625455 20.898909-16.058182 54.085818-24.064 99.607272-24.064 56.878545 0 95.511273 11.170909 115.805091 33.373091 20.293818 22.248727 30.394182 53.201455 30.394182 92.765091 0 26.810182-3.630545 46.173091-10.891636 58.088727-7.307636 11.915636-20.107636 22.807273-38.446546 32.628364z" p-id="2868"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1622128519822" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2243" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zM400 646c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zM904 160H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM904 792H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4 0.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1c-4.5 3.5-4.5 10.3 0 13.8z" p-id="2244"></path></svg>
|
||||
|
After Width: | Height: | Size: 876 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802846045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2750" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M868.593046 403.832442c-30.081109-28.844955-70.037123-44.753273-112.624057-44.753273L265.949606 359.079168c-42.554188 0-82.510202 15.908318-112.469538 44.690852-30.236652 28.782533-46.857191 67.222007-46.857191 108.198258l0 294.079782c0 40.977273 16.619516 79.414701 46.702672 108.136859 29.959336 28.844955 70.069869 44.814672 112.624057 44.814672l490.019383 0c42.585911 0 82.696444-15.969717 112.624057-44.814672 30.082132-28.844955 46.579875-67.222007 46.579875-108.136859L915.172921 511.968278C915.171897 471.053426 898.675178 432.677397 868.593046 403.832442zM841.821309 806.049083c0 22.098297-8.882298 42.772152-25.099654 58.306964-16.154935 15.661701-37.81935 24.203238-60.752666 24.203238L265.949606 888.559285c-22.934339 0-44.567032-8.54256-60.877509-24.264637-16.186657-15.474436-25.067932-36.148291-25.067932-58.246589L180.004165 511.968278c0-22.035876 8.881274-42.772152 25.192775-58.307987 16.186657-15.536858 37.81935-24.139793 60.753689-24.139793l490.019383 0c22.933315 0 44.597731 8.602935 60.752666 24.139793 16.21838 15.535835 25.099654 36.272112 25.099654 58.307987L841.822332 806.049083zM510.974136 135.440715c114.914216 0 208.318536 89.75214 208.318536 200.055338l73.350588 0c0-149.113109-126.366036-270.496667-281.669124-270.496667-155.333788 0-281.699824 121.383558-281.699824 270.496667l73.350588 0C302.623877 225.193879 396.059919 135.440715 510.974136 135.440715zM474.299865 747.244792l73.350588 0L547.650453 629.576859l-73.350588 0L474.299865 747.244792z" p-id="2751"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575966775973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="879" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M507.39346659 71.84873358c241.53533667 0 437.39770766 195.85422109 437.39770767 437.37442191 0 241.53766571-195.86237099 437.38955776-437.39770767 437.38955776-241.50040803 0-437.34997219-195.85189205-437.34997219-437.38955776C70.0434944 267.70295467 265.89189347 71.84873358 507.39346659 71.84873358L507.39346659 71.84873358zM507.39346659 282.81899805c-125.00686734 0-226.37039389 101.38914133-226.37039388 226.41813048 0 125.01268821 101.36352768 226.39717262 226.37039388 226.39717262 125.04295993 0 226.42395136-101.38448441 226.42395136-226.39717262C733.81625401 384.20813938 632.43642653 282.81899805 507.39346659 282.81899805L507.39346659 282.81899805zM507.39346659 120.78172615c-214.46664192 0-388.42047261 173.95150279-388.4204726 388.44026539 0 214.51204949 173.95499463 388.46122325 388.4204726 388.46122325 214.52369237 0 388.46005817-173.94800981 388.46005818-388.46122325C895.85236082 294.73322894 721.91715897 120.78172615 507.39346659 120.78172615z" p-id="880"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577246781606" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1098" xmlns:xlink="http://www.w3.org/1999/xlink" width="84.5595703125" height="81"><defs><style type="text/css"></style></defs><path d="M633.72929961 378.02038203l9.49872568 18.68789795 20.78025469 2.79745225 206.61592412 27.33248408a11.46496817 11.46496817 0 0 1 6.6095543 19.47324902l-147.2675168 147.35350284-14.89299345 14.89299345 3.8006376 20.68280244 37.84585956 204.89044571a11.46496817 11.46496817 0 0 1-16.4808914 12.2961788L554.68980898 751.84713388l-18.68789794-9.49299345-18.48726123 9.99171915-183.23885392 99.34968163a11.46496817 11.46496817 0 0 1-16.78471347-11.8662416l32.5433127-205.79617881 3.29617793-20.78598692-15.19108243-14.49172002-151.03375839-143.48407587a11.46496817 11.46496817 0 0 1 6.09936328-19.63949062l205.79617881-32.63503185 20.78598691-3.2961788L428.87898125 380.72038203 518.59235674 192.64331182a11.46496817 11.46496817 0 0 1 20.56815264-0.26369385l94.56879023 185.63503183zM496.64840732 85.52038203l-121.75796162 254.98089229L95.76433145 384.76178369A34.3949045 34.3949045 0 0 0 77.46050938 443.66879023l204.87324901 194.66369385-44.16879023 279.1146498a34.3949045 34.3949045 0 0 0 50.36560489 35.61592325l248.4-134.67898038 251.84522285 128.27579591a34.3949045 34.3949045 0 0 0 49.43694287-36.89426777l-51.30573223-277.85350284 199.73120977-199.90891758a34.3949045 34.3949045 0 0 0-19.82866201-58.40827998l-280.11783428-37.03184736L558.32993633 84.71210205a34.3949045 34.3949045 0 0 0-61.68152901 0.80254775z" p-id="1099"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588552949749" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1802" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M834.2654461 933.87476599H189.7345539A99.37494442 99.37494442 0 0 1 90.12523401 834.2654461V189.7345539A99.37494442 99.37494442 0 0 1 189.7345539 90.12523401h644.53089221A99.37494442 99.37494442 0 0 1 933.87476599 189.7345539v644.53089221A99.37494442 99.37494442 0 0 1 834.2654461 933.87476599zM189.7345539 140.04708127a49.68747262 49.68747262 0 0 0-49.68747263 49.68747263v644.53089221a49.68747262 49.68747262 0 0 0 49.68747262 49.68747262h644.53089221a49.68747262 49.68747262 0 0 0 49.68747263-49.68747262V189.7345539a49.68747262 49.68747262 0 0 0-49.68747263-49.68747263z" p-id="1803"></path><path d="M561.68747262 239.18765188h247.73423676a23.43748728 23.43748728 0 0 1 24.84373673 24.84373591 23.43748728 23.43748728 0 0 1-24.84373673 24.84373589H561.68747262a23.43748728 23.43748728 0 0 1-24.84373672-24.84373589 23.43748728 23.43748728 0 0 1 24.84373672-24.84373591z m0 123.9843057h247.73423676a24.84373591 24.84373591 0 0 1 0 49.68747262H561.68747262a24.84373591 24.84373591 0 1 1 0-49.68747262z m0 123.98430652h247.73423676a24.84373591 24.84373591 0 0 1 0 49.68747181H561.68747262a24.84373591 24.84373591 0 0 1 0-49.68747181zM214.57829062 611.1405698h594.84341876a24.84373591 24.84373591 0 0 1 0 49.68747263H214.57829062a24.84373591 24.84373591 0 0 1 0-49.68747263z m0 123.98430652h594.84341876a24.84373591 24.84373591 0 0 1 0 49.6874718H214.57829062a24.84373591 24.84373591 0 1 1 0-49.6874718z m52.03122061-280.07797001h133.82805103l32.10935696 81.79682959h46.87497372l-123.51555642-297.65608402H311.14073697l-121.40618308 297.65608403h46.87497373z m61.87496594-156.32803812a171.56240497 171.56240497 0 0 0 4.92187226-19.68748901 72.18745972 72.18745972 0 0 1 5.15624688 19.68748901l49.45309717 123.98430652H279.03137918z" p-id="1804"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579339929870" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M152 854.856875h325.7146875V237.715625H134.856875v600q0 6.99375 5.0746875 12.0684375T152 854.856875z m737.143125-17.1421875v-600H546.284375v617.1421875H872q6.99375 0 12.0684375-5.07375t5.0746875-12.0684375z m68.5715625-651.429375V837.715625q0 35.3821875-25.16625 60.5484375T872 923.4284375H152q-35.383125 0-60.5484375-25.1653125T66.284375 837.7146875V186.284375q0-35.3821875 25.16625-60.5484375T152 100.5715625h720q35.383125 0 60.5484375 25.1653125t25.16625 60.5484375z" p-id="1183"></path></svg>
|
||||
|
After Width: | Height: | Size: 873 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575803481213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="804" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62 511.97954521C62 263.86590869 263.90681826 62 511.97954521 62s449.97954521 201.825 449.97954521 449.97954521c0 248.19545479-201.90681826 449.97954521-449.97954521 449.97954521C263.90681826 962 62 760.175 62 511.97954521M901.98636348 511.97954521c0-215.24318174-175.00909131-390.41590869-390.00681827-390.41590869-215.03863652 0-389.96590869 175.17272695-389.96590868 390.41590869 0 215.28409131 175.00909131 390.45681826 389.96590868 390.45681826C727.01818174 902.47727305 901.98636348 727.30454521 901.98636348 511.97954521M264.17272695 430.28409131c0-5.76818174 2.12727305-11.51590869 6.64772696-15.87272696 8.71363652-8.75454521 22.88863652-8.75454521 31.725 0l209.4340913 208.22727305L721.45454521 414.53409131c8.75454521-8.71363652 22.97045479-8.71363652 31.90909132 0 8.71363652 8.75454521 8.71363652 22.88863652 0 31.60227304L511.97954521 685.74090869 270.71818174 446.01363653C266.27954521 441.77954521 264.17272695 436.05227305 264.17272695 430.28409131" p-id="805"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577185310368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M951.453125 476.84375H523.671875a131.8359375 131.8359375 0 0 0-254.1796875 0H72.546875v70.3125h196.9453125a131.8359375 131.8359375 0 0 0 254.1796875 0H951.453125z" p-id="1239"></path></svg>
|
||||
|
After Width: | Height: | Size: 564 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576042673958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1110" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M692 792H332c-150 0-270-120-270-270s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="1111"></path><path d="M341 522m-150 0a150 150 0 1 0 300 0 150 150 0 1 0-300 0Z" p-id="1112"></path></svg>
|
||||
|
After Width: | Height: | Size: 679 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595774196464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4269" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M856.187 65.711H167.215c-56.054 0-101.554 45.5-101.554 101.554v688.972c0 56.054 45.5 101.554 101.554 101.554h688.972c56.054 0 101.554-45.5 101.554-101.554V167.265c0-56.054-45.5-101.554-101.554-101.554z m-677.024 51.773H844.24c34.05 0 61.729 27.678 61.729 61.728v183.594H117.434V179.212c0-34.05 27.678-61.728 61.729-61.728z m217.046 297.094H634.66v219.934H396.209V414.578z m-51.773 219.834H117.434V414.578h227.002v219.834z m341.997-219.834H905.97v219.934H686.433V414.578z m157.807 491.44H179.163c-34.05 0-61.73-27.678-61.73-61.728V686.185h227.003v219.833h51.773V686.185H634.66v219.833h51.772V686.185H905.97V844.29c0 34.05-27.679 61.728-61.729 61.728z" p-id="4270"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802855098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2984" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64z m0 608c0 16-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V256c0-16 12.8-32 32-32h704c19.2 0 32 12.8 32 32v512z" p-id="2985"></path><path d="M224 288c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V320c0-16-12.8-32-32-32z m608 480c19.2 0 32-12.8 32-32V608L704 768h128z" p-id="2986"></path></svg>
|
||||
|
After Width: | Height: | Size: 787 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774825624" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1248" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.595712 482.290351 345.420077 482.290351l0 57.307194 210.477712 0L555.897789 274.196942l-57.301054 0L498.596735 482.290351zM498.595712 482.290351" p-id="1249"></path><path d="M577.685002 644.98478l379.879913 0 0 57.302077L577.685002 702.286858 577.685002 644.98478 577.685002 644.98478zM577.685002 644.98478" p-id="1250"></path><path d="M577.685002 773.764795l379.879913 0 0 57.307194L577.685002 831.071989 577.685002 773.764795 577.685002 773.764795zM577.685002 773.764795" p-id="1251"></path><path d="M577.685002 902.549927l379.879913 0 0 57.307194L577.685002 959.857121 577.685002 902.549927 577.685002 902.549927zM577.685002 902.549927" p-id="1252"></path><path d="M102.523001 382.290823c4.450359 2.615571 9.470699 3.954055 14.530948 3.954055 2.969635 0 5.952572-0.461511 8.836249-1.394766l190.809767-61.886489c15.052834-4.882194 23.297612-21.040199 18.415418-36.08894-4.882194-15.052834-21.040199-23.297612-36.093033-18.415418L175.676092 308.458257c15.994276-26.115797 35.170011-50.537 57.370639-72.743768 73.767074-73.767074 171.845857-114.388237 276.16783-114.388237 104.32095 0 202.39564 40.622186 276.16169 114.388237s114.393353 171.845857 114.393353 276.16783c0 26.427906-2.615571 52.449559-7.709589 77.780481l58.302871 0c4.464685-25.499767 6.708795-51.470255 6.708795-77.780481 0-60.449767-11.845793-119.102608-35.204803-174.336584-22.559808-53.334719-54.850236-101.226472-95.968725-142.349055-41.122583-41.122583-89.017406-73.408917-142.348032-95.968725C628.317169 75.866898 569.659211 64.021106 509.215584 64.021106c-60.448744 0-119.106702 11.845793-174.336584 35.207873-53.334719 22.559808-101.230566 54.846142-142.349055 95.968725-23.980157 23.980157-44.934398 50.278103-62.727647 78.601172l-20.738323-105.655342c-3.043313-15.527648-18.105357-25.642007-33.631982-22.599717-15.527648 3.048429-25.64303 18.105357-22.599717 33.637098l36.102243 183.932126C90.51348 371.153158 95.460142 378.13313 102.523001 382.290823L102.523001 382.290823zM102.523001 382.290823" p-id="1253"></path><path d="M126.020158 587.9416 67.768453 587.9416c5.759167 33.679054 15.368012 66.544579 28.789697 98.278327 22.559808 53.333696 54.850236 101.225449 95.971795 142.348032 41.122583 41.122583 89.014336 73.408917 142.349055 95.968725 54.112432 22.88829 111.517863 34.71157 170.668031 35.18229L505.547031 902.395408c-102.94972-0.941442-199.594851-41.445948-272.499277-114.349351C177.545672 732.543975 140.810003 663.275355 126.020158 587.9416L126.020158 587.9416zM126.020158 587.9416" p-id="1254"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577099827399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M520 559h204c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H488c-17.673 0-32-14.327-32-32 0-0.167 0.001-0.334 0.004-0.5a32.65 32.65 0 0 1-0.004-0.5V277c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32v282z m-8 401C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-64c212.077 0 384-171.923 384-384S724.077 128 512 128 128 299.923 128 512s171.923 384 384 384z" p-id="1009"></path></svg>
|
||||
|
After Width: | Height: | Size: 805 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1623495517222" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19363" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M128 128h256v64H192v192H128V128zM896 640v256h-256v-64h192v-192h64zM384 686.496L174.496 896 128 849.504 337.504 640H192v-64h256v256h-64v-145.504zM849.504 128L640 337.504V192h-64v256h256v-64h-145.504L896 174.496 849.504 128z" p-id="19364"></path></svg>
|
||||
|
After Width: | Height: | Size: 628 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,32 @@
|
||||
//定义基础色
|
||||
|
||||
//主色
|
||||
|
||||
body {
|
||||
--color-primary: #409eff;
|
||||
--color-primary-light: rgb(64 158 255 / 8%);
|
||||
}
|
||||
|
||||
@--color-primary:~ 'var(--color-primary)';
|
||||
@--color-primary-light:~ 'var(--color-primary-light)';
|
||||
|
||||
@text: #595959;
|
||||
@text-2: #8c8c8c;
|
||||
|
||||
//导航菜单
|
||||
@dark-text: rgb(255 255 255 / 66%);
|
||||
@dark-text-active: #eee;
|
||||
@dark-bg: #263238;
|
||||
@dark-bg-active: @--color-primary;
|
||||
|
||||
@light-text: @text;
|
||||
@light-text-active: @--color-primary;
|
||||
@light-bg: #fff;
|
||||
@light-bg-active: @--color-primary-light;
|
||||
|
||||
@primary-text: rgb(255 255 255 / 66%);
|
||||
@primary-text-2: rgb(255 255 255 / 65%);
|
||||
@primary-text-active: #fff;
|
||||
@primary-bg: @--color-primary;
|
||||
@primary-bg-light: @--color-primary-light;
|
||||
@primary-bg-active: @--color-primary-light;
|
||||
@@ -0,0 +1,935 @@
|
||||
@import "./base.less";
|
||||
|
||||
//主题样式
|
||||
|
||||
//=================
|
||||
.el-menu--vertical.rr-sidebar-menu-pop-light,
|
||||
.el-menu--vertical.rr-sidebar-menu-pop-dark {
|
||||
border-radius: 4px !important;
|
||||
box-shadow: none !important;
|
||||
.el-menu.el-menu--popup {
|
||||
min-width: 160px;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
//深色侧边栏
|
||||
.ui-sidebar-dark .rr-sidebar,
|
||||
.rr-sidebar-menu-pop-dark {
|
||||
background: @dark-bg !important;
|
||||
box-shadow: 0 4px 4px rgba(0, 21, 41, 0.35);
|
||||
.el-menu {
|
||||
background: @dark-bg !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
i,
|
||||
a {
|
||||
color: @dark-text-active !important;
|
||||
}
|
||||
}
|
||||
i,
|
||||
a {
|
||||
color: @dark-text !important;
|
||||
}
|
||||
&:not(.is-active):hover {
|
||||
background: inherit !important;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border-right: none !important;
|
||||
background: @dark-bg-active !important;
|
||||
}
|
||||
&.el-menu-item,
|
||||
> .el-sub-menu__title:first-child {
|
||||
i,
|
||||
a {
|
||||
color: @dark-text-active !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//浅色侧边栏
|
||||
.ui-sidebar-light .rr-sidebar,
|
||||
.rr-sidebar-menu-pop-light {
|
||||
background: @light-bg !important;
|
||||
box-shadow: 0 4px 4px rgba(0, 21, 41, 0.25);
|
||||
.el-menu {
|
||||
background: @light-bg !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
i,
|
||||
a {
|
||||
color: @light-text-active !important;
|
||||
}
|
||||
}
|
||||
i,
|
||||
a {
|
||||
color: @light-text !important;
|
||||
}
|
||||
&:not(.is-active):hover {
|
||||
background: inherit !important;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border-right: 2px solid @light-text-active !important;
|
||||
background: @light-bg-active !important;
|
||||
}
|
||||
&.el-menu-item,
|
||||
> .el-sub-menu__title:first-child {
|
||||
i,
|
||||
a {
|
||||
color: @light-text-active !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//================================
|
||||
.el-menu--horizontal.rr-sidebar-menu-pop-light,
|
||||
.el-menu--horizontal.rr-sidebar-menu-pop-dark {
|
||||
border-radius: 4px !important;
|
||||
box-shadow: none !important;
|
||||
background-color: @light-bg !important;
|
||||
border: none !important;
|
||||
margin-top: -5px;
|
||||
margin-left: 0;
|
||||
.el-popper {
|
||||
border: 0 !important;
|
||||
}
|
||||
.el-menu--horizontal {
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.el-menu.el-menu--popup {
|
||||
min-width: 160px;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
//浅色顶栏
|
||||
.ui-topHeader-light {
|
||||
.rr-header-ctx {
|
||||
box-shadow: 0 1px 1px #f1f1f1;
|
||||
&-logo {
|
||||
background: @light-bg !important;
|
||||
color: #000000bf;
|
||||
}
|
||||
}
|
||||
&.ui-sidebar-dark {
|
||||
.rr-header-ctx {
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
|
||||
}
|
||||
}
|
||||
.rr-header-right {
|
||||
background: @light-bg !important;
|
||||
.rr-header-right-items {
|
||||
* {
|
||||
color: @light-text !important;
|
||||
}
|
||||
> div {
|
||||
&:hover {
|
||||
color: #262626 !important;
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
}
|
||||
.el-badge__content {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
.rr-sidebar-menu {
|
||||
&.el-menu {
|
||||
background: @light-bg !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
i,
|
||||
a {
|
||||
color: @light-text-active !important;
|
||||
}
|
||||
}
|
||||
i,
|
||||
a {
|
||||
color: @light-text !important;
|
||||
}
|
||||
|
||||
i:not(.el-sub-menu__icon-arrow) {
|
||||
width: 17px !important;
|
||||
height: 17px !important;
|
||||
margin-right: 0 !important;
|
||||
margin-top: -4px;
|
||||
line-height: 17px;
|
||||
}
|
||||
span {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border-bottom: 2px solid @light-text-active !important;
|
||||
background: @light-bg !important;
|
||||
}
|
||||
&.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
i,
|
||||
a {
|
||||
color: @light-text-active !important;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
}
|
||||
&.isLink {
|
||||
border-bottom: 0 !important;
|
||||
i,
|
||||
a {
|
||||
color: @light-text !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//深色顶栏
|
||||
.ui-topHeader-dark {
|
||||
.rr-header-ctx {
|
||||
&-logo {
|
||||
background: @dark-bg !important;
|
||||
}
|
||||
}
|
||||
.rr-header-right {
|
||||
background: @dark-bg !important;
|
||||
.rr-header-right-items {
|
||||
* {
|
||||
color: @dark-text !important;
|
||||
&:hover {
|
||||
color: @dark-text-active !important;
|
||||
}
|
||||
}
|
||||
.el-badge__content {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
.rr-sidebar-menu {
|
||||
&.el-menu {
|
||||
background: @dark-bg !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background: @dark-bg !important;
|
||||
i,
|
||||
a {
|
||||
color: @dark-text-active !important;
|
||||
}
|
||||
}
|
||||
i,
|
||||
a {
|
||||
color: @dark-text !important;
|
||||
}
|
||||
&:not(.is-active):hover {
|
||||
background: inherit !important;
|
||||
}
|
||||
i:not(.el-sub-menu__icon-arrow) {
|
||||
width: 17px !important;
|
||||
height: 17px !important;
|
||||
margin-right: 0 !important;
|
||||
margin-top: -4px;
|
||||
line-height: 17px;
|
||||
}
|
||||
span {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border-bottom: 2px solid @dark-text-active !important;
|
||||
background: @dark-bg !important;
|
||||
}
|
||||
&.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
border-bottom: 2px solid @dark-text-active !important;
|
||||
i,
|
||||
a {
|
||||
color: @dark-text-active !important;
|
||||
}
|
||||
}
|
||||
&.isLink {
|
||||
border-bottom: 0 !important;
|
||||
i,
|
||||
a {
|
||||
color: @dark-text !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//主题色
|
||||
.ui-topHeader-primary {
|
||||
.rr-header-ctx {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
||||
position: relative;
|
||||
z-index: 102;
|
||||
}
|
||||
.rr-header-ctx-logo {
|
||||
background: @primary-bg !important;
|
||||
}
|
||||
.rr-header-right {
|
||||
background: @primary-bg !important;
|
||||
.rr-header-right-items,
|
||||
.rr-header-right-left-br {
|
||||
div,
|
||||
span,
|
||||
svg,
|
||||
i {
|
||||
color: @primary-text !important;
|
||||
&:hover {
|
||||
color: @primary-text-active !important;
|
||||
}
|
||||
}
|
||||
> div:not(.el-breadcrumb) {
|
||||
&:hover {
|
||||
color: #262626 !important;
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
}
|
||||
.el-badge__content {
|
||||
color: #fff !important;
|
||||
}
|
||||
.el-breadcrumb {
|
||||
.el-breadcrumb__item {
|
||||
&:not(:first-child) {
|
||||
* {
|
||||
color: @primary-text-2 !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.rr-sidebar-menu {
|
||||
&.el-menu {
|
||||
background: @primary-bg !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
i,
|
||||
a {
|
||||
color: @primary-text-active !important;
|
||||
}
|
||||
}
|
||||
i,
|
||||
a {
|
||||
color: @primary-text !important;
|
||||
}
|
||||
i:not(.el-sub-menu__icon-arrow) {
|
||||
width: 17px !important;
|
||||
height: 17px !important;
|
||||
margin-right: 0 !important;
|
||||
margin-top: -4px;
|
||||
line-height: 17px;
|
||||
}
|
||||
span {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
&.el-menu-item {
|
||||
border-bottom: 2px solid @primary-text-active !important;
|
||||
}
|
||||
&.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
border-bottom: 2px solid @primary-text-active !important;
|
||||
i,
|
||||
a {
|
||||
color: @primary-text-active !important;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
}
|
||||
&.isLink {
|
||||
border-bottom: 0 !important;
|
||||
i,
|
||||
a {
|
||||
color: @primary-text !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============
|
||||
//导航模式
|
||||
.ui-navLayout-left {
|
||||
&.ui-sidebar-light {
|
||||
.rr-sidebar {
|
||||
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.08);
|
||||
z-index: 101;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-navLayout-top {
|
||||
&.ui-topHeader-light {
|
||||
.rr-header-right {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
.rr-header-ctx-logo {
|
||||
max-width: inherit !important;
|
||||
&-text {
|
||||
max-width: inherit !important;
|
||||
overflow: inherit !important;
|
||||
}
|
||||
}
|
||||
.rr-view-tab-wrap {
|
||||
left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-navLayout-mix {
|
||||
.rr-header-ctx-logo {
|
||||
max-width: inherit !important;
|
||||
&-text {
|
||||
max-width: inherit !important;
|
||||
overflow: inherit !important;
|
||||
}
|
||||
}
|
||||
&.ui-sidebar-light {
|
||||
.rr-sidebar {
|
||||
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.08);
|
||||
z-index: 101;
|
||||
}
|
||||
}
|
||||
.rr-sidebar {
|
||||
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.08);
|
||||
z-index: 101;
|
||||
}
|
||||
.rr-header-right-left-br {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
//========
|
||||
//内容不铺满
|
||||
.ui-contentFull-false {
|
||||
.rr-view-ctx {
|
||||
width: 1200px !important;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
//=======
|
||||
//tab标签栏开关
|
||||
.ui-openTabsPage {
|
||||
&-false {
|
||||
.rr-view-ctx {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=======
|
||||
//logo自动
|
||||
//导航模式在顶部时logo自动要取消
|
||||
.ui-logoAuto-true,
|
||||
.ui-navLayout-top {
|
||||
.rr-header-ctx-logo {
|
||||
width: inherit !important;
|
||||
padding: 0 15px 0 20px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
&.ui-topHeader-primary .rr-header-ctx-logo {
|
||||
background: @primary-bg !important;
|
||||
color: #ffffffd9 !important;
|
||||
}
|
||||
&.ui-topHeader-dark .rr-header-ctx-logo {
|
||||
background: @dark-bg !important;
|
||||
color: #ffffffd9 !important;
|
||||
}
|
||||
&.ui-topHeader-light .rr-header-ctx-logo {
|
||||
background: @light-bg !important;
|
||||
color: #000000bf;
|
||||
box-shadow: 1px 0 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
//侧边栏多彩图标
|
||||
.ui-colorIcon-true {
|
||||
.rr-sidebar {
|
||||
.el-menu {
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item,
|
||||
.isLink {
|
||||
margin-left: -5px !important;
|
||||
}
|
||||
li {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
flex-shrink: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
font-size: 14px;
|
||||
background-color: rgb(97, 178, 252);
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
color: rgb(255, 255, 255) !important;
|
||||
|
||||
.iconfont {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(2n) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(125, 215, 51);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(3) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(50, 162, 212);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(4) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(115, 131, 207);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(5) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(245, 104, 111);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(6) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(43, 204, 206);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(7) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(125, 215, 51);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-child(8) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child {
|
||||
background-color: rgb(250, 173, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//--
|
||||
.el-sub-menu {
|
||||
.el-menu {
|
||||
li,
|
||||
.el-sub-menu__title {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
line-height: 8px;
|
||||
font-size: 30px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
margin: 0 0 0 10px;
|
||||
background: @dark-text !important;
|
||||
color: @dark-text !important;
|
||||
&:before {
|
||||
content: "";
|
||||
margin-left: -11px;
|
||||
font-family: element-icons !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-menu-item,
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
i:first-child {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.ui-sidebar-light {
|
||||
.rr-sidebar {
|
||||
.el-sub-menu .el-menu {
|
||||
.el-sub-menu {
|
||||
.el-sub-menu__title {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
color: @light-text !important;
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
color: @light-text-active !important;
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.is-active .el-sub-menu__title [class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
color: @light-text-active !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-menu-item {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
background: @light-text !important;
|
||||
color: @light-text !important;
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
&.is-active,
|
||||
&:hover {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
background: @light-text-active !important;
|
||||
color: @light-text-active !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover:not(.is-active) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.ui-sidebar-dark {
|
||||
.rr-sidebar {
|
||||
.el-sub-menu {
|
||||
.el-sub-menu.is-opened {
|
||||
&.is-active {
|
||||
.el-sub-menu__title {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
background: @dark-text-active !important;
|
||||
color: @dark-text-active !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-menu .el-menu-item,
|
||||
.el-sub-menu.is-opened .el-sub-menu__title {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
background: @dark-text !important;
|
||||
color: @dark-text !important;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
&.is-active,
|
||||
&:hover {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
background: @dark-text-active !important;
|
||||
color: @dark-text-active !important;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover:not(.is-active) {
|
||||
[class^="el-icon"] {
|
||||
&:first-child:not(.el-sub-menu__icon-arrow) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//侧边栏收缩状态
|
||||
.rr.ui-sidebarCollapse {
|
||||
&-true {
|
||||
.rr-view-tab-wrap {
|
||||
left: 60px;
|
||||
}
|
||||
.rr-header-ctx-logo-line {
|
||||
width: 0;
|
||||
}
|
||||
.enabled-logo-false {
|
||||
display: flex;
|
||||
}
|
||||
&.ui-logoAuto {
|
||||
&-false {
|
||||
.rr-header-ctx-logo {
|
||||
width: 60px !important;
|
||||
&-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-true {
|
||||
.enabled-logo-false {
|
||||
display: none;
|
||||
}
|
||||
.rr-header-ctx-logo-line {
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.ui-navLayout-top {
|
||||
//导航模式为顶部时自动展开logo状态
|
||||
.rr-header-ctx-logo {
|
||||
width: inherit !important;
|
||||
padding: 0 15px 0 20px;
|
||||
box-shadow: none !important;
|
||||
&-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.enabled-logo-false {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.rr-sidebar:not(.rr-sidebar-mobile) {
|
||||
width: 60px !important;
|
||||
.el-menu {
|
||||
width: 60px !important;
|
||||
}
|
||||
// 收起效果
|
||||
.rr-sidebar-menu {
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title,
|
||||
.el-sub-menu {
|
||||
a,
|
||||
.el-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-false {
|
||||
.rr-header-ctx-logo {
|
||||
&-text {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//tabStyle
|
||||
.ui-tabStyle-default {
|
||||
.rr-view-tab {
|
||||
.el-tabs__item {
|
||||
border-right: none !important;
|
||||
padding: 0 15px 0 !important;
|
||||
&.is-active {
|
||||
color: @--color-primary !important;
|
||||
}
|
||||
&:before {
|
||||
content: none;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
height: 3px;
|
||||
width: 0;
|
||||
background-color: @--color-primary !important;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
&.is-active:after,
|
||||
&:hover:after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.el-tabs__nav-wrap {
|
||||
&:before,
|
||||
&:after,
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui-tabStyle-dot {
|
||||
.rr-view-tab-wrap {
|
||||
.rr-view-tab {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
color: @--color-primary !important;
|
||||
&:before {
|
||||
background-color: @--color-primary !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui-tabStyle-card {
|
||||
.rr-view-tab-wrap {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
padding-top: 10px;
|
||||
.rr-view-tab {
|
||||
height: 30px;
|
||||
background: transparent !important;
|
||||
&-ops {
|
||||
border-radius: 4px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
width: 30px;
|
||||
background-color: #fff;
|
||||
margin-right: 10px;
|
||||
.el-icon--right {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.el-tabs__item {
|
||||
margin-left: 8px;
|
||||
padding: 0 15px 0 !important;
|
||||
border-radius: 4px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
background-color: #fff;
|
||||
&:nth-child(2) {
|
||||
margin-left: 0;
|
||||
padding: 0 15px !important;
|
||||
}
|
||||
&.is-active {
|
||||
background-color: @--color-primary !important;
|
||||
color: #fff;
|
||||
}
|
||||
&:before {
|
||||
content: none;
|
||||
}
|
||||
&:after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
.el-tabs__nav-wrap {
|
||||
&:before,
|
||||
&:after,
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
background: #eff2f5 !important;
|
||||
}
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
&:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//外链
|
||||
.rr-sidebar-menu.el-menu .el-menu-item.is-active.isLink {
|
||||
background: inherit !important;
|
||||
}
|
||||
|
||||
//不同语言下的差异
|
||||
[lang="en-US"] {
|
||||
.rr-header-ctx-logo-text {
|
||||
letter-spacing: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
:not(html):not(body)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:not(html):not(body)::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:not(html):not(body)::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background-color: hsla(0, 0%, 54.9%, 0.3);
|
||||
}
|
||||
|
||||
:not(html):not(body)::-webkit-scrollbar-thumb:hover {
|
||||
background-color: hsla(0, 0%, 54.9%, 0.5);
|
||||
}
|
||||
|
||||
.ele-scrollbar-mini::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.ele-scrollbar-mini::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.ele-scrollbar-hide::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
@import "./base.less";
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.rr-header-action {
|
||||
display: flex !important;
|
||||
}
|
||||
.show-xs-only {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 768px) {
|
||||
}
|
||||
@media only screen and (min-width: 768px) and (max-width: 992px) {
|
||||
}
|
||||
@media only screen and (max-width: 992px) {
|
||||
}
|
||||
@media only screen and (min-width: 992px) {
|
||||
}
|
||||
@media only screen and (min-width: 992px) and (max-width: 1200px) {
|
||||
}
|
||||
@media only screen and (max-width: 1200px) {
|
||||
}
|
||||
@media only screen and (min-width: 1200px) {
|
||||
}
|
||||
@media only screen and (min-width: 1200px) and (max-width: 1920px) {
|
||||
}
|
||||
@media only screen and (max-width: 1920px) {
|
||||
}
|
||||
@media only screen and (min-width: 1920px) {
|
||||
}
|
||||
|
||||
//
|
||||
.ui-mobile {
|
||||
.rr-view-tab-wrap {
|
||||
left: 0 !important;
|
||||
transition: left 0s !important;
|
||||
}
|
||||
.rr-header-ctx-logo-img-wrap {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.rr-sidebar-mobile {
|
||||
z-index: 9999 !important;
|
||||
&-inner {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui-sidebar-light .rr-sidebar-mobile {
|
||||
.el-drawer__body,
|
||||
.rr-header-ctx-logo-mobile {
|
||||
background: @light-bg !important;
|
||||
}
|
||||
.rr-header-ctx-logo-mobile {
|
||||
color: @light-text !important;
|
||||
}
|
||||
}
|
||||
.ui-sidebar-dark .rr-sidebar-mobile {
|
||||
.el-drawer__body,
|
||||
.rr-header-ctx-logo-mobile {
|
||||
background: @dark-bg !important;
|
||||
}
|
||||
.rr-header-ctx-logo-mobile {
|
||||
color: @dark-text !important;
|
||||
}
|
||||
}
|
||||
.ui-sidebarCollapse-true,
|
||||
.ui-sidebarCollapse-false {
|
||||
.rr-sidebar-mobile {
|
||||
width: initial !important;
|
||||
.el-menu.rr-sidebar-menu {
|
||||
width: 230px !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
a {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.rr-header-ctx-logo.rr-header-ctx-logo-mobile {
|
||||
width: auto !important;
|
||||
.rr-header-ctx-logo-text {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer,
|
||||
.el-drawer__body {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { withInstall } from "@/utils/utils";
|
||||
import SvgIcon from "./index.vue";
|
||||
|
||||
export default withInstall(SvgIcon);
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" :class="`iconfont ${className}`" :style="`width:${width};height:${height};color:${color};${style}`">
|
||||
<use :xlink:href="symbolId" />
|
||||
</svg>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
|
||||
/**
|
||||
* 自定义svg图标,可自行将svg图标下载后存放在/src/assets/icons/svg目录下
|
||||
* `使用方法:<svg-icon name="earth" color="red"></svg-icon>`
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "SvgIcon",
|
||||
props: {
|
||||
prefix: {
|
||||
type: String,
|
||||
default: "icon"
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
width: String,
|
||||
height: String,
|
||||
className: { type: String, default: "" },
|
||||
style: { type: String, default: "" }
|
||||
},
|
||||
setup(props) {
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.name.replace("icon-", "")}`);
|
||||
return { symbolId };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { withInstall } from "@/utils/utils";
|
||||
import RenDeptTree from "./src/ren-dept-tree.vue";
|
||||
|
||||
RenDeptTree.name = "RenDeptTree";
|
||||
export default withInstall(RenDeptTree);
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-input v-model="showDeptName" :placeholder="placeholder" @click="deptDialog">
|
||||
<template v-slot:append>
|
||||
<el-button icon="search" @click="deptDialog"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-dialog v-model="visibleDept" width="30%" :modal="false" :title="placeholder" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form size="small" :inline="true">
|
||||
<el-form-item label="关键字:">
|
||||
<el-input v-model="filterText" :style="{ width: '150px' }"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="default">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-tree class="filter-tree" :data="deptList" :default-expanded-keys="expandedKeys" :props="{ label: 'name', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" :highlight-current="true" node-key="id" ref="treeRef"> </el-tree>
|
||||
<template v-slot:footer>
|
||||
<el-button type="default" @click="cancelHandle()">取消</el-button>
|
||||
<el-button v-if="query" type="info" @click="clearHandle()">清除</el-button>
|
||||
<el-button type="primary" @click="commitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, watch } from "vue";
|
||||
import { IObject } from "@/types/interface";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const filterText = ref("");
|
||||
const visibleDept = ref(false);
|
||||
const deptList = ref<any[]>([]);
|
||||
const showDeptName = ref("");
|
||||
const expandedKeys = ref<any[]>([]);
|
||||
const treeRef = ref();
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
deptName: String,
|
||||
query: Boolean,
|
||||
placeholder: String
|
||||
});
|
||||
|
||||
watch(
|
||||
() => filterText.value,
|
||||
(val) => {
|
||||
treeRef.value.filter(val);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.deptName,
|
||||
(val) => {
|
||||
showDeptName.value = val as string;
|
||||
}
|
||||
);
|
||||
|
||||
const deptDialog = () => {
|
||||
expandedKeys.value = [];
|
||||
visibleDept.value = true;
|
||||
getDeptList(props.modelValue);
|
||||
};
|
||||
|
||||
const filterNode = (value: string, data: IObject) => {
|
||||
if (!value) return true;
|
||||
return data.name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
const getDeptList = (id?: string) => {
|
||||
return baseService.get("/sys/dept/list").then((res) => {
|
||||
deptList.value = res.data;
|
||||
nextTick(() => {
|
||||
if (id) {
|
||||
treeRef.value.setCurrentKey(id);
|
||||
expandedKeys.value = [id];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const cancelHandle = () => {
|
||||
visibleDept.value = false;
|
||||
deptList.value = [];
|
||||
filterText.value = "";
|
||||
};
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "update:deptName"]);
|
||||
|
||||
const clearHandle = () => {
|
||||
emit("update:modelValue", "");
|
||||
emit("update:deptName", "");
|
||||
showDeptName.value = "";
|
||||
visibleDept.value = false;
|
||||
deptList.value = [];
|
||||
filterText.value = "";
|
||||
};
|
||||
|
||||
const commitHandle = () => {
|
||||
const node = treeRef.value.getCurrentNode();
|
||||
if (!node) {
|
||||
ElMessage.error("请选择部门");
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", node.id);
|
||||
emit("update:deptName", node.name);
|
||||
showDeptName.value = node.name;
|
||||
visibleDept.value = false;
|
||||
deptList.value = [];
|
||||
filterText.value = "";
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { withInstall } from "@/utils/utils";
|
||||
import RenRadioGroup from "./src/ren-radio-group.vue";
|
||||
|
||||
export default withInstall(RenRadioGroup);
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-radio-group v-model="value" @change="$emit('update:modelValue', $event)">
|
||||
<el-radio :label="data.dictValue" v-for="data in dataList" :key="data.dictValue">{{ data.dictLabel }}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { getDictDataList } from "@/utils/utils";
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { useAppStore } from "@/store";
|
||||
export default defineComponent({
|
||||
name: "RenRadioGroup",
|
||||
props: {
|
||||
modelValue: [Number, String],
|
||||
dictType: String
|
||||
},
|
||||
setup(props) {
|
||||
const store = useAppStore();
|
||||
return {
|
||||
value: computed(() => `${props.modelValue}`),
|
||||
dataList: getDictDataList(store.state.dicts, props.dictType)
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { withInstall } from "@/utils/utils";
|
||||
import RenRegionTree from "./src/ren-region-tree.vue";
|
||||
|
||||
RenRegionTree.name = "RenRegionTree";
|
||||
export default withInstall(RenRegionTree);
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div class="ren-region">
|
||||
<el-input v-model="showName" :placeholder="placeholder" @click="treeDialog">
|
||||
<template v-slot:append>
|
||||
<el-button icon="search" @click="treeDialog"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-dialog v-model="visibleTree" width="360px" :modal="false" :title="placeholder" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form size="small" :inline="true">
|
||||
<el-form-item label="关键字">
|
||||
<el-input v-model="filterText" :style="{ width: '150px' }"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="default">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-tree class="filter-tree" :data="dataList" :default-expanded-keys="expandedKeys" :props="{ label: 'name', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" :highlight-current="true" node-key="id" ref="treeRef"> </el-tree>
|
||||
<template v-slot:footer>
|
||||
<el-button type="default" @click="cancelHandle()">取消</el-button>
|
||||
<el-button type="info" @click="clearHandle()">清除</el-button>
|
||||
<el-button type="primary" @click="commitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, watch } from "vue";
|
||||
import { treeDataTranslate } from "@/utils/utils";
|
||||
import { IObject } from "@/types/interface";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const filterText = ref("");
|
||||
const visibleTree = ref(false);
|
||||
const dataList = ref<any[]>([]);
|
||||
const showName = ref("");
|
||||
const expandedKeys = ref<any[]>([]);
|
||||
const treeRef = ref();
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: [Number, String],
|
||||
parentName: String,
|
||||
placeholder: String
|
||||
});
|
||||
|
||||
watch(
|
||||
() => filterText.value,
|
||||
(val) => {
|
||||
treeRef.value.filter(val);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.parentName,
|
||||
(val) => {
|
||||
showName.value = val as string;
|
||||
}
|
||||
);
|
||||
|
||||
const treeDialog = () => {
|
||||
expandedKeys.value = [];
|
||||
if (treeRef.value) {
|
||||
treeRef.value.setCurrentKey(null);
|
||||
}
|
||||
visibleTree.value = true;
|
||||
getDataList(props.modelValue);
|
||||
};
|
||||
|
||||
const filterNode = (value: string, data: IObject) => {
|
||||
if (!value) return true;
|
||||
return data.name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
const getDataList = (id: any) => {
|
||||
return baseService.get("/sys/region/tree").then((res) => {
|
||||
dataList.value = treeDataTranslate(res.data);
|
||||
nextTick(() => {
|
||||
treeRef.value.setCurrentKey(id);
|
||||
expandedKeys.value = [id];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const cancelHandle = () => {
|
||||
visibleTree.value = false;
|
||||
dataList.value = [];
|
||||
filterText.value = "";
|
||||
};
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "update:parentName"]);
|
||||
|
||||
const clearHandle = () => {
|
||||
emit("update:modelValue", "0");
|
||||
emit("update:parentName", "");
|
||||
showName.value = "";
|
||||
visibleTree.value = false;
|
||||
dataList.value = [];
|
||||
filterText.value = "";
|
||||
};
|
||||
|
||||
const commitHandle = () => {
|
||||
const node = treeRef.value.getCurrentNode();
|
||||
if (!node) {
|
||||
ElMessage.error("请选择");
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", node.id);
|
||||
emit("update:parentName", node.name);
|
||||
|
||||
showName.value = node.name;
|
||||
visibleTree.value = false;
|
||||
dataList.value = [];
|
||||
filterText.value = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ren-region {
|
||||
.filter-tree {
|
||||
max-height: 230px;
|
||||
overflow: auto;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 0 0 0 20px;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding: 10px 20px 8px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { withInstall } from "@/utils/utils";
|
||||
import RenSelect from "./src/ren-select.vue";
|
||||
|
||||
export default withInstall(RenSelect);
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<el-select v-model="value" @change="$emit('update:modelValue', $event)" :placeholder="placeholder" clearable>
|
||||
<el-option :label="data.dictLabel" v-for="data in dataList" :key="data.dictValue" :value="data.dictValue">{{ data.dictLabel }}</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { getDictDataList } from "@/utils/utils";
|
||||
import { useAppStore } from "@/store";
|
||||
export default defineComponent({
|
||||
name: "RenSelect",
|
||||
props: {
|
||||
modelValue: [Number, String],
|
||||
dictType: String,
|
||||
placeholder: String
|
||||
},
|
||||
setup(props) {
|
||||
const store = useAppStore();
|
||||
return {
|
||||
value: computed(() => `${props.modelValue}`),
|
||||
dataList: getDictDataList(store.state.dicts, props.dictType)
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div style="border: 1px solid #ccc; z-index: 100">
|
||||
<!-- 工具栏 -->
|
||||
<Toolbar :editor="editorRef" :mode="mode" style="border-bottom: 1px solid #ccc" />
|
||||
<!-- 编辑器 -->
|
||||
<Editor :model-value="modelValue" :style="style" :disabled="disabled" :default-config="editorConfig" :mode="mode" @onCreated="handleCreated" @onChange="handleChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import "@wangeditor/editor/dist/css/style.css";
|
||||
import { onBeforeUnmount, shallowRef } from "vue";
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
|
||||
import { IDomEditor, IEditorConfig } from "@wangeditor/editor";
|
||||
import app from "@/constants/app";
|
||||
import { getToken } from "@/utils/cache";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "default" // 可选值:[default | simple]
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
style: {
|
||||
type: String,
|
||||
default: "height: 300px;"
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef();
|
||||
|
||||
type InsertFnType = (url: string, alt: string, href: string) => void;
|
||||
|
||||
// 编辑器配置
|
||||
const editorConfig: Partial<IEditorConfig> = {
|
||||
placeholder: props.placeholder,
|
||||
readOnly: props.disabled,
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
server: `${app.api}/sys/oss/upload?token=${getToken()}`, // 上传地址
|
||||
fieldName: "file",
|
||||
// 自定义插入图片
|
||||
customInsert(res: any, insertFn: InsertFnType) {
|
||||
// res 即服务端的返回结果
|
||||
// 从 res 中找到 url alt href ,然后插图图片
|
||||
insertFn(res.data.src, "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) {
|
||||
return;
|
||||
}
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
const handleCreated = (editor: IDomEditor) => {
|
||||
editorRef.value = editor;
|
||||
};
|
||||
|
||||
// 编辑器change事件触发
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const handleChange = (editor: IDomEditor) => {
|
||||
emit("update:modelValue", editor.getHtml());
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
import { getValueByKeys } from "@/utils/utils";
|
||||
import appPack from "../../package.json";
|
||||
/**
|
||||
* app系统配置
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* 系统版本号,自动读取package.json中的version字段
|
||||
*/
|
||||
version: appPack.version,
|
||||
|
||||
/**
|
||||
* 系统默认语言
|
||||
*/
|
||||
defaultLang: "zh-CN",
|
||||
|
||||
/**
|
||||
* api请求地址,这里读取env环境变量中的VITE_APP_API,优先使用全局变量window.SITE_CONFIG.apiURL钩子,支持在index.html中配置
|
||||
*/
|
||||
api: getValueByKeys(window, "SITE_CONFIG.apiURL") || import.meta.env.VITE_APP_API,
|
||||
|
||||
/**
|
||||
* 启用logo图标,logo尺寸32*32,存放路径@/assets/images/logo.png
|
||||
*/
|
||||
enabledLogo: false,
|
||||
|
||||
/**
|
||||
* 开启页面缓存
|
||||
*/
|
||||
enabledKeepAlive: true,
|
||||
|
||||
/**
|
||||
* 网络请求超时时间,单位毫秒
|
||||
*/
|
||||
requestTimeout: 30000,
|
||||
|
||||
/**
|
||||
* 全屏渲染的页面
|
||||
*/
|
||||
fullscreenPages: ["/login"]
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* token值
|
||||
*/
|
||||
export const CacheToken = "CacheToken";
|
||||
|
||||
/**
|
||||
* 语言
|
||||
*/
|
||||
export const CacheLang = "CacheLang";
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
export const CacheTheme = "CacheTheme";
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 主题设置默认值
|
||||
*/
|
||||
export const themeSetting = {
|
||||
sidebar: "dark",
|
||||
topHeader: "primary",
|
||||
themeColor: "#2d88ac",
|
||||
navLayout: "left",
|
||||
contentFull: true,
|
||||
logoAuto: false,
|
||||
colorIcon: false,
|
||||
sidebarUniOpened: true,
|
||||
openTabsPage: true,
|
||||
tabStyle: "default",
|
||||
sidebarCollapse: false
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 页面渲染布局
|
||||
*/
|
||||
export enum EPageLayoutEnum {
|
||||
"page",
|
||||
"fullscreen"
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航模式
|
||||
*/
|
||||
export enum ESidebarLayoutEnum {
|
||||
/**
|
||||
* 左侧导航
|
||||
*/
|
||||
Left = "left",
|
||||
/**
|
||||
* 顶部导航
|
||||
*/
|
||||
Top = "top",
|
||||
/**
|
||||
* 混合导航
|
||||
*/
|
||||
Mix = "mix"
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题设置
|
||||
*/
|
||||
export enum EThemeSetting {
|
||||
/**
|
||||
* 侧边栏风格
|
||||
*/
|
||||
Sidebar = "sidebar",
|
||||
/**
|
||||
* 顶部风格
|
||||
*/
|
||||
TopHeader = "topHeader",
|
||||
/**
|
||||
* 主题色
|
||||
*/
|
||||
ThemeColor = "themeColor",
|
||||
//---
|
||||
/**
|
||||
* 布局模式
|
||||
*/
|
||||
NavLayout = "navLayout",
|
||||
/**
|
||||
* 内容是否铺满
|
||||
*/
|
||||
ContentFull = "contentFull",
|
||||
//---
|
||||
/**
|
||||
* logo宽度自动
|
||||
*/
|
||||
LogoAuto = "logoAuto",
|
||||
/**
|
||||
* 多彩图标
|
||||
*/
|
||||
ColorIcon = "colorIcon",
|
||||
/**
|
||||
* 侧边栏排他展开
|
||||
*/
|
||||
SidebarUniOpened = "sidebarUniOpened",
|
||||
/**
|
||||
* 开启tab标签页
|
||||
*/
|
||||
OpenTabsPage = "openTabsPage",
|
||||
/**
|
||||
* tab标签风格
|
||||
*/
|
||||
TabStyle = "tabStyle",
|
||||
//---
|
||||
/**
|
||||
* 侧边栏展开收起
|
||||
*/
|
||||
SidebarCollapse = "sidebarCollapse"
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统框架事件枚举
|
||||
*/
|
||||
export enum EMitt {
|
||||
/**
|
||||
* 全局加载
|
||||
*/
|
||||
OnLoading = "onLoading",
|
||||
/**
|
||||
* 切换左侧侧边栏
|
||||
*/
|
||||
OnSwitchLeftSidebar = "onSwitchLeftSidebar",
|
||||
/**
|
||||
* 推送菜单到tab标签页
|
||||
*/
|
||||
OnPushMenuToTabs = "onPushMenuToTabs",
|
||||
/**
|
||||
* 设置主题
|
||||
*/
|
||||
OnSetTheme = "onSetTheme",
|
||||
/**
|
||||
* 设置侧边栏排他展开
|
||||
*/
|
||||
OnSetThemeNotUniqueOpened = "onSetTheme_not_uniqueOpened",
|
||||
/**
|
||||
* 设置开启标签页
|
||||
*/
|
||||
OnSetThemeTabsPage = "onSetTheme_tabsPage",
|
||||
/**
|
||||
* 设置导航模式
|
||||
*/
|
||||
OnSetNavLayout = "onSetNavLayout",
|
||||
/**
|
||||
* 刷新tab标签页
|
||||
*/
|
||||
OnReloadTabPage = "onReloadTabPage",
|
||||
|
||||
//
|
||||
/**
|
||||
* 移动端打开侧边栏
|
||||
*/
|
||||
OnMobileOpenSidebar = "onMobileOpenSidebar",
|
||||
|
||||
//
|
||||
/**
|
||||
* 混合导航选中顶部主菜单
|
||||
*/
|
||||
OnSelectHeaderNavMenusByMixNav = "onSelectHeaderNavMenusByMixNav",
|
||||
|
||||
/**
|
||||
* 关闭当前tab页
|
||||
*/
|
||||
OnCloseCurrTab = "onCloseCurrTab"
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题是key
|
||||
*/
|
||||
export enum EThemeColor {
|
||||
/**
|
||||
* 主题色
|
||||
*/
|
||||
ThemeColor = "--color-primary"
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
import app from "@/constants/app";
|
||||
import { EMitt, EThemeSetting } from "@/constants/enum";
|
||||
import { IObject, IViewHooks, IViewHooksOptions } from "@/types/interface";
|
||||
import { registerDynamicToRouterAndNext } from "@/router";
|
||||
import baseService from "@/service/baseService";
|
||||
import { getToken } from "@/utils/cache";
|
||||
import emits from "@/utils/emits";
|
||||
import { getThemeConfigCacheByKey } from "@/utils/theme";
|
||||
import { checkPermission, getDictLabel } from "@/utils/utils";
|
||||
import qs from "qs";
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
/**
|
||||
* 通用视图业务逻辑(列表/增删改查基本业务)
|
||||
* @param props 自定义通用业务state
|
||||
* @returns 返回响应式自定义state和通用方法
|
||||
*/
|
||||
const useView = (props: IViewHooksOptions | IObject): IViewHooks => {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const store = useAppStore();
|
||||
const defaultOptions: IViewHooksOptions = {
|
||||
createdIsNeed: true,
|
||||
activatedIsNeed: false,
|
||||
getDataListURL: "",
|
||||
getDataListIsPage: false,
|
||||
deleteURL: "",
|
||||
deleteIsBatch: false,
|
||||
deleteIsBatchKey: "id",
|
||||
exportURL: "",
|
||||
dataForm: {},
|
||||
dataList: [],
|
||||
order: "",
|
||||
orderField: "",
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
elTable: {}
|
||||
};
|
||||
const mergeDefaultStateToPageState = (options: IObject, props: IObject): IViewHooksOptions => {
|
||||
for (const key in options) {
|
||||
if (!Object.getOwnPropertyDescriptor(props, key)) {
|
||||
props[key] = options[key];
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
const state = mergeDefaultStateToPageState(defaultOptions, props);
|
||||
onMounted(() => {
|
||||
if (state.createdIsNeed && !state.activatedIsNeed) {
|
||||
viewFns.query();
|
||||
}
|
||||
});
|
||||
onActivated(() => {
|
||||
if (store.state.closedTabs.includes(store.state.activeTabName)) {
|
||||
//如果当前打开的tab页面是之前已经关闭过的会存在keep-alive缓存
|
||||
//这里采用临时刷新页面解决方案
|
||||
//待vue官方开放缓存策略后再行实现 https://github.com/vuejs/vue-next/pull/4339 https://github.com/vuejs/rfcs/pull/284
|
||||
|
||||
const closedTabs = store.state.closedTabs;
|
||||
store.updateState({
|
||||
closedTabs: closedTabs.filter((x: string) => x !== store.state.activeTabName)
|
||||
});
|
||||
emits.emit(EMitt.OnReloadTabPage);
|
||||
}
|
||||
|
||||
if (state.activatedIsNeed) {
|
||||
viewFns.query();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
const rejectFns = {
|
||||
hasPermission(key: string) {
|
||||
return checkPermission(store.state.permissions as string[], key);
|
||||
},
|
||||
getDictLabel(dictType: string, dictValue: number) {
|
||||
return getDictLabel(store.state.dicts, dictType, dictValue);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
const viewFns = {
|
||||
// 获取数据列表
|
||||
query() {
|
||||
if (!state.getDataListURL) {
|
||||
return;
|
||||
}
|
||||
state.dataListLoading = true;
|
||||
baseService
|
||||
.get(state.getDataListURL, {
|
||||
order: state.order,
|
||||
orderField: state.orderField,
|
||||
page: state.getDataListIsPage ? state.page : null,
|
||||
limit: state.getDataListIsPage ? state.limit : null,
|
||||
...state.dataForm
|
||||
})
|
||||
.then((res) => {
|
||||
state.dataListLoading = false;
|
||||
state.dataList = state.getDataListIsPage ? res.data.list : res.data;
|
||||
state.total = state.getDataListIsPage ? res.data.total : 0;
|
||||
})
|
||||
.catch(() => {
|
||||
state.dataListLoading = false;
|
||||
});
|
||||
},
|
||||
// 多选
|
||||
dataListSelectionChangeHandle(val: IObject[]) {
|
||||
state.dataListSelections = val;
|
||||
},
|
||||
// 排序
|
||||
dataListSortChangeHandle(data: IObject) {
|
||||
if (!data.order || !data.prop) {
|
||||
state.order = "";
|
||||
state.orderField = "";
|
||||
return false;
|
||||
}
|
||||
state.order = data.order.replace(/ending$/, "");
|
||||
state.orderField = data.prop.replace(/([A-Z])/g, "_$1").toLowerCase();
|
||||
viewFns.query();
|
||||
},
|
||||
// 分页, 每页条数
|
||||
pageSizeChangeHandle(val: number) {
|
||||
state.page = 1;
|
||||
state.limit = val;
|
||||
viewFns.query();
|
||||
},
|
||||
// 分页, 当前页
|
||||
pageCurrentChangeHandle(val: number) {
|
||||
state.page = val;
|
||||
viewFns.query();
|
||||
},
|
||||
//搜索
|
||||
getDataList() {
|
||||
state.page = 1;
|
||||
viewFns.query();
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id?: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (
|
||||
state.deleteIsBatch &&
|
||||
!id &&
|
||||
state.dataListSelections &&
|
||||
state.dataListSelections.length <= 0
|
||||
) {
|
||||
ElMessage.warning({
|
||||
message: "请选择操作项",
|
||||
duration: 500
|
||||
});
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm("确定进行[删除]操作?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
baseService
|
||||
.delete(
|
||||
`${state.deleteURL}${state.deleteIsBatch ? "" : "/" + id}`,
|
||||
state.deleteIsBatch
|
||||
? id
|
||||
? [id]
|
||||
: state.dataListSelections
|
||||
? state.dataListSelections.map(
|
||||
(item: IObject) => state.deleteIsBatchKey && item[state.deleteIsBatchKey]
|
||||
)
|
||||
: {}
|
||||
: {}
|
||||
)
|
||||
.then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
viewFns.query();
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
});
|
||||
});
|
||||
},
|
||||
// 导出
|
||||
exportHandle() {
|
||||
window.location.href = `${app.api}${state.exportURL}?${qs.stringify({
|
||||
...state.dataForm,
|
||||
token: getToken()
|
||||
})}`;
|
||||
// baseService.download(state.exportURL, { ...state.dataForm, token: getToken() });
|
||||
},
|
||||
//关闭当前窗口
|
||||
closeCurrentTab() {
|
||||
if (getThemeConfigCacheByKey(EThemeSetting.OpenTabsPage)) {
|
||||
emits.emit(EMitt.OnCloseCurrTab);
|
||||
} else {
|
||||
router.replace("/home");
|
||||
}
|
||||
},
|
||||
// 处理流程路由
|
||||
handleFlowRoute(data: IObject) {
|
||||
const routeParams = {
|
||||
path: `/flow/task-form`,
|
||||
query: {
|
||||
taskId: data.taskId,
|
||||
processInstanceId: data.processInstanceId,
|
||||
processDefinitionId: data.processDefinitionId,
|
||||
showType: "taskHandle",
|
||||
_mt: `${route.meta.title} - ${data.processDefinitionName}`
|
||||
}
|
||||
};
|
||||
registerDynamicToRouterAndNext(routeParams);
|
||||
},
|
||||
// 查看流程详情
|
||||
flowDetailRoute(data: IObject) {
|
||||
const routeParams = {
|
||||
path: `/flow/task-form`,
|
||||
query: {
|
||||
taskId: data.taskId,
|
||||
processInstanceId: data.processInstanceId,
|
||||
processDefinitionId: data.processDefinitionId,
|
||||
showType: "detail",
|
||||
_mt: `${route.meta.title} - ${data.processDefinitionName}`
|
||||
}
|
||||
};
|
||||
registerDynamicToRouterAndNext(routeParams);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
return {
|
||||
...viewFns,
|
||||
...rejectFns
|
||||
};
|
||||
};
|
||||
|
||||
export default useView;
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
/**
|
||||
* 全屏布局
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "FullScreenLayout",
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
return { route };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="`rr-fullscreen ${route.query.pop ? 'new-pop-window' : ''}`">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import logo from "@/assets/images/logo.png";
|
||||
import { EMitt, ESidebarLayoutEnum, EThemeSetting } from "@/constants/enum";
|
||||
import emits from "@/utils/emits";
|
||||
import { getThemeConfigCacheByKey } from "@/utils/theme";
|
||||
import { defineComponent, reactive } from "vue";
|
||||
import { useAppStore } from "@/store";
|
||||
import BaseSidebar from "../sidebar/base-sidebar.vue";
|
||||
import Breadcrumb from "./breadcrumb.vue";
|
||||
import CollapseSidebarBtn from "./collapse-sidebar-btn.vue";
|
||||
import Expand from "./expand.vue";
|
||||
import HeaderMixNavMenus from "./header-mix-nav-menus.vue";
|
||||
import Logo from "./logo.vue";
|
||||
import "@/assets/css/header.less";
|
||||
|
||||
/**
|
||||
* 顶部主区域
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "Header",
|
||||
components: { BaseSidebar, Breadcrumb, CollapseSidebarBtn, Expand, HeaderMixNavMenus, Logo },
|
||||
setup() {
|
||||
const store = useAppStore();
|
||||
const state = reactive({
|
||||
sidebarLayout: getThemeConfigCacheByKey(EThemeSetting.NavLayout)
|
||||
});
|
||||
emits.on(EMitt.OnSetNavLayout, (vl) => {
|
||||
state.sidebarLayout = vl;
|
||||
});
|
||||
const onRefresh = () => {
|
||||
emits.emit(EMitt.OnReloadTabPage);
|
||||
};
|
||||
return { store, state, onRefresh, logo, ESidebarLayoutEnum };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="rr-header-ctx">
|
||||
<div class="rr-header-ctx-logo hidden-xs-only">
|
||||
<logo :logoUrl="logo" logoName="气象数据管理系统"></logo>
|
||||
</div>
|
||||
<div class="rr-header-right">
|
||||
<div class="rr-header-right-left">
|
||||
<div class="rr-header-right-items rr-header-action" :style="`display:${state.sidebarLayout === ESidebarLayoutEnum.Top ? 'none' : ''}`">
|
||||
<collapse-sidebar-btn></collapse-sidebar-btn>
|
||||
<div @click="onRefresh" style="cursor: pointer">
|
||||
<div class="el-badge">
|
||||
<el-icon><refresh-right /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rr-header-right-left-br ele-scrollbar-hide hidden-xs-only">
|
||||
<base-sidebar v-if="state.sidebarLayout === ESidebarLayoutEnum.Top" mode="horizontal" :router="true"></base-sidebar>
|
||||
<header-mix-nav-menus v-else-if="state.sidebarLayout === ESidebarLayoutEnum.Mix"></header-mix-nav-menus>
|
||||
<breadcrumb v-else></breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-shrink: 0">
|
||||
<expand :userName="store.state.user.username"></expand>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { IObject } from "@/types/interface";
|
||||
import { getValueByKeys } from "@/utils/utils";
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { RouteLocationMatched, useRouter } from "vue-router";
|
||||
|
||||
/**
|
||||
* 顶部面包屑
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "Breadcrumb",
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const breadcrumbs = ref<IObject[]>([]);
|
||||
const { currentRoute } = router;
|
||||
const firstRoute = (router.options.routes[0] || {}) as RouteLocationMatched;
|
||||
const home: RouteLocationMatched = firstRoute.children && firstRoute.children.length > 0 ? (firstRoute.children[0] as RouteLocationMatched) : firstRoute;
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
breadcrumbs.value = currentRoute.value.path !== home.path ? getValueByKeys(currentRoute.value, "meta.matched", []) : [];
|
||||
}
|
||||
);
|
||||
|
||||
return { breadcrumbs, currentRoute, home };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<el-breadcrumb separator="/" style="padding-top: 4px">
|
||||
<el-breadcrumb-item :to="{ path: home.path }"> 主页 </el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-for="x in breadcrumbs" :key="x.path">{{ currentRoute.query._mt || x.title || "" }} </el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import SvgIcon from "@/components/base/svg-icon";
|
||||
import { EMitt, EThemeSetting } from "@/constants/enum";
|
||||
import emits from "@/utils/emits";
|
||||
import { getThemeConfigCacheByKey, setThemeConfigToCache } from "@/utils/theme";
|
||||
import { defineComponent, reactive } from "vue";
|
||||
|
||||
/**
|
||||
* PC和移动端下的侧边栏展开收起按钮
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "CollapseSidebarBtn",
|
||||
components: { SvgIcon },
|
||||
setup() {
|
||||
const state = reactive({
|
||||
collapseSidebar: getThemeConfigCacheByKey(EThemeSetting.SidebarCollapse)
|
||||
});
|
||||
const onClickSidebarSwitcher = () => {
|
||||
const key = EThemeSetting.SidebarCollapse;
|
||||
state.collapseSidebar = !state.collapseSidebar;
|
||||
emits.emit(EMitt.OnSwitchLeftSidebar);
|
||||
emits.emit(EMitt.OnSetTheme, [key, key + "-" + state.collapseSidebar]);
|
||||
setThemeConfigToCache(key, state.collapseSidebar);
|
||||
};
|
||||
const onClickSidebarSwitcherByMobile = () => {
|
||||
emits.emit(EMitt.OnMobileOpenSidebar);
|
||||
};
|
||||
return { state, onClickSidebarSwitcher, onClickSidebarSwitcherByMobile };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="hidden-xs-only" @click="onClickSidebarSwitcher">
|
||||
<svg-icon :name="state.collapseSidebar ? 'indent' : 'outdent'"></svg-icon>
|
||||
</div>
|
||||
<div class="hidden-sm-and-up show-xs-only" @click="onClickSidebarSwitcherByMobile">
|
||||
<svg-icon name="icon-indent"></svg-icon>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import SvgIcon from "@/components/base/svg-icon";
|
||||
import baseService from "@/service/baseService";
|
||||
import { useFullscreen } from "@vueuse/core";
|
||||
import { defineComponent } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import userLogo from "@/assets/images/user.png";
|
||||
import "@/assets/css/header.less";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
interface IExpand {
|
||||
userName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶部右侧扩展区域
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "Expand",
|
||||
components: { SvgIcon },
|
||||
props: {
|
||||
userName: String
|
||||
},
|
||||
setup(props: IExpand) {
|
||||
const router = useRouter();
|
||||
const store = useAppStore();
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
|
||||
const onClickUserMenus = (path: string) => {
|
||||
if (path === "/login") {
|
||||
ElMessageBox.confirm("确定进行[退出]操作?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
baseService.post("/logout").finally(() => {
|
||||
router.push(path);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
});
|
||||
} else {
|
||||
router.push(path);
|
||||
}
|
||||
};
|
||||
return {
|
||||
props,
|
||||
store,
|
||||
isFullscreen,
|
||||
userLogo,
|
||||
onClickUserMenus,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="rr-header-right-items">
|
||||
<div @click="toggle" class="hidden-xs-only">
|
||||
<span>
|
||||
<svg-icon :name="isFullscreen ? 'tuichuquanping' : 'fullscreen2'"></svg-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center; align-items: center">
|
||||
<img :src="userLogo" :alt="props.userName" style="width: 30px; height: 30px; border-radius: 50%; margin-top: 3px; margin-right: 5px" />
|
||||
<el-dropdown @command="onClickUserMenus">
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="lock" command="/user/password"> 修改密码 </el-dropdown-item>
|
||||
<el-dropdown-item icon="switch-button" divided command="/login"> 退出登录 </el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
<span class="el-dropdown-link" style="display: flex">
|
||||
{{ props.userName }}
|
||||
<el-icon class="el-icon--right" style="font-size: 14px"><arrow-down /></el-icon>
|
||||
</span>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { EMitt, ESidebarLayoutEnum, EThemeSetting } from "@/constants/enum";
|
||||
import emits from "@/utils/emits";
|
||||
import { getThemeConfigCacheByKey } from "@/utils/theme";
|
||||
import { getValueByKeys } from "@/utils/utils";
|
||||
import { computed, defineComponent, reactive, watch } from "vue";
|
||||
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import BaseSidebar from "../sidebar/base-sidebar.vue";
|
||||
|
||||
/**
|
||||
* 顶部导航菜单,混合布局模式下用到
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "HeaderMixNavMenus",
|
||||
components: { BaseSidebar },
|
||||
setup() {
|
||||
const store = useAppStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const routers = router.options.routes;
|
||||
const state = reactive({
|
||||
currRoute: getValueByKeys(getValueByKeys(router.currentRoute.value.meta, "matched", [])[0], "path", "")
|
||||
});
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
if (getThemeConfigCacheByKey(EThemeSetting.NavLayout) === ESidebarLayoutEnum.Mix) {
|
||||
const matchedRoute = getValueByKeys(getValueByKeys(router.currentRoute.value.meta, "matched", [])[0], "path", "");
|
||||
if (matchedRoute) {
|
||||
state.currRoute = matchedRoute;
|
||||
emits.emit(EMitt.OnSelectHeaderNavMenusByMixNav, matchedRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
const topHeaderMenus = computed(() => {
|
||||
const rs: any[] = [];
|
||||
store.state.routes.forEach((x: RouteRecordRaw) => {
|
||||
rs.push({
|
||||
path: x.path,
|
||||
children: [],
|
||||
meta: x.meta ? x.meta : {}
|
||||
});
|
||||
});
|
||||
return rs;
|
||||
});
|
||||
const onSelect = (path: string) => {
|
||||
const curr = routers.find((x: RouteRecordRaw) => x.path === path);
|
||||
|
||||
if (!curr?.children?.length) {
|
||||
router.push(path);
|
||||
} else {
|
||||
state.currRoute = path;
|
||||
emits.emit(EMitt.OnSelectHeaderNavMenusByMixNav, path);
|
||||
}
|
||||
};
|
||||
return { state, topHeaderMenus, onSelect };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<base-sidebar mode="horizontal" :menus="topHeaderMenus" :router="false" :currRoute="state.currRoute" :is-mobile="false" :onSelect="onSelect"></base-sidebar>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import app from "@/constants/app";
|
||||
import { defineComponent } from "vue";
|
||||
interface ILogo {
|
||||
logoUrl?: string;
|
||||
logoName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶部logo
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "Logo",
|
||||
props: {
|
||||
logoUrl: String,
|
||||
logoName: {
|
||||
type: String,
|
||||
default: "logo"
|
||||
}
|
||||
},
|
||||
setup(props: ILogo) {
|
||||
return { props, app };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="`rr-header-ctx-logo-img-wrap ${'enabled-logo-' + app.enabledLogo}`">
|
||||
<!-- 支持显示图片logo或者产品名称缩写,二选一模式,通过注释开启功能,app.enabledLogo控制正常模式下图片logo是否显示,如果有图片logo,收起状态会强制显示图片logo -->
|
||||
<!-- <img :src="props.logoUrl" class="rr-header-ctx-logo-img" :alt="props.logoName" /> -->
|
||||
<span>XX</span>
|
||||
<span class="rr-header-ctx-logo-line"></span>
|
||||
</span>
|
||||
<span class="rr-header-ctx-logo-text">{{ props.logoName }}</span>
|
||||
</template>
|
||||
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { EMitt, ESidebarLayoutEnum, EThemeSetting } from "@/constants/enum";
|
||||
import emits from "@/utils/emits";
|
||||
import { getThemeConfigCache, getThemeConfigCacheByKey, getThemeConfigToClass } from "@/utils/theme";
|
||||
import { getValueByKeys } from "@/utils/utils";
|
||||
import { useMediaQuery } from "@vueuse/core";
|
||||
import { computed, defineComponent, reactive } from "vue";
|
||||
import { RouteRecordRaw, useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import BaseHeader from "./header/base-header.vue";
|
||||
import BaseSidebar from "./sidebar/base-sidebar.vue";
|
||||
import MobileSidebar from "./sidebar/mobile-sidebar.vue";
|
||||
import BaseView from "./view/base-view.vue";
|
||||
|
||||
/**
|
||||
* 多标签页布局
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "Layout",
|
||||
components: { BaseView, BaseHeader, BaseSidebar, MobileSidebar },
|
||||
setup() {
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const themeCache = getThemeConfigCache();
|
||||
const sidebarLayoutCache = getThemeConfigCacheByKey(EThemeSetting.NavLayout, themeCache);
|
||||
const router = useRouter();
|
||||
const store = useAppStore();
|
||||
const state = reactive({
|
||||
isShowNav: sidebarLayoutCache !== ESidebarLayoutEnum.Top,
|
||||
sidebarLayout: sidebarLayoutCache,
|
||||
themeClass: getThemeConfigToClass(themeCache),
|
||||
loading: false,
|
||||
mixLayoutRoutes: router.options.routes.find((x: RouteRecordRaw) => x.path === "/")?.children ?? ([] as RouteRecordRaw[])
|
||||
});
|
||||
const containerClassNames = computed(() =>
|
||||
Object.values(state.themeClass)
|
||||
.concat(isMobile.value ? ["ui-mobile"] : [])
|
||||
.join(" ")
|
||||
);
|
||||
emits.on(EMitt.OnSelectHeaderNavMenusByMixNav, (path) => {
|
||||
state.mixLayoutRoutes = store.state.routes.find((x: RouteRecordRaw) => x.path === path)?.children ?? [];
|
||||
});
|
||||
emits.on(EMitt.OnSetTheme, ([type, value]) => {
|
||||
state.themeClass[type] = "ui-" + value;
|
||||
});
|
||||
emits.on(EMitt.OnSetNavLayout, (vl) => {
|
||||
state.sidebarLayout = vl;
|
||||
state.isShowNav = vl !== ESidebarLayoutEnum.Top;
|
||||
if (vl === ESidebarLayoutEnum.Mix) {
|
||||
const currRoute = getValueByKeys(getValueByKeys(router.currentRoute.value.meta, "matched", [])[0], "path", "");
|
||||
state.mixLayoutRoutes = store.state.routes.find((x: RouteRecordRaw) => x.path === currRoute)?.children ?? [];
|
||||
}
|
||||
});
|
||||
emits.on(EMitt.OnLoading, (vl) => {
|
||||
state.loading = vl;
|
||||
});
|
||||
return { state, ESidebarLayoutEnum, containerClassNames };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<el-container :class="`rr ${containerClassNames}`" v-loading="state.loading" element-loading-background="#0000" element-loading-lock="true" element-loading-custom-class="rr-loading">
|
||||
<el-header class="rr-header" height="50px">
|
||||
<base-header></base-header>
|
||||
</el-header>
|
||||
<el-container class="rr-body">
|
||||
<el-aside v-if="state.isShowNav" class="rr-sidebar hidden-xs-only" width="auto">
|
||||
<base-sidebar v-if="state.sidebarLayout === ESidebarLayoutEnum.Left" :router="true" mode="vertical" :is-mobile="false"></base-sidebar>
|
||||
<base-sidebar v-else :menus="state.mixLayoutRoutes" :router="true" mode="vertical" :is-mobile="false"></base-sidebar>
|
||||
</el-aside>
|
||||
<div class="rr-sidebar rr-sidebar-mobile hidden-sm-and-up show-xs-only">
|
||||
<mobile-sidebar></mobile-sidebar>
|
||||
</div>
|
||||
<el-container class="rr-view-container">
|
||||
<el-main class="rr-view">
|
||||
<base-view></base-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import { themeSetting } from "@/constants/config";
|
||||
import { EMitt, EThemeSetting } from "@/constants/enum";
|
||||
import { IObject } from "@/types/interface";
|
||||
import Layout from "@/layout/layout.vue";
|
||||
import emits from "@/utils/emits";
|
||||
import { toValidRoutes } from "@/utils/router";
|
||||
import { getThemeConfigCacheByKey } from "@/utils/theme";
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
import { defineComponent, onMounted, reactive, ref, watch } from "vue";
|
||||
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import SidebarMenusItems from "./sidebar-menus-items.vue";
|
||||
import { getValueByKeys } from "@/utils/utils";
|
||||
|
||||
/**
|
||||
* 侧边栏导航菜单
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "BaseSidebar",
|
||||
components: { SidebarMenusItems },
|
||||
props: {
|
||||
mode: { type: String, default: "vertical" },
|
||||
menus: Array,
|
||||
currRoute: String,
|
||||
router: Boolean,
|
||||
onSelect: Function,
|
||||
isMobile: Boolean
|
||||
},
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const win = useWindowSize();
|
||||
const store = useAppStore();
|
||||
const defaultMenus = toValidRoutes((props.menus ?? store.state.routes) as RouteRecordRaw[]);
|
||||
const getPopClassName = () => {
|
||||
const sidebarCache = getThemeConfigCacheByKey(EThemeSetting.Sidebar);
|
||||
return `rr-sidebar-menu-pop-${props.mode === "vertical" && sidebarCache === "dark" ? "dark" : "light"}`;
|
||||
};
|
||||
const state = reactive({
|
||||
collapseSidebar: getThemeConfigCacheByKey(EThemeSetting.SidebarCollapse),
|
||||
uniqueOpened: themeSetting.sidebarUniOpened,
|
||||
windowWidth: win.width || 800,
|
||||
hiddenIndex: -1,
|
||||
rawMenus: defaultMenus,
|
||||
menus: defaultMenus,
|
||||
popClassName: getPopClassName(),
|
||||
currRoute: props.currRoute ?? route.path
|
||||
});
|
||||
const elm = ref({} as IObject);
|
||||
const li = ref({
|
||||
widths: [] as number[]
|
||||
});
|
||||
const initComputeSidebarLayout = (width: number) => {
|
||||
if (props.mode === "horizontal") {
|
||||
//存储水平布局元素信息
|
||||
const el = elm.value.$el;
|
||||
const lis = el.querySelectorAll("li");
|
||||
li.value.widths = [];
|
||||
lis.forEach((x: Element) => {
|
||||
li.value.widths.push(x.getBoundingClientRect().width);
|
||||
});
|
||||
computeSidebarLayout(width);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
onMounted(() => {
|
||||
initComputeSidebarLayout(state.windowWidth);
|
||||
});
|
||||
watch(
|
||||
() => props.menus,
|
||||
(vl) => {
|
||||
const ms = toValidRoutes((vl ? vl : store.state.routes) as RouteRecordRaw[]);
|
||||
state.menus = ms;
|
||||
state.rawMenus = ms;
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => store.state.routes,
|
||||
(vl) => {
|
||||
const ms = toValidRoutes(vl as RouteRecordRaw[]);
|
||||
state.rawMenus = ms;
|
||||
state.menus = ms;
|
||||
}
|
||||
);
|
||||
emits.on(EMitt.OnSwitchLeftSidebar, () => {
|
||||
state.collapseSidebar = !state.collapseSidebar;
|
||||
});
|
||||
emits.on(EMitt.OnSetThemeNotUniqueOpened, (vl) => {
|
||||
state.uniqueOpened = vl;
|
||||
});
|
||||
emits.on(EMitt.OnSetTheme, ([vl]) => {
|
||||
if (vl === EThemeSetting.Sidebar) {
|
||||
state.popClassName = getPopClassName();
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => route.path,
|
||||
(vl) => {
|
||||
const matchedRoute = getValueByKeys(getValueByKeys(router.currentRoute.value.meta, "matched", [])[0], "path", "");
|
||||
if (!route.query.pop && matchedRoute) {
|
||||
setTimeout(() => {
|
||||
state.currRoute = vl;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => state.windowWidth,
|
||||
(vl) => {
|
||||
computeSidebarLayout(vl);
|
||||
}
|
||||
);
|
||||
|
||||
const computeSidebarLayout = (windowWidth: number) => {
|
||||
if (props.mode === "horizontal" && windowWidth > 768 && elm.value.$el) {
|
||||
//菜单水平方向菜单过长,采用折叠效果
|
||||
const width = elm.value.$el.parentNode.getBoundingClientRect().width;
|
||||
let liWidth = 0;
|
||||
let index = -1;
|
||||
for (let i = 0; i < li.value.widths.length; i++) {
|
||||
liWidth += li.value.widths[i];
|
||||
if (liWidth > width) {
|
||||
index = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
state.hiddenIndex = index;
|
||||
state.menus =
|
||||
index > -1
|
||||
? state.rawMenus.slice(0, index).concat({
|
||||
path: "/__more",
|
||||
component: Layout,
|
||||
meta: { title: "更多菜单", icon: false, isMore: true },
|
||||
children: state.rawMenus.slice(index)
|
||||
})
|
||||
: state.rawMenus;
|
||||
}
|
||||
};
|
||||
|
||||
return { elm, props, state };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu
|
||||
ref="elm"
|
||||
:default-active="props.currRoute ?? state.currRoute"
|
||||
:mode="props.mode"
|
||||
:collapse="props.isMobile ? false : props.mode === 'vertical' && state.collapseSidebar"
|
||||
:router="props.router"
|
||||
:unique-opened="state.uniqueOpened"
|
||||
:onSelect="props.onSelect"
|
||||
:collapse-transition="false"
|
||||
class="rr-sidebar-menu"
|
||||
>
|
||||
<sidebar-menus-items :className="state.popClassName" :menus="state.menus" :hiddenIndex="state.hiddenIndex"></sidebar-menus-items>
|
||||
</el-menu>
|
||||
</template>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { EMitt } from "@/constants/enum";
|
||||
import emits from "@/utils/emits";
|
||||
import { defineComponent, reactive } from "vue";
|
||||
import BaseSidebar from "./base-sidebar.vue";
|
||||
import Logo from "../header/logo.vue";
|
||||
import logoUrl from "@/assets/images/logo.png";
|
||||
|
||||
/**
|
||||
* 移动端侧边栏菜单
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "MobileSidebar",
|
||||
components: { BaseSidebar, Logo },
|
||||
setup() {
|
||||
const state = reactive({
|
||||
show: true
|
||||
});
|
||||
emits.on(EMitt.OnMobileOpenSidebar, () => {
|
||||
state.show = true;
|
||||
});
|
||||
const onSelect = () => {
|
||||
state.show = false;
|
||||
};
|
||||
return { state, onSelect, logoUrl };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer v-model="state.show" :append-to-body="false" size="230" :withHeader="false" direction="ltr" class="rr-setting-wrap">
|
||||
<div class="rr-header-ctx-logo rr-header-ctx-logo-mobile">
|
||||
<logo :logoUrl="logoUrl" logoName="人人开源"></logo>
|
||||
</div>
|
||||
<div class="rr-sidebar-mobile-inner" style="overflow: auto; height: calc(100vh - 50px); width: initial !important">
|
||||
<base-sidebar :router="true" mode="vertical" :isMobile="true" :onSelect="onSelect"></base-sidebar>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import classNames from "classnames";
|
||||
import SvgIcon from "@/components/base/svg-icon";
|
||||
import { RouteRecordRaw } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SidebarMenusItems",
|
||||
components: { SvgIcon },
|
||||
props: {
|
||||
menus: Array as PropType<RouteRecordRaw[]>,
|
||||
hiddenIndex: Number,
|
||||
className: String
|
||||
},
|
||||
setup(props) {
|
||||
const getStyle = (index: number): string => {
|
||||
const styles: Array<any> = [];
|
||||
const isHidden = props.hiddenIndex ? props.hiddenIndex > -1 && index > props.hiddenIndex : false;
|
||||
styles.push("display:" + (isHidden ? "none" : "block"));
|
||||
return styles.join(";");
|
||||
};
|
||||
return { props, classNames, getStyle };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<template v-for="(x, index) in props.menus || []" :key="x.path">
|
||||
<el-sub-menu v-if="x.children && x.children.length > 0" :index="x.path" :popper-class="props.className" :class="classNames({ isMore: x.meta?.isMore })" :style="getStyle(index)">
|
||||
<template #title>
|
||||
<el-icon v-if="x.meta?.icon !== false">
|
||||
<svg-icon :name="`${x.meta?.icon || 'icon-file-fill'}`"></svg-icon>
|
||||
</el-icon>
|
||||
<span>
|
||||
<a>{{ x.meta?.title }}</a>
|
||||
</span>
|
||||
</template>
|
||||
<sidebar-menus-items :menus="x.children"></sidebar-menus-items>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else :index="x.meta?.isNewPage ? x.path : x.path" :class="classNames({ isLink: !!x.meta?.isNewPage, isMore: x.meta?.isMore })" :style="getStyle(index)">
|
||||
<template #title>
|
||||
<a v-if="x.meta?.isNewPage" :href="`${x.meta.url}`" target="_blank" rel="opener">
|
||||
{{ x.meta.title }}
|
||||
</a>
|
||||
<a v-else>{{ x.meta?.title }}</a>
|
||||
</template>
|
||||
<el-icon v-if="x.meta?.icon !== false">
|
||||
<svg-icon :name="`${x.meta?.icon || 'icon-file-fill'}`"></svg-icon>
|
||||
</el-icon>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import app from "@/constants/app";
|
||||
import { EMitt, EThemeSetting } from "@/constants/enum";
|
||||
import emits from "@/utils/emits";
|
||||
import { getThemeConfigCacheByKey } from "@/utils/theme";
|
||||
import { defineComponent, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
import Tabs from "./tabs.vue";
|
||||
|
||||
/**
|
||||
* 业务内容视图框架
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "View",
|
||||
components: { Tabs },
|
||||
setup() {
|
||||
const store = useAppStore();
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
openTabsPage: getThemeConfigCacheByKey(EThemeSetting.OpenTabsPage)
|
||||
});
|
||||
const routerKeys = ref({} as any);
|
||||
emits.on(EMitt.OnSetThemeTabsPage, (vl) => {
|
||||
state.openTabsPage = vl;
|
||||
});
|
||||
emits.on(EMitt.OnReloadTabPage, () => {
|
||||
routerKeys.value[route.fullPath] = new Date().getTime();
|
||||
});
|
||||
return { state, store, enabledKeepAlive: app.enabledKeepAlive, routerKeys };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tabs v-if="state.openTabsPage" :tabs="store.state.tabs" :activeTabName="store.state.activeTabName"></tabs>
|
||||
<div class="rr-view-ctx">
|
||||
<el-card shadow="never" class="rr-view-ctx-card">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive v-if="enabledKeepAlive">
|
||||
<component :is="Component" :key="routerKeys[$route.fullPath] || $route.fullPath" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-if="!enabledKeepAlive" />
|
||||
</router-view>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,155 @@
|
||||
<script lang="ts">
|
||||
import SvgIcon from "@/components/base/svg-icon";
|
||||
import { EMitt } from "@/constants/enum";
|
||||
import { IObject } from "@/types/interface";
|
||||
import emits from "@/utils/emits";
|
||||
import { arrayToObject } from "@/utils/utils";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { findIndex } from "lodash";
|
||||
import { defineComponent, reactive, watch } from "vue";
|
||||
import { RouteLocationMatched, useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/store";
|
||||
|
||||
/**
|
||||
* tab标签页
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: "Tabs",
|
||||
components: { SvgIcon },
|
||||
props: {
|
||||
tabs: Array,
|
||||
activeTabName: String
|
||||
},
|
||||
setup(props) {
|
||||
const ops = [
|
||||
{ label: "关闭当前标签页", value: 5, icon: "close" },
|
||||
{ label: "关闭其他标签页", value: 1, icon: "close" },
|
||||
{ label: "关闭全部标签页", value: 4, icon: "circle-close" }
|
||||
];
|
||||
const router = useRouter();
|
||||
const store = useAppStore();
|
||||
const firstRoute = (router.options.routes[0] || {}) as RouteLocationMatched;
|
||||
const home: RouteLocationMatched = firstRoute.children && firstRoute.children.length > 0 ? (firstRoute.children[0] as RouteLocationMatched) : firstRoute;
|
||||
const defaultTab = { label: "", value: home.path };
|
||||
const state = reactive({
|
||||
activeTabName: props.activeTabName || defaultTab.value,
|
||||
tabs: (props.tabs && props.tabs.length ? props.tabs : [defaultTab]) as IObject[]
|
||||
});
|
||||
watch(
|
||||
() => state.tabs,
|
||||
(res) => {
|
||||
store.updateState({ tabs: res });
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
emits.on(EMitt.OnPushMenuToTabs, (route) => {
|
||||
const path: string = route.value;
|
||||
if (path.includes("/error")) {
|
||||
return;
|
||||
}
|
||||
const tabKeys: IObject<number> = arrayToObject(state.tabs, "value", () => 1);
|
||||
if (!tabKeys[path]) {
|
||||
state.tabs.push(route);
|
||||
}
|
||||
if (state.activeTabName !== path) {
|
||||
state.activeTabName = path;
|
||||
}
|
||||
});
|
||||
emits.on(EMitt.OnCloseCurrTab, () => {
|
||||
onClose(5);
|
||||
});
|
||||
const onTabClick = (tab: any) => {
|
||||
tab.props.name && router.push(tab.props.name);
|
||||
};
|
||||
const onTabRemove = (targetName: string) => {
|
||||
const index = findIndex(state.tabs, (x) => x.value === targetName);
|
||||
if (state.tabs.length > 1) {
|
||||
updateClosedTabs([...store.state.closedTabs, targetName], false);
|
||||
if (state.activeTabName === targetName) {
|
||||
const toIndex = index === 0 ? index + 1 : index - 1;
|
||||
state.activeTabName = state.tabs[toIndex].value;
|
||||
router.push(state.activeTabName);
|
||||
}
|
||||
state.tabs.splice(index, 1);
|
||||
} else {
|
||||
ElMessage({ type: "error", message: "只剩下一个标签页,不支持关闭", offset: 0 });
|
||||
}
|
||||
};
|
||||
const updateClosedTabs = (closedTabs: any[], isTransform = true) => {
|
||||
if (isTransform) {
|
||||
closedTabs = closedTabs.map((x) => x.value);
|
||||
}
|
||||
store.updateState({ closedTabs });
|
||||
};
|
||||
const onClose = (value: number) => {
|
||||
let index = null;
|
||||
const rawTabs = state.tabs;
|
||||
switch (value) {
|
||||
case 1:
|
||||
//其他
|
||||
state.tabs = state.tabs.filter((x) => [home.path, state.activeTabName].includes(x.value));
|
||||
updateClosedTabs(rawTabs.filter((x) => ![home.path, state.activeTabName].includes(x.value)));
|
||||
break;
|
||||
case 2:
|
||||
//右侧
|
||||
index = findIndex(state.tabs, (x) => x.value === state.activeTabName);
|
||||
state.tabs.splice(index + 1, state.tabs.length - (index + 1));
|
||||
updateClosedTabs(rawTabs.slice(index + 1));
|
||||
break;
|
||||
case 3:
|
||||
//左侧
|
||||
index = findIndex(state.tabs, (x) => x.value === state.activeTabName);
|
||||
state.tabs.splice(1, index - 1);
|
||||
updateClosedTabs(rawTabs.slice(1, index - 1));
|
||||
break;
|
||||
case 4:
|
||||
//全部
|
||||
state.tabs = [defaultTab];
|
||||
state.activeTabName = defaultTab.value;
|
||||
updateClosedTabs(rawTabs);
|
||||
router.push(state.activeTabName);
|
||||
break;
|
||||
case 5:
|
||||
//当前
|
||||
if (state.activeTabName !== defaultTab.value) {
|
||||
updateClosedTabs([...store.state.closedTabs, state.activeTabName], false);
|
||||
index = findIndex(state.tabs, (x) => x.value === state.activeTabName);
|
||||
state.tabs.splice(index, 1);
|
||||
state.activeTabName = state.tabs[state.tabs.length - 1].value;
|
||||
router.push(state.activeTabName);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
return { state, onTabClick, onTabRemove, home, onClose, ops };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="rr-view-tab-wrap">
|
||||
<el-tabs class="rr-view-tab" v-model="state.activeTabName" @tab-click="onTabClick" @tab-remove="onTabRemove">
|
||||
<el-tab-pane :name="home.path" :closable="false">
|
||||
<template #label>
|
||||
<!-- 文字主页和图标主页tab -->
|
||||
<!-- {{ t("ui.router.pageHome") }} -->
|
||||
<svg-icon name="home"></svg-icon>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-for="x in state.tabs.slice(1)" :key="x.value" :label="x.label" :name="x.value" :closable="true"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-dropdown trigger="click" placement="bottom-end" class="rr-view-tab-ops" @command="onClose">
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="x in ops" :key="x.value" :icon="x.icon" :command="x.value">
|
||||
{{ x.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
<span class="el-dropdown-link">
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</span>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
import "@/assets/icons/iconfont/iconfont.js";
|
||||
import RenDeptTree from "@/components/ren-dept-tree";
|
||||
import RenRadioGroup from "@/components/ren-radio-group";
|
||||
import RenRegionTree from "@/components/ren-region-tree";
|
||||
import RenSelect from "@/components/ren-select";
|
||||
import ElementPlus from "element-plus";
|
||||
import "element-plus/theme-chalk/display.css";
|
||||
import "element-plus/theme-chalk/index.css";
|
||||
import locale from "element-plus/es/locale/lang/zh-cn";
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import * as ElementPlusIcons from "@element-plus/icons-vue";
|
||||
|
||||
import axios from "axios";
|
||||
import "virtual:svg-icons-register";
|
||||
|
||||
const app = createApp(App);
|
||||
Object.keys(ElementPlusIcons).forEach((iconName) => {
|
||||
app.component(iconName, ElementPlusIcons[iconName as keyof typeof ElementPlusIcons]);
|
||||
});
|
||||
|
||||
app
|
||||
.use(createPinia())
|
||||
.use(router)
|
||||
.use(RenRadioGroup)
|
||||
.use(RenSelect)
|
||||
.use(RenDeptTree)
|
||||
.use(RenRegionTree)
|
||||
.use(ElementPlus, { size: "default", locale: locale })
|
||||
.mount("#app");
|
||||
|
||||
window.axios = axios;
|
||||
@@ -0,0 +1,52 @@
|
||||
import Layout from "@/layout/layout.vue";
|
||||
import Error from "@/views/error.vue";
|
||||
import { RouteRecordRaw } from "vue-router";
|
||||
import Login from "@/views/login.vue";
|
||||
import Iframe from "@/views/iframe.vue";
|
||||
|
||||
/**
|
||||
* 框架基础路由
|
||||
*/
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
component: Layout,
|
||||
redirect: "/home",
|
||||
meta: { title: "工作台", icon: "icon-desktop" },
|
||||
children: [
|
||||
{
|
||||
path: "/home",
|
||||
component: () => import("@/views/home.vue"),
|
||||
meta: { title: "主页", icon: "icon-home" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: Login,
|
||||
meta: { title: "登录", isNavigationMenu: false }
|
||||
},
|
||||
{
|
||||
path: "/user/password",
|
||||
component: () => import("@/views/sys/user-update-password.vue"),
|
||||
meta: { title: "修改密码", requiresAuth: true, isNavigationMenu: false }
|
||||
},
|
||||
{
|
||||
path: "/iframe/:id?",
|
||||
component: Iframe,
|
||||
meta: { title: "iframe", isNavigationMenu: false }
|
||||
},
|
||||
{
|
||||
path: "/error",
|
||||
name: "error",
|
||||
component: Error,
|
||||
meta: { title: "错误页面", isNavigationMenu: false }
|
||||
},
|
||||
{
|
||||
path: "/:path(.*)*",
|
||||
redirect: { path: "/error", query: { to: 404 }, replace: true },
|
||||
meta: { isNavigationMenu: false }
|
||||
}
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,187 @@
|
||||
import { IObject } from "@/types/interface";
|
||||
import { useAppStore } from "@/store";
|
||||
import { getToken } from "@/utils/cache";
|
||||
import { getBaseRouteToMeta, registerToRouter } from "@/utils/router";
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
RouteLocationNormalized,
|
||||
RouteRecordRaw
|
||||
} from "vue-router";
|
||||
import baseRoutes from "./base";
|
||||
import emits from "@/utils/emits";
|
||||
import { EMitt } from "@/constants/enum";
|
||||
|
||||
interface dynamicRouteParams {
|
||||
path: string;
|
||||
query?: IObject;
|
||||
mete?: IObject;
|
||||
}
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(), //createWebHashHistory() hash模式
|
||||
routes: baseRoutes
|
||||
});
|
||||
|
||||
// 路由加载前
|
||||
router.beforeEach((to, from, next) => {
|
||||
//外链
|
||||
if (to.meta.isNewPage) {
|
||||
if (to.query.pop !== "true") {
|
||||
next(undefined);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const store = useAppStore();
|
||||
|
||||
//token
|
||||
const token = getToken();
|
||||
const isPop = to.query.pop === "true"; //新窗口打开内页
|
||||
NProgress.start();
|
||||
if (to.path !== "/login") {
|
||||
if (store.state.routes.length) {
|
||||
if (to.name === "error") {
|
||||
const isMatched = autoRegisterDynamicToRouterAndNext(to);
|
||||
if (!isMatched) {
|
||||
store.updateState({ appIsRender: true, appIsReady: true });
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
if (!to.query.pop) {
|
||||
const routeMeta: IObject = store.state.routeToMeta[to.path];
|
||||
emits.emit(EMitt.OnPushMenuToTabs, {
|
||||
label: to.query._mt || routeMeta.title || to.path,
|
||||
value: to.fullPath,
|
||||
mete: routeMeta
|
||||
});
|
||||
}
|
||||
store.updateState({ appIsRender: true, appIsReady: true });
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
if (token) {
|
||||
store.initApp().then((res: Array<RouteRecordRaw>) => {
|
||||
const mergeRoute = baseRoutes.concat(res);
|
||||
router.options.routes = mergeRoute;
|
||||
registerToRouter(router, mergeRoute);
|
||||
if (!to.matched.length) {
|
||||
registerDynamicToRouterAndNext({ path: to.path, query: to.query });
|
||||
}
|
||||
store.updateState({
|
||||
appIsReady: true,
|
||||
routes: mergeRoute,
|
||||
routeToMeta: { ...store.state.routeToMeta, ...getBaseRouteToMeta(baseRoutes) }
|
||||
});
|
||||
setTimeout(() => {
|
||||
store.updateState({ appIsRender: true, appIsLogin: true });
|
||||
}, 600);
|
||||
next({ ...to, replace: true });
|
||||
});
|
||||
} else {
|
||||
if (isPop) {
|
||||
if (!to.matched.length) {
|
||||
registerDynamicToRouterAndNext({ path: to.path, query: to.query });
|
||||
store.updateState({ appIsRender: true, appIsReady: true });
|
||||
next(to.fullPath);
|
||||
} else {
|
||||
store.updateState({ appIsRender: true, appIsReady: true });
|
||||
if (to.meta.requiresAuth) {
|
||||
next("/login");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
store.updateState({ appIsReady: true, appIsRender: true });
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
// 路由加载后
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取系统视图路径映射
|
||||
* @returns
|
||||
*/
|
||||
export const getSysRouteMap = (): IObject => {
|
||||
return import.meta.glob("/src/views/**/*.vue");
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据路由path转换为系统视图组件路径
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
export const toSysViewComponentPath = (path: string): string => {
|
||||
path = path.replace("_", "-");
|
||||
return `/src/views${path}.vue`;
|
||||
};
|
||||
/**
|
||||
* 自动注册路由
|
||||
* @param to
|
||||
* @returns
|
||||
*/
|
||||
const autoRegisterDynamicToRouterAndNext = (to: RouteLocationNormalized): boolean => {
|
||||
if (to.redirectedFrom) {
|
||||
const path = to.redirectedFrom.path;
|
||||
const component = matchedSysRouteComponent(path);
|
||||
if (component) {
|
||||
registerToRouter(router, [
|
||||
{
|
||||
path: path,
|
||||
name: path,
|
||||
component,
|
||||
redirect: ""
|
||||
}
|
||||
]);
|
||||
router.push(to.redirectedFrom);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* 寻找视图组件
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
const matchedSysRouteComponent = (path: string): any => {
|
||||
const sysRouteMap = getSysRouteMap();
|
||||
const component = sysRouteMap[toSysViewComponentPath(path)];
|
||||
if (!component) {
|
||||
console.error("实时注册动态路由失败,未找到组件路径", path);
|
||||
}
|
||||
return component;
|
||||
};
|
||||
|
||||
/**
|
||||
* 实时注册动态路由并直接跳转过去
|
||||
* @param route
|
||||
*/
|
||||
export const registerDynamicToRouterAndNext = (route: dynamicRouteParams): void => {
|
||||
const component = matchedSysRouteComponent(route.path);
|
||||
const newRoute: RouteRecordRaw = {
|
||||
path: route.path,
|
||||
name: route.path,
|
||||
component,
|
||||
redirect: !component ? { path: "/error", query: { to: 404 }, replace: true } : ""
|
||||
};
|
||||
registerToRouter(router, [newRoute]);
|
||||
router.push(route);
|
||||
};
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { IHttpResponse, IObject } from "@/types/interface";
|
||||
import http from "../utils/http";
|
||||
|
||||
/**
|
||||
* 常用CRUD
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* 删除
|
||||
* @param path
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
delete(path: string, params: IObject): Promise<IHttpResponse> {
|
||||
return http({
|
||||
url: path,
|
||||
data: params,
|
||||
method: "DELETE"
|
||||
});
|
||||
},
|
||||
get(path: string, params?: IObject, headers?: IObject): Promise<IHttpResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
http({
|
||||
url: path,
|
||||
params,
|
||||
headers,
|
||||
method: "GET"
|
||||
})
|
||||
.then(resolve)
|
||||
.catch((error) => {
|
||||
if (error !== "-999") {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
put(path: string, params?: IObject, headers?: IObject): Promise<IHttpResponse> {
|
||||
return http({
|
||||
url: path,
|
||||
data: params,
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
...headers
|
||||
},
|
||||
method: "PUT"
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 通用post方法
|
||||
* @param path
|
||||
* @param body
|
||||
* @returns
|
||||
*/
|
||||
post(path: string, body?: IObject, headers?: IObject): Promise<IHttpResponse> {
|
||||
return http({
|
||||
url: path,
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
...headers
|
||||
},
|
||||
data: body
|
||||
});
|
||||
},
|
||||
// 在 baseService.ts 中添加
|
||||
upload(path: string, formData: FormData, headers?: IObject): Promise<IHttpResponse> {
|
||||
return http({
|
||||
url: path,
|
||||
method: "post",
|
||||
// 关键:不要在这里设置 Content-Type,让浏览器/Axios 自动处理
|
||||
headers: {
|
||||
...headers
|
||||
},
|
||||
data: formData
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { CacheToken } from "@/constants/cacheKey";
|
||||
import { IObject } from "@/types/interface";
|
||||
import { getSysRouteMap } from "@/router";
|
||||
import baseService from "@/service/baseService";
|
||||
import { removeCache } from "@/utils/cache";
|
||||
import { mergeServerRoute } from "@/utils/router";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useAppStore = defineStore("useAppStore", {
|
||||
state: () => ({
|
||||
state: {
|
||||
appIsLogin: false, //是否登录
|
||||
appIsReady: false, //app数据是否就绪
|
||||
appIsRender: false, //app是否开始渲染内容
|
||||
permissions: [], //权限集合
|
||||
user: {
|
||||
createDate: "",
|
||||
deptId: "",
|
||||
deptName: "",
|
||||
email: "",
|
||||
gender: 0,
|
||||
headUrl: "",
|
||||
id: "",
|
||||
mobile: "",
|
||||
postIdList: "",
|
||||
realName: "",
|
||||
roleIdList: "",
|
||||
status: 0,
|
||||
superAdmin: 0,
|
||||
username: ""
|
||||
}, //用户信息
|
||||
dicts: [], //字典
|
||||
routes: [], //最终的路由集合
|
||||
menus: [], //菜单集合
|
||||
routeToMeta: {}, //url对应标题meta信息
|
||||
tabs: [], //tab标签页集合
|
||||
activeTabName: "", //tab当前焦点页
|
||||
closedTabs: [] //存储已经关闭过的tab
|
||||
} as IObject
|
||||
}),
|
||||
actions: {
|
||||
updateState(data: IObject) {
|
||||
Object.keys(data).forEach((x: string) => {
|
||||
this.state[x] = data[x];
|
||||
});
|
||||
},
|
||||
initApp() {
|
||||
return Promise.all([
|
||||
baseService.get("/sys/menu/nav"), //加载菜单
|
||||
baseService.get("/sys/menu/permissions"), //加载权限
|
||||
baseService.get("/sys/user/info"), //加载用户信息
|
||||
baseService.get("/sys/dict/type/all") //加载字典
|
||||
]).then(([menus, permissions, user, dicts]) => {
|
||||
if (user.code !== 0) {
|
||||
console.error("初始化用户数据错误", user.msg);
|
||||
}
|
||||
const [routes, routeToMeta] = mergeServerRoute(menus.data || [], getSysRouteMap());
|
||||
this.updateState({
|
||||
permissions: permissions.data || [],
|
||||
user: user.data || {},
|
||||
dicts: dicts.data || [],
|
||||
routeToMeta: routeToMeta || {},
|
||||
menus: []
|
||||
});
|
||||
return routes;
|
||||
});
|
||||
},
|
||||
//退出
|
||||
logout() {
|
||||
removeCache(CacheToken, true);
|
||||
this.updateState({
|
||||
appIsLogin: false,
|
||||
permissions: [],
|
||||
user: {},
|
||||
dicts: [],
|
||||
menus: [],
|
||||
routes: [],
|
||||
tabs: [],
|
||||
activeTabName: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
interface ImportMetaEnv {
|
||||
/**
|
||||
* api接口环境
|
||||
*/
|
||||
VITE_APP_API: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
declare interface Window {
|
||||
axios: any;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
export interface IFunction<T = any> {
|
||||
(x?: any): T;
|
||||
}
|
||||
|
||||
export interface IObject<T = any> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
export interface IHttpResponse {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单
|
||||
*/
|
||||
export interface IServerMenus {
|
||||
createDate: string;
|
||||
icon: string | boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
parentName: string;
|
||||
permissions: string;
|
||||
pid: string;
|
||||
sort: number;
|
||||
type: number;
|
||||
url: string;
|
||||
openStyle: number;
|
||||
redirect?: string;
|
||||
children?: IServerMenus[];
|
||||
}
|
||||
|
||||
export interface ICacheOptions {
|
||||
/**
|
||||
* 是否取值后立即删除缓存
|
||||
*/
|
||||
isDelete?: boolean;
|
||||
/**
|
||||
* 是否采用JSON格式化缓存值
|
||||
*/
|
||||
isParse?: boolean;
|
||||
/**
|
||||
* 是否采用会话缓存介质
|
||||
*/
|
||||
isSessionStorage?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewHooksOptions {
|
||||
// 设置属性
|
||||
/**
|
||||
* 此页面是否在创建时,调用查询数据列表接口?
|
||||
*/
|
||||
createdIsNeed?: boolean;
|
||||
/**
|
||||
* 此页面是否在激活(进入)时,调用查询数据列表接口?
|
||||
*/
|
||||
activatedIsNeed?: boolean;
|
||||
/**
|
||||
* 数据列表接口,API地址
|
||||
*/
|
||||
getDataListURL?: string;
|
||||
/**
|
||||
* 数据列表接口,是否需要分页?
|
||||
*/
|
||||
getDataListIsPage?: boolean;
|
||||
/**
|
||||
* 删除接口,API地址
|
||||
*/
|
||||
deleteURL?: "";
|
||||
/**
|
||||
* 删除接口,是否需要批量?
|
||||
*/
|
||||
deleteIsBatch?: boolean;
|
||||
/**
|
||||
* 删除接口,批量状态下由那个key进行标记操作?比如:pid,uid...
|
||||
*/
|
||||
deleteIsBatchKey?: string;
|
||||
/**
|
||||
* 导出接口,API地址
|
||||
*/
|
||||
exportURL?: string;
|
||||
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
dataForm?: IObject;
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
dataList?: IObject[];
|
||||
/**
|
||||
* 排序,asc/desc
|
||||
*/
|
||||
order?: string;
|
||||
/**
|
||||
* 排序,字段
|
||||
*/
|
||||
orderField?: string;
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* 总条数
|
||||
*/
|
||||
total?: number;
|
||||
/**
|
||||
* 数据列表,loading状态
|
||||
*/
|
||||
dataListLoading?: boolean;
|
||||
/**
|
||||
* 数据列表,多选项
|
||||
*/
|
||||
dataListSelections?: IObject[];
|
||||
elTable?: IObject;
|
||||
}
|
||||
|
||||
export interface IViewHooks extends IViewHooksOptions, IObject {
|
||||
/**
|
||||
* 检查权限
|
||||
*/
|
||||
hasPermission: (key: string) => boolean;
|
||||
/**
|
||||
* 获取字典名称
|
||||
*/
|
||||
getDictLabel: (dictType: string, dictValue: number) => string | number;
|
||||
/**
|
||||
* 查询列表记录
|
||||
*/
|
||||
query: () => void;
|
||||
/**
|
||||
* 列表多选事件
|
||||
*/
|
||||
dataListSelectionChangeHandle: (list: IObject[]) => void;
|
||||
/**
|
||||
* 列表排序事件
|
||||
*/
|
||||
dataListSortChangeHandle: (sort: IObject) => void;
|
||||
/**
|
||||
* 列表切换每页显示数量事件
|
||||
*/
|
||||
pageSizeChangeHandle: (pageSize: number) => void;
|
||||
/**
|
||||
* 列表分页事件
|
||||
*/
|
||||
pageCurrentChangeHandle: (pageIndex: number) => void;
|
||||
/**
|
||||
* 列表搜索事件
|
||||
*/
|
||||
getDataList: () => void;
|
||||
/**
|
||||
* 列表删除事件
|
||||
*/
|
||||
deleteHandle: (id?: string) => Promise<any>;
|
||||
/**
|
||||
* 列表导出事件
|
||||
*/
|
||||
exportHandle: () => void;
|
||||
/**
|
||||
* 关闭当前tab页
|
||||
*/
|
||||
closeCurrentTab: () => void;
|
||||
/**
|
||||
* 处理流程
|
||||
*/
|
||||
handleFlowRoute: (e: IObject) => void;
|
||||
/**
|
||||
* 查看流程详情
|
||||
*/
|
||||
flowDetailRoute: (e: IObject) => void;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* eslint-disable */
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
declare module "*.svg";
|
||||
declare module "*.png";
|
||||
declare module "*.jpg";
|
||||
declare module "*.jpeg";
|
||||
declare module "*.gif";
|
||||
declare module "*.bmp";
|
||||
declare module "*.tiff";
|
||||
declare module "*.gif";
|
||||
|
||||
declare module "*.less";
|
||||
|
||||
declare global {
|
||||
interface ImportMeta {
|
||||
env: Record<string, unknown>;
|
||||
globEager<T = unknown>(globPath: string): Record<string, T>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "virtual:*" {
|
||||
const result: any;
|
||||
export default result;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { CacheToken } from "@/constants/cacheKey";
|
||||
import { ICacheOptions } from "@/types/interface";
|
||||
import { isNullOrUndefined } from "./utils";
|
||||
|
||||
const fix = "v1@";
|
||||
|
||||
/**
|
||||
* 存储介质适配器
|
||||
* @param isSessionStorage
|
||||
* @returns
|
||||
*/
|
||||
const cacheAdapter = (isSessionStorage?: boolean) => {
|
||||
return isSessionStorage ? sessionStorage : localStorage;
|
||||
};
|
||||
|
||||
/**
|
||||
* 取缓存值
|
||||
* @param {*} key
|
||||
* @param {*} options
|
||||
*/
|
||||
export const getCache = (key: string, options?: ICacheOptions, defaultValue?: unknown): any => {
|
||||
key = fix + key;
|
||||
options = { isParse: true, isDelete: false, ...options };
|
||||
try {
|
||||
const value = cacheAdapter(options.isSessionStorage).getItem(key);
|
||||
if (options.isDelete) {
|
||||
cacheAdapter(options.isSessionStorage).removeItem(key);
|
||||
}
|
||||
return isNullOrUndefined(value)
|
||||
? defaultValue
|
||||
: options.isParse
|
||||
? value
|
||||
? JSON.parse(value)
|
||||
: defaultValue
|
||||
: value;
|
||||
} catch (error) {
|
||||
console.error("getCache", error);
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置缓存值
|
||||
* @param {*} key
|
||||
* @param {*} value
|
||||
*/
|
||||
export const setCache = (
|
||||
key: string,
|
||||
value: string | Record<string, unknown> | Array<any>[],
|
||||
isSessionStorage?: boolean
|
||||
): void => {
|
||||
key = fix + key;
|
||||
cacheAdapter(isSessionStorage).setItem(
|
||||
key,
|
||||
typeof value === "object" ? JSON.stringify(value) : value
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @param key
|
||||
* @param isSessionStorage
|
||||
*/
|
||||
export const removeCache = (key: string, isSessionStorage?: boolean): void => {
|
||||
key = fix + key;
|
||||
cacheAdapter(isSessionStorage).removeItem(key);
|
||||
};
|
||||
|
||||
export const getToken = (): string => {
|
||||
return getCache(CacheToken, { isSessionStorage: true }, {})["token"];
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import mitt, { Emitter } from "mitt";
|
||||
|
||||
/**
|
||||
* 事件总线
|
||||
*/
|
||||
export default mitt() as Emitter;
|
||||
@@ -0,0 +1,90 @@
|
||||
import app from "@/constants/app";
|
||||
import { IHttpResponse, IObject } from "@/types/interface";
|
||||
import router from "@/router";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import qs from "qs";
|
||||
import { getToken } from "./cache";
|
||||
import { getValueByKeys } from "./utils";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: app.api,
|
||||
timeout: app.requestTimeout
|
||||
});
|
||||
|
||||
http.interceptors.request.use(
|
||||
function (config: any) {
|
||||
config.headers["X-Requested-With"] = "XMLHttpRequest";
|
||||
config.headers["Request-Start"] = new Date().getTime();
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
config.headers["token"] = token;
|
||||
}
|
||||
if (config.method?.toUpperCase() === "GET") {
|
||||
config.params = { ...config.params, _t: new Date().getTime() };
|
||||
}
|
||||
if (Object.values(config.headers).includes("application/x-www-form-urlencoded")) {
|
||||
config.data = qs.stringify(config.data);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
function (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
http.interceptors.response.use(
|
||||
(response) => {
|
||||
// 响应成功
|
||||
if (response.data.code === 0) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// 错误提示
|
||||
ElMessage.error(response.data.msg);
|
||||
|
||||
if (response.data.code === 401) {
|
||||
//自定义业务状态码
|
||||
redirectLogin();
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(response.data.msg || "Error"));
|
||||
},
|
||||
(error) => {
|
||||
const status = getValueByKeys(error, "response.status", 500);
|
||||
const httpCodeLabel: IObject<string> = {
|
||||
400: "请求参数错误",
|
||||
401: "未授权,请登录",
|
||||
403: "拒绝访问",
|
||||
404: `请求地址出错: ${getValueByKeys(error, "response.config.url", "")}`,
|
||||
408: "请求超时",
|
||||
500: "API接口报500错误",
|
||||
501: "服务未实现",
|
||||
502: "网关错误",
|
||||
503: "服务不可用",
|
||||
504: "网关超时",
|
||||
505: "HTTP版本不受支持"
|
||||
};
|
||||
if (error && error.response) {
|
||||
console.error("请求错误", error.response.data);
|
||||
}
|
||||
if (status === 401) {
|
||||
redirectLogin();
|
||||
}
|
||||
return Promise.reject(new Error(httpCodeLabel[status] || "接口错误"));
|
||||
}
|
||||
);
|
||||
|
||||
const redirectLogin = () => {
|
||||
router.replace("/login");
|
||||
return;
|
||||
};
|
||||
|
||||
export default (o: AxiosRequestConfig): Promise<IHttpResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http(o)
|
||||
.then((res) => {
|
||||
return resolve(res.data);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
import app from "@/constants/app";
|
||||
import Layout from "@/layout/layout.vue";
|
||||
import { toSysViewComponentPath } from "@/router";
|
||||
import { IObject, IServerMenus } from "@/types/interface";
|
||||
import Iframe from "@/views/iframe.vue";
|
||||
import { Router, RouteRecordNormalized, RouteRecordRaw } from "vue-router";
|
||||
import { getValueByKeys, isExternalLink } from "./utils";
|
||||
|
||||
/**
|
||||
* 合并本地路由和服务端菜单,追加isIframe和isNewPage参数到meta中
|
||||
* @param serverRoutes
|
||||
* @param sysRouteMap
|
||||
* @returns
|
||||
*/
|
||||
export const mergeServerRoute = (
|
||||
serverRoutes: IServerMenus[],
|
||||
sysRouteMap: IObject,
|
||||
matched: IObject[] = []
|
||||
): [RouteRecordRaw[], IObject] => {
|
||||
const rs: RouteRecordRaw[] = [];
|
||||
let routeToMeta: IObject = {};
|
||||
serverRoutes.forEach((x: IServerMenus) => {
|
||||
const [path, meta] = mergeRouteToOpenStyle(x.url, x);
|
||||
const viewComponent = sysRouteMap[toSysViewComponentPath(path)];
|
||||
const isNotMatchComponent =
|
||||
!viewComponent && !meta.isIframe && !meta.isNewPage && !(x.children && x.children.length);
|
||||
const r: RouteRecordRaw = {
|
||||
path,
|
||||
name: path,
|
||||
component: meta.isIframe ? Iframe : x.children && x.children.length ? Layout : viewComponent
|
||||
};
|
||||
r.meta = {
|
||||
title: x.name,
|
||||
icon: x.icon,
|
||||
openStyle: x.openStyle,
|
||||
id: x.id,
|
||||
url: x.url,
|
||||
matched: [...matched, { path, title: x.name }],
|
||||
...meta
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
r.redirect =
|
||||
x.redirect ||
|
||||
(isNotMatchComponent ? { path: "/error", query: { to: 404 }, replace: true } : "");
|
||||
if (path) {
|
||||
routeToMeta[path] = r.meta;
|
||||
}
|
||||
if (x.children && x.children.length) {
|
||||
const childrenRoutes = mergeServerRoute(
|
||||
x.children,
|
||||
sysRouteMap,
|
||||
getValueByKeys(r.meta, "matched", [])
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
r.children = childrenRoutes[0];
|
||||
routeToMeta = { ...routeToMeta, ...childrenRoutes[1] };
|
||||
}
|
||||
rs.push(r);
|
||||
});
|
||||
return [rs, routeToMeta];
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置路由
|
||||
* @param router
|
||||
* @param routes
|
||||
*/
|
||||
export const resetRoute = (router: Router, routes: RouteRecordRaw[]): void => {
|
||||
routes.forEach((route: any) => {
|
||||
const { name } = route;
|
||||
router.hasRoute(name) && router.removeRoute(name);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 路由转换成对象格式
|
||||
* @param routes
|
||||
* @returns
|
||||
*/
|
||||
export const routesToObject = (routes: any[]): IObject<RouteRecordNormalized> => {
|
||||
const rs: IObject<RouteRecordNormalized> = {};
|
||||
const loop = (ms: any[]) => {
|
||||
ms.forEach((x: RouteRecordNormalized): void => {
|
||||
rs[x.path] = x;
|
||||
if (x.children && x.children.length) {
|
||||
loop(x.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
loop(routes);
|
||||
return rs;
|
||||
};
|
||||
|
||||
/**
|
||||
* 转化为有效的导航路由
|
||||
* @param routes
|
||||
* @returns
|
||||
*/
|
||||
export const toValidRoutes = (routes: RouteRecordRaw[]): RouteRecordRaw[] => {
|
||||
const rs: RouteRecordRaw[] = [];
|
||||
routes.forEach((x: RouteRecordRaw) => {
|
||||
if (x.meta && x.meta.isNavigationMenu !== false) {
|
||||
if (x.children && x.children.length) {
|
||||
x.children = toValidRoutes(x.children);
|
||||
}
|
||||
rs.push(x);
|
||||
}
|
||||
});
|
||||
return rs;
|
||||
};
|
||||
|
||||
/**
|
||||
* 注册路由,keep-alive不支持多级路由,这里将多级路由转化为1级路由
|
||||
* @param router
|
||||
* @param rs
|
||||
*/
|
||||
export const registerToRouter = (router: Router, rs: RouteRecordRaw[]): void => {
|
||||
rs.forEach((x: RouteRecordRaw) => {
|
||||
if (!router.hasRoute(x.path)) {
|
||||
if (x.children && x.children.length) {
|
||||
router.addRoute({ ...x, children: [] });
|
||||
registerToRouter(router, x.children);
|
||||
} else {
|
||||
router.addRoute(x);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const mergeRouteToOpenStyle = (url: string, item: IServerMenus): [string, IObject] => {
|
||||
url = url || `/iframe/${item.id}`;
|
||||
let meta: IObject = {};
|
||||
const toRoutePath = (url: string): string => {
|
||||
return (url = !/^\//g.test(url) ? "/" + url : url);
|
||||
};
|
||||
//生成变量路由数据
|
||||
const renderVariableHook = (url: string): string => {
|
||||
return url.replace("{{ApiUrl}}", app.api);
|
||||
};
|
||||
if (item.openStyle === 1) {
|
||||
//外部
|
||||
if (isExternalLink(url)) {
|
||||
url = renderVariableHook(url);
|
||||
meta = { url, isNewPage: true };
|
||||
url = `/webview/${item.id}`; //虚拟无效地址
|
||||
} else {
|
||||
url = toRoutePath(url);
|
||||
meta = { url: `/#${url}?pop=true`, isNewPage: true };
|
||||
}
|
||||
} else {
|
||||
//内部
|
||||
if (isExternalLink(url)) {
|
||||
url = renderVariableHook(url);
|
||||
meta = { url, isIframe: true };
|
||||
url = `/iframe/${item.id}`;
|
||||
} else {
|
||||
url = toRoutePath(url);
|
||||
}
|
||||
}
|
||||
return [url, meta];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param routes 获取基础路由url和meta数据
|
||||
* @returns
|
||||
*/
|
||||
export const getBaseRouteToMeta = (routes: RouteRecordRaw[]): IObject => {
|
||||
let routeToMeta: IObject = {};
|
||||
routes.forEach((x) => {
|
||||
if (x.path && x.meta) {
|
||||
routeToMeta[x.path] = { ...x.meta, openStyle: 0, id: x.path, url: x.path };
|
||||
}
|
||||
if (x.children && x.children.length) {
|
||||
routeToMeta = { ...routeToMeta, ...getBaseRouteToMeta(x.children) };
|
||||
}
|
||||
});
|
||||
return routeToMeta;
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
import { CacheTheme } from "@/constants/cacheKey";
|
||||
import { themeSetting } from "@/constants/config";
|
||||
import { EMitt, EThemeSetting } from "@/constants/enum";
|
||||
import { IFunction, IObject } from "@/types/interface";
|
||||
import { getCache, setCache } from "./cache";
|
||||
import emits from "./emits";
|
||||
import chalkCss from "element-plus/theme-chalk/index.css?inline";
|
||||
|
||||
/**
|
||||
* 取主题设置缓存
|
||||
* @returns
|
||||
*/
|
||||
export const getThemeConfigCache = (): IObject => {
|
||||
const cache = getCache(CacheTheme, {}, {});
|
||||
return { ...themeSetting, ...cache };
|
||||
};
|
||||
|
||||
/**
|
||||
* 取主题设置缓存
|
||||
* @param key
|
||||
* @param config
|
||||
* @returns
|
||||
*/
|
||||
export const getThemeConfigCacheByKey = (
|
||||
key: EThemeSetting,
|
||||
config?: IObject
|
||||
): string | boolean | number => {
|
||||
config = config || getCache(CacheTheme, {}, {});
|
||||
return config ? config[key] ?? themeSetting[key] : themeSetting[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成主题设置样式名称
|
||||
* @param config
|
||||
* @returns
|
||||
*/
|
||||
export const getThemeConfigToClass = (config: IObject = {}): IObject<string> => {
|
||||
const cl: IObject<string> = {};
|
||||
Object.keys(config).forEach((x) => {
|
||||
cl[x] = `ui-${x}-${config[x]}`;
|
||||
});
|
||||
return cl;
|
||||
};
|
||||
|
||||
/**
|
||||
* 主题设置到缓存
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
export const setThemeConfigToCache = (
|
||||
key: EThemeSetting,
|
||||
value: string | boolean | number
|
||||
): void => {
|
||||
const theme = getCache(CacheTheme, {}, {});
|
||||
setCache(CacheTheme, { ...theme, [key]: value });
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置主题色
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
export const setThemeColor = (key: string, value: string): void => {
|
||||
const elm = window.document.querySelector("body");
|
||||
if (elm) {
|
||||
elm.style.setProperty(key, value);
|
||||
elm.style.setProperty(key + "-light", value + "14");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成主题色
|
||||
* @param theme
|
||||
* @returns
|
||||
*/
|
||||
export const getThemeCluster = (theme: string): string[] => {
|
||||
const tintColor = (color: string, tint: number) => {
|
||||
let red: any = parseInt(color.slice(0, 2), 16);
|
||||
let green: any = parseInt(color.slice(2, 4), 16);
|
||||
let blue: any = parseInt(color.slice(4, 6), 16);
|
||||
|
||||
if (tint === 0) {
|
||||
// when primary color is in its rgb space
|
||||
return [red, green, blue].join(",");
|
||||
} else {
|
||||
red += Math.round(tint * (255 - red));
|
||||
green += Math.round(tint * (255 - green));
|
||||
blue += Math.round(tint * (255 - blue));
|
||||
|
||||
red = red.toString(16);
|
||||
green = green.toString(16);
|
||||
blue = blue.toString(16);
|
||||
|
||||
return `#${red}${green}${blue}`;
|
||||
}
|
||||
};
|
||||
|
||||
const shadeColor = (color: string, shade: number): string => {
|
||||
let red: any = parseInt(color.slice(0, 2), 16);
|
||||
let green: any = parseInt(color.slice(2, 4), 16);
|
||||
let blue: any = parseInt(color.slice(4, 6), 16);
|
||||
|
||||
red = Math.round((1 - shade) * red);
|
||||
green = Math.round((1 - shade) * green);
|
||||
blue = Math.round((1 - shade) * blue);
|
||||
|
||||
red = red.toString(16);
|
||||
green = green.toString(16);
|
||||
blue = blue.toString(16);
|
||||
|
||||
return `#${red}${green}${blue}`;
|
||||
};
|
||||
|
||||
const clusters = [theme];
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))));
|
||||
}
|
||||
clusters.push(shadeColor(theme, 0.1));
|
||||
return clusters;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取主题css
|
||||
* @param url
|
||||
* @param callback
|
||||
* @param variable
|
||||
*/
|
||||
export const getCSSString = (callback: IFunction, variable?: string): void => {
|
||||
if (variable) {
|
||||
(window as any)[variable] = chalkCss;
|
||||
}
|
||||
callback(chalkCss);
|
||||
};
|
||||
|
||||
export const updateStyle = (style: string, oldCluster: string[], newCluster: string[]): string => {
|
||||
let newStyle = style;
|
||||
oldCluster.forEach((color, index) => {
|
||||
newStyle = newStyle.replace(new RegExp(color, "ig"), newCluster[index]);
|
||||
});
|
||||
return newStyle;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新主题色
|
||||
* @param themeColor
|
||||
* @param val
|
||||
* @returns
|
||||
*/
|
||||
export const updateTheme = (val: string): void => {
|
||||
emits.emit(EMitt.OnLoading, true);
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
const themeCluster = getThemeCluster(val.replace("#", ""));
|
||||
const getHandler = (variable: string, id: string) => {
|
||||
return () => {
|
||||
const originalCluster = getThemeCluster("#409eff".replace("#", ""));
|
||||
const newStyle = updateStyle((window as any)[variable], originalCluster, themeCluster);
|
||||
let styleTag = document.getElementById(id);
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement("style");
|
||||
styleTag.setAttribute("id", id);
|
||||
head.appendChild(styleTag);
|
||||
}
|
||||
styleTag.innerText = newStyle;
|
||||
emits.emit(EMitt.OnLoading, false);
|
||||
};
|
||||
};
|
||||
const chalkHandler = getHandler("__chalk", "chalk-style");
|
||||
if (!(window as any)["__chalk"]) {
|
||||
getCSSString(chalkHandler, "__chalk");
|
||||
} else {
|
||||
chalkHandler();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,252 @@
|
||||
import { IFunction, IObject } from "@/types/interface";
|
||||
import { debounce, DebouncedFunc, DebounceSettings } from "lodash";
|
||||
import type { App, Plugin } from "vue";
|
||||
|
||||
/**
|
||||
* 获取对象下的字段值
|
||||
* @param record {}
|
||||
* @param key 'a.b.c'
|
||||
* @param defaultValue
|
||||
* @returns
|
||||
*/
|
||||
export const getValueByKeys = (record: IObject = {}, key: string, defaultValue?: unknown): any => {
|
||||
const keys = key.split(".");
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
record = record[keys[i]] || (i === keys.length - 1 ? defaultValue : {});
|
||||
}
|
||||
return record || defaultValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* 数组转对象
|
||||
* @param {*} rs 集合
|
||||
* @param {*} key 需要转换目标key的名称
|
||||
*/
|
||||
export const arrayToObject = (
|
||||
rs: any[] = [],
|
||||
key: string | IFunction,
|
||||
render?: IFunction
|
||||
): IObject => {
|
||||
const o: IObject = {};
|
||||
rs.forEach((x) => {
|
||||
o[typeof key === "function" ? key(x) : x[key]] = render ? render(x) : x;
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
/**
|
||||
* 数组转换格式
|
||||
* @param rs
|
||||
* @param render
|
||||
* @returns
|
||||
*/
|
||||
export const arrayToKeyValueArray = (rs: any[] = [], render?: IFunction): any[] => {
|
||||
return rs.map((x) => (render ? render(x) : typeof x === "object" ? x : { label: x, value: x }));
|
||||
};
|
||||
|
||||
/**
|
||||
* 是否只null和undefined值
|
||||
* @param vl
|
||||
* @returns
|
||||
*/
|
||||
export const isNullOrUndefined = (vl: unknown): boolean => {
|
||||
return vl === null || typeof vl === "undefined";
|
||||
};
|
||||
|
||||
/**
|
||||
* 是否外链
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
export const isExternalLink = (path: string): boolean => {
|
||||
return /^(https?:|\/\/|mailto:|tel:|^{{\s?ApiUrl\s?}})/.test(path);
|
||||
};
|
||||
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
*/
|
||||
export const copyToClipboard = (value: string): void => {
|
||||
const transfer = document.createElement("textarea");
|
||||
document.body.appendChild(transfer);
|
||||
transfer.value = value;
|
||||
transfer.focus();
|
||||
transfer.select();
|
||||
if (document.execCommand("copy")) {
|
||||
document.execCommand("copy");
|
||||
}
|
||||
transfer.blur();
|
||||
document.body.removeChild(transfer);
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查是否有权限
|
||||
* @param permission
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
export const checkPermission = (permission: string[], key: string): boolean => {
|
||||
return permission.includes(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取uuid
|
||||
*/
|
||||
export const getUuid = (): string => {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
* @param {*} s
|
||||
*/
|
||||
export const isEmail = (s: string): boolean => {
|
||||
return /^([a-zA-Z0-9._-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
* @param {*} s
|
||||
*/
|
||||
export const isMobile = (s: string): boolean => {
|
||||
return /^1[0-9]{10}$/.test(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* 电话号码
|
||||
* @param {*} s
|
||||
*/
|
||||
export const isPhone = (s: string): boolean => {
|
||||
return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* URL地址
|
||||
* @param {*} s
|
||||
*/
|
||||
export const isURL = (s: string): boolean => {
|
||||
return /^http[s]?:\/\/.*/.test(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* 正整数
|
||||
* @param {*} s
|
||||
*/
|
||||
export const isNumber = (s: string): boolean => {
|
||||
return /^\+?[1-9][0-9]*$/.test(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取字典数据列表
|
||||
* @param dictType 字典类型
|
||||
*/
|
||||
export const getDictDataList = (list: IObject[], dictType?: string): IObject[] => {
|
||||
const type = list.find((element: IObject) => element.dictType === dictType);
|
||||
if (type) {
|
||||
return type.dataList;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取字典名称
|
||||
* @param dictType 字典类型
|
||||
* @param dictValue 字典值
|
||||
*/
|
||||
export const getDictLabel = (
|
||||
list: IObject[],
|
||||
dictType: string,
|
||||
dictValue: number
|
||||
): string | number => {
|
||||
const type = list.find((element: IObject) => element.dictType === dictType);
|
||||
if (type) {
|
||||
const val = type.dataList.find((element: IObject) => element.dictValue === dictValue + "");
|
||||
if (val) {
|
||||
return val.dictLabel;
|
||||
} else {
|
||||
return dictValue;
|
||||
}
|
||||
} else {
|
||||
return dictValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取svg图标(id)列表
|
||||
*/
|
||||
export const getIconList = (): string[] => {
|
||||
const rs: string[] = [];
|
||||
const list = document.querySelectorAll("svg symbol");
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
rs.push(list[i].id);
|
||||
}
|
||||
return rs;
|
||||
};
|
||||
|
||||
/**
|
||||
* 树形数据转换
|
||||
* @param {*} data
|
||||
* @param {*} id
|
||||
* @param {*} pid
|
||||
*/
|
||||
export const treeDataTranslate = (data: IObject[], id?: string, pid?: string): IObject[] => {
|
||||
const res: IObject[] = [];
|
||||
const temp: IObject = {};
|
||||
id = id || "id";
|
||||
pid = pid || "pid";
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
temp[data[i][id]] = data[i];
|
||||
}
|
||||
for (let k = 0; k < data.length; k++) {
|
||||
if (!temp[data[k][pid]] || data[k][id] === data[k][pid]) {
|
||||
res.push(data[k]);
|
||||
continue;
|
||||
}
|
||||
if (!temp[data[k][pid]]["children"]) {
|
||||
temp[data[k][pid]]["children"] = [];
|
||||
}
|
||||
temp[data[k][pid]]["children"].push(data[k]);
|
||||
data[k]["_level"] = (temp[data[k][pid]]._level || 0) + 1;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* 全局组件安装
|
||||
* @param component
|
||||
* @param alias
|
||||
* @returns
|
||||
*/
|
||||
export const withInstall = <T extends object>(component: T, alias?: string): T & Plugin => {
|
||||
const comp = component as any;
|
||||
comp.install = (app: App) => {
|
||||
app.component(comp.name || comp.displayName, component);
|
||||
if (alias) {
|
||||
app.config.globalProperties[alias] = component;
|
||||
}
|
||||
};
|
||||
return component as T & Plugin;
|
||||
};
|
||||
|
||||
/**
|
||||
* 节流函数 `created() {this.dataFormSubmitHandle = useDebounce(this.dataFormSubmitHandle);}`
|
||||
* @param fn
|
||||
* @param wait
|
||||
* @returns
|
||||
*/
|
||||
export const useDebounce = (
|
||||
fn: (e?: any) => any,
|
||||
wait?: number,
|
||||
options?: DebounceSettings
|
||||
): DebouncedFunc<any> => {
|
||||
return debounce(fn, wait ?? 1000, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
...options
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="!dataForm.id ? '新增观测数据' : '修改观测数据'" width="800px" :close-on-click-modal="false">
|
||||
<el-form :model="dataForm" :rules="rules" ref="dataFormRef" label-width="140px" style="padding-right: 20px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点ID" prop="stationId">
|
||||
<el-input v-model="dataForm.stationId" placeholder="请输入区站号"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="观测日期" prop="observeDate">
|
||||
<el-date-picker v-model="dataForm.observeDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD 00:00:00" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-divider content-position="left">气温数据 (℃)</el-divider>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-form-item label="平均气温" prop="avgTemp" label-width="80px">
|
||||
<el-input-number v-model="dataForm.avgTemp" :precision="1" :step="0.1" controls-position="right" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最高气温" prop="maxTemp" label-width="80px">
|
||||
<el-input-number v-model="dataForm.maxTemp" :precision="1" :step="0.1" controls-position="right" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出现时刻" prop="maxTempTime" label-width="80px">
|
||||
<el-input v-model="dataForm.maxTempTime" placeholder="HHmm"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最低气温" prop="minTemp" label-width="80px">
|
||||
<el-input-number v-model="dataForm.minTemp" :precision="1" :step="0.1" controls-position="right" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出现时刻" prop="minTempTime" label-width="80px">
|
||||
<el-input v-model="dataForm.minTempTime" placeholder="HHmm"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-divider content-position="left">降水与风力</el-divider>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="20-20时降水量" prop="rain2020">
|
||||
<el-input-number v-model="dataForm.rain2020" :precision="1" :min="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="08-08时降水量" prop="rain0808">
|
||||
<el-input-number v-model="dataForm.rain0808" :precision="1" :min="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最大风速(m/s)" prop="maxWindSpeed">
|
||||
<el-input-number v-model="dataForm.maxWindSpeed" :precision="1" :min="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="风向(角度)" prop="maxWindDirection">
|
||||
<el-input-number v-model="dataForm.maxWindDirection" :min="0" :max="360" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const emit = defineEmits(["refreshDataList"]);
|
||||
const visible = ref(false);
|
||||
const dataFormRef = ref();
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
stationId: '',
|
||||
observeDate: '',
|
||||
avgTemp: 0,
|
||||
maxTemp: 0,
|
||||
maxTempTime: '',
|
||||
minTemp: 0,
|
||||
minTempTime: '',
|
||||
rain2020: 0,
|
||||
rain0808: 0,
|
||||
maxWindSpeed: 0,
|
||||
maxWindDirection: 0,
|
||||
maxWindTime: '',
|
||||
extremeWindSpeed: 0,
|
||||
extremeWindDirection: 0,
|
||||
extremeWindTime: '',
|
||||
createDate: ''
|
||||
});
|
||||
|
||||
// 校验规则优化
|
||||
const rules = ref({
|
||||
stationId: [{ required: true, message: '站点不能为空', trigger: 'blur' }],
|
||||
observeDate: [{ required: true, message: '观测日期不能为空', trigger: 'change' }],
|
||||
// 时刻格式简单校验 (HHmm)
|
||||
maxTempTime: [{ pattern: /^([01]\d|2[0-3])[0-5]\d$/, message: '格式应为HHmm(如0715)', trigger: 'blur' }],
|
||||
minTempTime: [{ pattern: /^([01]\d|2[0-3])[0-5]\d$/, message: '格式应为HHmm(如1430)', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true;
|
||||
// 重置表单,清空之前的 ID 和数据
|
||||
dataForm.id = "";
|
||||
Object.assign(dataForm, {
|
||||
avgTemp: 0, maxTemp: 0, minTemp: 0, rain2020: 0, rain0808: 0,
|
||||
maxWindSpeed: 0, maxWindDirection: 0, extremeWindSpeed: 0
|
||||
});
|
||||
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields();
|
||||
}
|
||||
|
||||
if (id) {
|
||||
getInfo(id);
|
||||
}
|
||||
};
|
||||
|
||||
const getInfo = (id: number) => {
|
||||
baseService.get("/dailyweather/weatherdailydata/" + id).then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
const dataFormSubmitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) return false;
|
||||
|
||||
const action = !dataForm.id ? baseService.post : baseService.put;
|
||||
action("/dailyweather/weatherdailydata", dataForm).then((res) => {
|
||||
ElMessage.success({
|
||||
message: '提交成功',
|
||||
duration: 800,
|
||||
onClose: () => {
|
||||
visible.value = false;
|
||||
emit("refreshDataList");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({ init });
|
||||
</script>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="批量导入气象数据" width="450px" :close-on-click-modal="false">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
class="upload-demo"
|
||||
drag
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:on-change="handleChange"
|
||||
:http-request="uploadFileRequest"
|
||||
:limit="1"
|
||||
:on-exceed="handleExceed"
|
||||
accept=".xlsx, .xls"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或 <em>点击选择</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="text-align: center;">
|
||||
只能上传 Excel 文件,且不超过 10MB
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="dataFormSubmitHandle">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import baseService from "@/service/baseService"; // 确保路径对应你的项目
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const uploadRef = ref();
|
||||
const fileRaw = ref<File | null>(null);
|
||||
const emit = defineEmits(['refreshDataList']);
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
visible.value = true;
|
||||
fileRaw.value = null;
|
||||
if(uploadRef.value) uploadRef.value.clearFiles();
|
||||
};
|
||||
|
||||
// 监听文件选择状态
|
||||
const handleChange = (file: any) => {
|
||||
fileRaw.value = file.raw;
|
||||
};
|
||||
|
||||
// 限制只能选一个文件,选新文件替换旧文件
|
||||
const handleExceed = (files: any) => {
|
||||
uploadRef.value!.clearFiles();
|
||||
const file = files[0];
|
||||
uploadRef.value!.handleStart(file);
|
||||
};
|
||||
|
||||
const dataFormSubmitHandle = () => {
|
||||
if (!fileRaw.value) {
|
||||
return ElMessage.warning("请先选择文件");
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const formData = new FormData();
|
||||
// 这里的 "file" 必须和后端 Controller 的 @RequestParam("file") 名字一致
|
||||
formData.append("file", fileRaw.value);
|
||||
|
||||
// 注意:很多封装好的 baseService 在 post 时会自动序列化 data
|
||||
// 尝试直接发送 formData,不要加额外的 {} 包装
|
||||
baseService.upload("/dailyweather/weatherdailydata/import", formData)
|
||||
.then(() => {
|
||||
ElMessage.success("导入成功");
|
||||
visible.value = false;
|
||||
emit("refreshDataList");
|
||||
})
|
||||
.catch((err) => {
|
||||
// 捕获具体报错
|
||||
console.error("上传细节错误:", err);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({ init });
|
||||
</script>
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="mod-dailyweather__weatherdailydata">
|
||||
<el-form :inline="true" :model="state.dataForm" @keyup.enter="state.getDataList()">
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('dailyweather:weatherdailydata:save')" type="primary" icon="Plus" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="state.hasPermission('dailyweather:weatherdailydata:delete')" type="danger" icon="Delete" @click="state.deleteHandle()">批量删除</el-button>
|
||||
<el-button type="success" icon="Upload" @click="importHandle()">批量导入</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="float: right; margin-right: 0">
|
||||
<el-popover placement="bottom-end" :width="300" trigger="click">
|
||||
<template #reference>
|
||||
<el-button icon="Grid">展示列设置</el-button>
|
||||
</template>
|
||||
<div class="column-setting-container">
|
||||
<div v-for="group in columnGroups" :key="group.title" class="column-group">
|
||||
<div class="group-title">{{ group.title }}</div>
|
||||
<el-checkbox-group v-model="selectedColumns">
|
||||
<el-checkbox
|
||||
v-for="col in group.columns"
|
||||
:key="col.prop"
|
||||
:label="col.prop"
|
||||
style="width: 120px"
|
||||
>
|
||||
{{ col.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<el-divider style="margin: 8px 0" />
|
||||
</div>
|
||||
<div style="text-align: right">
|
||||
<el-button size="small" type="primary" link @click="resetColumns">恢复默认</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
v-loading="state.dataListLoading"
|
||||
:data="state.dataList"
|
||||
border
|
||||
@selection-change="state.dataListSelectionChangeHandle"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50" fixed="left"></el-table-column>
|
||||
|
||||
<template v-for="group in columnGroups">
|
||||
<template v-for="item in group.columns">
|
||||
<el-table-column
|
||||
v-if="selectedColumns.includes(item.prop)"
|
||||
:key="item.prop"
|
||||
:prop="item.prop"
|
||||
:label="item.label"
|
||||
header-align="center"
|
||||
align="center"
|
||||
min-width="120"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
<span v-if="item.prop === 'observeDate'">
|
||||
{{ formatObserveDate(scope.row.observeDate) }}
|
||||
</span>
|
||||
|
||||
<span v-else-if="item.prop.endsWith('Time') && item.prop !== 'createTime'">
|
||||
<el-tag v-if="isValidTime(scope.row[item.prop])" size="small" effect="plain" type="info">
|
||||
{{ formatWeatherTime(scope.row[item.prop]) }}
|
||||
</el-tag>
|
||||
<span v-else>--</span>
|
||||
</span>
|
||||
|
||||
<span v-else>{{ scope.row[item.prop] ?? '--' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="150">
|
||||
<template v-slot="scope">
|
||||
<el-button v-if="state.hasPermission('dailyweather:weatherdailydata:update')" type="primary" link @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button v-if="state.hasPermission('dailyweather:weatherdailydata:delete')" type="primary" link @click="state.deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="state.page"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="state.limit"
|
||||
:total="state.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="state.pageSizeChangeHandle"
|
||||
@current-change="state.pageCurrentChangeHandle"
|
||||
/>
|
||||
|
||||
<add-or-update ref="addOrUpdateRef" @refreshDataList="state.getDataList" />
|
||||
<import-excel ref="importExcelRef" @refreshDataList="state.getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useView from "@/hooks/useView";
|
||||
import { reactive, ref, toRefs, watch, onMounted } from "vue";
|
||||
import AddOrUpdate from "./weatherdailydata-add-or-update.vue";
|
||||
import ImportExcel from "./weatherdailydata-import.vue";
|
||||
|
||||
const columnGroups = [
|
||||
{
|
||||
title: '基础信息',
|
||||
columns: [
|
||||
{ prop: 'stationId', label: '区站号', default: true },
|
||||
{ prop: 'stationName', label: '站名', default: true },
|
||||
{ prop: 'observeDate', label: '观测日期', default: true },
|
||||
{ prop: 'createDate', label: '创建时间', default: false },
|
||||
{ prop: 'id', label: '系统编号', default: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '气温数据',
|
||||
columns: [
|
||||
{ prop: 'avgTemp', label: '平均气温', default: true },
|
||||
{ prop: 'maxTemp', label: '最高气温', default: true },
|
||||
{ prop: 'maxTempTime', label: '最高气温时间', default: false },
|
||||
{ prop: 'minTemp', label: '最低气温', default: true },
|
||||
{ prop: 'minTempTime', label: '最低气温时间', default: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '降水/风力数据',
|
||||
columns: [
|
||||
{ prop: 'rain2020', label: '20-20降水', default: true },
|
||||
{ prop: 'rain0808', label: '08-08降水', default: true },
|
||||
{ prop: 'maxWindSpeed', label: '最大风速', default: false },
|
||||
{ prop: 'maxWindDirection', label: '最大风向', default: false },
|
||||
{ prop: 'maxWindTime', label: '最大风时间', default: false },
|
||||
{ prop: 'extremeWindSpeed', label: '极大风速', default: false },
|
||||
{ prop: 'extremeWindTime', label: '极大风时间', default: false }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 智能日期截取
|
||||
const formatObserveDate = (dateStr: any) => {
|
||||
if (!dateStr) return '--';
|
||||
const str = String(dateStr);
|
||||
const separator = str.includes('T') ? 'T' : ' ';
|
||||
return str.split(separator)[0];
|
||||
};
|
||||
|
||||
// 校验有效时间数字
|
||||
const isValidTime = (val: any) => {
|
||||
return val !== null && val !== undefined && val !== '' && !isNaN(Number(val));
|
||||
};
|
||||
|
||||
// 时刻格式化:712 -> 07:12
|
||||
const formatWeatherTime = (time: any) => {
|
||||
let timeStr = String(time);
|
||||
if (timeStr.length === 3) timeStr = '0' + timeStr;
|
||||
if (timeStr.length === 4) {
|
||||
return `${timeStr.substring(0, 2)}:${timeStr.substring(2, 4)}`;
|
||||
}
|
||||
return timeStr;
|
||||
};
|
||||
|
||||
const view = reactive({
|
||||
deleteIsBatch: true,
|
||||
getDataListURL: "/dailyweather/weatherdailydata/page",
|
||||
getDataListIsPage: true,
|
||||
exportURL: "/dailyweather/weatherdailydata/export",
|
||||
deleteURL: "/dailyweather/weatherdailydata"
|
||||
});
|
||||
|
||||
const state = reactive({ ...useView(view), ...toRefs(view) });
|
||||
|
||||
const getDefaultColumns = () => {
|
||||
const defaultCols: string[] = [];
|
||||
columnGroups.forEach(g => {
|
||||
g.columns.forEach(c => { if(c.default) defaultCols.push(c.prop) });
|
||||
});
|
||||
return defaultCols;
|
||||
};
|
||||
|
||||
const selectedColumns = ref<string[]>(getDefaultColumns());
|
||||
|
||||
watch(selectedColumns, (newVal) => {
|
||||
localStorage.setItem('weather_column_pref', JSON.stringify(newVal));
|
||||
}, { deep: true });
|
||||
|
||||
onMounted(() => {
|
||||
const cache = localStorage.getItem('weather_column_pref');
|
||||
if (cache) {
|
||||
selectedColumns.value = JSON.parse(cache);
|
||||
}
|
||||
});
|
||||
|
||||
const resetColumns = () => {
|
||||
selectedColumns.value = getDefaultColumns();
|
||||
};
|
||||
|
||||
const addOrUpdateRef = ref();
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id);
|
||||
};
|
||||
|
||||
const importExcelRef = ref();
|
||||
const importHandle = () => {
|
||||
importExcelRef.value.init();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.column-setting-container {
|
||||
padding: 5px;
|
||||
}
|
||||
.group-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
color: #409eff;
|
||||
}
|
||||
.column-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 15px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { IObject } from "@/types/interface";
|
||||
import { defineComponent } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
interface ITip {
|
||||
title: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { to } = route.query;
|
||||
const tips: IObject = {
|
||||
404: {
|
||||
title: "404",
|
||||
message: "访问页面不存在"
|
||||
},
|
||||
error: {
|
||||
title: "错误",
|
||||
message: "访问出错了"
|
||||
}
|
||||
};
|
||||
const tip: ITip = tips[to?.toString() ?? "error"];
|
||||
const onBack = () => {
|
||||
router.back();
|
||||
};
|
||||
const onToHome = () => {
|
||||
router.replace("/");
|
||||
};
|
||||
return { tip, onBack, onToHome };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rr-error">
|
||||
<el-result>
|
||||
<template v-slot:icon>
|
||||
<el-icon style="font-size: 64px; color: #f5222d"><warning /></el-icon>
|
||||
</template>
|
||||
<template v-slot:title>
|
||||
<span style="font-size: 48px; font-weight: 800">{{ tip.title }}</span>
|
||||
</template>
|
||||
<template v-slot:subTitle>
|
||||
<span style="font-size: 36px">{{ tip.message }}</span>
|
||||
</template>
|
||||
<template v-slot:extra>
|
||||
<el-space :size="30">
|
||||
<el-button type="info" plain @click="onBack"> 返回 </el-button>
|
||||
<el-button type="primary" @click="onToHome"> 主页 </el-button>
|
||||
</el-space>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,521 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<el-card class="header-card" shadow="never">
|
||||
<div class="header-content">
|
||||
<div class="title-main-zone">
|
||||
<div class="calendar-icon-box">
|
||||
<div class="cal-top">{{ today.month }}月</div>
|
||||
<div class="cal-body">{{ today.day }}</div>
|
||||
</div>
|
||||
|
||||
<div class="title-text-group">
|
||||
<div class="top-row">
|
||||
<h2 class="main-title">{{ isRegionalMode ? '全域气象汇总对比' : '单站气象历史回溯' }}</h2>
|
||||
|
||||
<el-tag :type="isRegionalMode ? 'warning' : ''" effect="light" class="current-location-tag">
|
||||
<i class="el-icon-map-location"></i>
|
||||
{{ isRegionalMode ? '全市汇总 (郴州市)' : currentStationName }}
|
||||
</el-tag>
|
||||
|
||||
<el-tag :type="isRegionalMode ? 'warning' : 'success'" effect="dark" size="small">
|
||||
{{ isRegionalMode ? '市级' : '县级' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="bottom-row">
|
||||
<template v-if="!isRegionalMode">
|
||||
<span class="info-item clickable">
|
||||
<i class="el-icon-edit-outline"></i>
|
||||
<el-select v-model="selectedStation" size="mini" class="inline-select" @change="refreshData">
|
||||
<el-option v-for="item in stations" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
</span>
|
||||
<el-divider direction="vertical"/>
|
||||
</template>
|
||||
|
||||
<span class="info-item"><i class="el-icon-date"></i> 锚点:历年今日</span>
|
||||
<el-divider direction="vertical"/>
|
||||
<span class="info-item">
|
||||
<i class="el-icon-time"></i>
|
||||
范围:过去 {{ backwardYears }} 年 ({{ startYear }}-{{ currentYear }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
<el-radio-group v-model="mode" size="small" @change="handleModeChange" class="mode-switch">
|
||||
<el-radio-button label="single">单站</el-radio-button>
|
||||
<el-radio-button label="regional">全域</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-select
|
||||
v-model="backwardYears"
|
||||
size="small"
|
||||
class="year-select"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择或输入年限"
|
||||
@change="handleYearChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="y in [10, 15, 30, 50, 60]"
|
||||
:key="y"
|
||||
:label="'回溯' + y + '年'"
|
||||
:value="y"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-button-group style="margin-left: 20px">
|
||||
<el-button type="primary" size="small" icon="el-icon-picture" @click="handleExport('png')">保存图片
|
||||
</el-button>
|
||||
<el-button type="warning" size="small" icon="el-icon-document" @click="handleExport('pdf')">导出PDF
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-card class="module-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-data-analysis"></i> {{ chartTitle }}</span>
|
||||
<span class="unit-text">(雨: mm / 温: ℃)</span>
|
||||
</div>
|
||||
<div ref="mainChartRef" class="chart-box"></div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="module-card table-module">
|
||||
<div slot="header" class="table-header">
|
||||
<span class="table-title"><i class="el-icon-tickets"></i> 详细实况清单 ({{ currentAreaLabel }})</span>
|
||||
<el-link type="primary" :underline="false" @click="isTableExpanded = !isTableExpanded">
|
||||
{{ isTableExpanded ? '收起历史数据' : '查看全部 ' + tableData.length + ' 条' }}
|
||||
<i :class="isTableExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
|
||||
</el-link>
|
||||
</div>
|
||||
|
||||
<el-table :data="displayTableData" size="small" border stripe :max-height="isTableExpanded ? 600 : 250">
|
||||
<el-table-column v-if="isRegionalMode" prop="stationName" label="观测站点" align="center"/>
|
||||
<el-table-column v-else prop="fullDate" label="观测日期" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<span class="date-cell">{{ scope.row.fullDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rainfall" label="当日降雨" align="center" sortable>
|
||||
<template #default="scope">{{ scope.row.rainfall }} mm</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tmax" label="最高气温" align="center">
|
||||
<template #default="scope">{{ scope.row.tmax }} ℃</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tavg" label="平均气温" align="center"/>
|
||||
<el-table-column prop="tmin" label="最低气温" align="center"/>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-card class="module-card summary-card">
|
||||
<div slot="header"><b><i class="el-icon-document"></i> 摘要总结</b></div>
|
||||
<div class="summary-text">{{ summaryText }}</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="module-card disaster-card">
|
||||
<div slot="header"><b><i class="el-icon-message-solid"></i> 灾情回溯</b></div>
|
||||
<el-timeline class="custom-timeline">
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in disasterData"
|
||||
:key="index"
|
||||
:timestamp="item.year + '年今日'"
|
||||
placement="top"
|
||||
color="#F56C6C"
|
||||
>
|
||||
<div class="disaster-box">
|
||||
<div class="tag-row">
|
||||
<el-tag v-for="tag in item.tags" :key="tag" size="mini" effect="dark" :color="getTagColor(tag)"
|
||||
class="m-tag">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<p class="disaster-info">
|
||||
区域:{{ item.area }}
|
||||
<el-divider direction="vertical"/>
|
||||
损失:<span class="loss-val">{{ item.loss }}万</span>
|
||||
</p>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, computed, watch, nextTick} from "vue";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
// --- 状态逻辑 ---
|
||||
const mode = ref('single');
|
||||
const isRegionalMode = computed(() => mode.value === 'regional');
|
||||
const backwardYears = ref(30);
|
||||
const selectedStation = ref(1); // 默认选中站点ID为1
|
||||
const currentYear = new Date().getFullYear();
|
||||
const startYear = computed(() => currentYear - backwardYears.value);
|
||||
const isTableExpanded = ref(false);
|
||||
|
||||
const mainChartRef = ref(null);
|
||||
let chartInstance = null;
|
||||
const tableData = ref([]);
|
||||
const disasterData = ref([]);
|
||||
const summaryText = ref("");
|
||||
|
||||
const handleYearChange = (val) => {
|
||||
// 1. 将输入值转为整数
|
||||
let num = parseInt(val);
|
||||
|
||||
// 2. 容错校验:非数字或负数设为默认值 10,最大限制 100
|
||||
if (isNaN(num) || num <= 0) {
|
||||
backwardYears.value = 10;
|
||||
} else {
|
||||
backwardYears.value = num > 100 ? 100 : num;
|
||||
}
|
||||
|
||||
// 3. 触发数据刷新
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const currentStationName = computed(() => {
|
||||
const s = stations.find(item => item.id === selectedStation.value);
|
||||
return s ? s.name : '未知站点';
|
||||
});
|
||||
|
||||
const stations = [
|
||||
{id: 1, name: "安仁"}, {id: 2, name: "资兴"},
|
||||
{id: 3, name: "桂阳"}, {id: 4, name: "宜章"},
|
||||
{id: 5, name: "北湖"}, {id: 6, name: "苏仙"}
|
||||
];
|
||||
|
||||
const today = computed(() => {
|
||||
const d = new Date();
|
||||
return {month: d.getMonth() + 1, day: d.getDate()};
|
||||
});
|
||||
|
||||
const displayTableData = computed(() => isTableExpanded.value ? tableData.value : tableData.value.slice(0, 6));
|
||||
|
||||
const currentAreaLabel = computed(() => {
|
||||
if (isRegionalMode.value) return "郴州市";
|
||||
const s = stations.find(item => item.id === selectedStation.value);
|
||||
return s ? s.name + '站' : '';
|
||||
});
|
||||
|
||||
const chartTitle = computed(() => isRegionalMode.value ? "全市各站点实况对比" : `${currentAreaLabel.value}·历年今日演变趋势`);
|
||||
|
||||
const getTagColor = (t) => ({
|
||||
"暴雨": "#409EFF",
|
||||
"雷电": "#E6A23C",
|
||||
"高温": "#F56C6C",
|
||||
"大风": "#67C23A"
|
||||
}[t] || "#909399");
|
||||
|
||||
// --- 核心业务逻辑 ---
|
||||
function refreshData() {
|
||||
const mockTable = [];
|
||||
if (isRegionalMode.value) {
|
||||
stations.forEach(s => {
|
||||
mockTable.push({
|
||||
stationName: s.name,
|
||||
fullDate: `${currentYear}-${String(today.value.month).padStart(2, '0')}-${String(today.value.day).padStart(2, '0')}`,
|
||||
rainfall: (Math.random() * 45).toFixed(1),
|
||||
tmax: (26 + Math.random() * 6).toFixed(1),
|
||||
tavg: (21 + Math.random() * 4).toFixed(1),
|
||||
tmin: (18 + Math.random() * 3).toFixed(1)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
for (let i = 0; i < backwardYears.value; i++) {
|
||||
const year = currentYear - i;
|
||||
mockTable.push({
|
||||
year,
|
||||
fullDate: `${year}-${String(today.value.month).padStart(2, '0')}-${String(today.value.day).padStart(2, '0')}`,
|
||||
rainfall: (Math.random() * 70).toFixed(1),
|
||||
tmax: (22 + Math.random() * 12).toFixed(1),
|
||||
tavg: (18 + Math.random() * 6).toFixed(1),
|
||||
tmin: (14 + Math.random() * 5).toFixed(1)
|
||||
});
|
||||
}
|
||||
}
|
||||
tableData.value = mockTable;
|
||||
|
||||
disasterData.value = [
|
||||
{year: 2023, tags: ["暴雨", "雷电"], area: "安仁、资兴", loss: 520},
|
||||
{year: 2018, tags: ["大风"], area: "桂阳", loss: 80},
|
||||
{year: 2018, tags: ["大风", "暴雨", "城市内涝", "山洪", "泥石流",], area: "桂阳", loss: 80}
|
||||
].filter(d => d.year >= startYear.value);
|
||||
|
||||
summaryText.value = `${currentAreaLabel.value}在过去${backwardYears.value}年中,今日气候波动${tableData.value[0].rainfall > 40 ? '显著' : '平稳'}。`;
|
||||
nextTick(() => renderChart());
|
||||
}
|
||||
|
||||
function renderChart() {
|
||||
if (!chartInstance) chartInstance = echarts.init(mainChartRef.value);
|
||||
const xAxisData = isRegionalMode.value
|
||||
? tableData.value.map(d => d.stationName)
|
||||
: tableData.value.map(d => d.year).reverse();
|
||||
|
||||
chartInstance.setOption({
|
||||
tooltip: {trigger: 'axis'},
|
||||
legend: {bottom: 0},
|
||||
grid: {top: '15%', bottom: '15%', left: '5%', right: '5%'},
|
||||
xAxis: {type: 'category', data: xAxisData},
|
||||
yAxis: [{type: 'value', name: '降雨(mm)'}, {type: 'value', name: '气温(℃)', position: 'right', scale: true}],
|
||||
series: [
|
||||
{
|
||||
name: '降雨量', type: 'bar', barWidth: 20,
|
||||
data: isRegionalMode.value ? tableData.value.map(d => d.rainfall) : tableData.value.map(d => d.rainfall).reverse(),
|
||||
itemStyle: {color: '#409EFF'},
|
||||
label: {show: true, position: 'top', fontSize: 10}
|
||||
},
|
||||
{
|
||||
name: '最高气温', type: 'line', yAxisIndex: 1, smooth: true,
|
||||
data: isRegionalMode.value ? tableData.value.map(d => d.tmax) : tableData.value.map(d => d.tmax).reverse(),
|
||||
itemStyle: {color: '#F56C6C'},
|
||||
label: {show: true, position: 'top'}
|
||||
}
|
||||
]
|
||||
}, true);
|
||||
}
|
||||
|
||||
const handleModeChange = () => {
|
||||
isTableExpanded.value = false;
|
||||
refreshData();
|
||||
};
|
||||
|
||||
import html2canvas from 'html2canvas';
|
||||
import jsPDF from 'jspdf';
|
||||
|
||||
const exportRef = ref(null); // 在 template 最外层 div 绑定 ref="exportRef"
|
||||
|
||||
const handleExport = async (type = 'png') => {
|
||||
const element = document.querySelector('.container'); // 或者使用 ref
|
||||
const canvas = await html2canvas(element, {
|
||||
useCORS: true, // 允许跨域图片
|
||||
scale: 2, // 提高清晰度
|
||||
backgroundColor: '#f4f7f9' // 保持背景色
|
||||
});
|
||||
|
||||
if (type === 'png') {
|
||||
// 导出图片
|
||||
const link = document.createElement('a');
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.download = `气象回溯_${today.value.month}月${today.value.day}日.png`;
|
||||
link.click();
|
||||
} else {
|
||||
// 导出 PDF
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const imgProps = pdf.getImageProperties(imgData);
|
||||
const pdfWidth = pdf.internal.pageSize.getWidth();
|
||||
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
|
||||
pdf.save(`气象回溯_${today.value.month}月${today.value.day}日.pdf`);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
refreshData();
|
||||
window.addEventListener('resize', () => chartInstance?.resize());
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background: #f4f7f9;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 头部布局 */
|
||||
.header-card {
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-main-zone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 日历图标 */
|
||||
.calendar-icon-box {
|
||||
width: 54px;
|
||||
height: 58px;
|
||||
background: #fff;
|
||||
border: 2px solid #409EFF;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cal-top {
|
||||
background: #409EFF;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.cal-body {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: #303133;
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
/* 标题区 */
|
||||
.title-text-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 内联选择器样式 */
|
||||
.inline-select {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
:deep(.inline-select .el-input__inner) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0 5px;
|
||||
color: #409EFF;
|
||||
font-weight: bold;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
:deep(.inline-select .el-input__suffix) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏小箭头以保持简洁,或保留 */
|
||||
|
||||
.info-item i {
|
||||
margin-right: 3px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.year-select {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
/* 模块样式 */
|
||||
.module-card {
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-cell {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: #fffbe6;
|
||||
border-left: 5px solid #faad14;
|
||||
}
|
||||
|
||||
/* 灾情卡片内部容器 */
|
||||
.disaster-card :deep(.el-card__body) {
|
||||
padding-top: 10px; /* 进一步确保 body 顶部有呼吸感 */
|
||||
}
|
||||
|
||||
/* 时间线整体样式优化 */
|
||||
.custom-timeline {
|
||||
padding-top: 25px; /* 关键:拉开与卡片标题的距离 */
|
||||
padding-left: 5px; /* 关键:让时间线尽可能靠左对齐 */
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 调整时间线内部每一项的间距 */
|
||||
.custom-timeline :deep(.el-timeline-item) {
|
||||
padding-bottom: 25px; /* 拉开每条灾情之间的垂直距离 */
|
||||
}
|
||||
|
||||
/* 时间戳样式微调 */
|
||||
.custom-timeline :deep(.el-timeline-item__timestamp) {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
margin-bottom: 12px; /* 时间戳与下方灾情盒子的间距 */
|
||||
}
|
||||
|
||||
/* 灾情内容盒子微调 */
|
||||
.disaster-box {
|
||||
background: #f8f9fb;
|
||||
padding: 15px; /* 增加内部填充,显得不拥挤 */
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
margin-left: -5px; /* 配合靠左对齐,微调内容位置 */
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.loss-val {
|
||||
color: #cf1322;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<iframe class="iframe" :src="url" @load="load"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, watch } from "vue";
|
||||
import { RouteLocationNormalizedLoaded, useRoute } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const url = ref("about:_blank");
|
||||
const loading = ref(false);
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(vl) => {
|
||||
if (vl.path === "/iframe") {
|
||||
setUrl(vl);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
setUrl(route);
|
||||
});
|
||||
const setUrl = (route: RouteLocationNormalizedLoaded): void => {
|
||||
const { meta, query } = route;
|
||||
loading.value = true;
|
||||
if (query.url) {
|
||||
url.value = query.url as string;
|
||||
} else {
|
||||
if (meta && meta.isIframe) {
|
||||
url.value = meta.url as string;
|
||||
}
|
||||
}
|
||||
};
|
||||
const load = () => {
|
||||
loading.value = false;
|
||||
};
|
||||
return { url, loading, load };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.iframe {
|
||||
min-height: calc(100vh - 130px);
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()" label-width="120px">
|
||||
<el-form-item prop="beanName" label="bean名称">
|
||||
<el-input v-model="dataForm.beanName" placeholder="spring bean名称, 如: testTask"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="params" label="参数">
|
||||
<el-input v-model="dataForm.params" placeholder="参数"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="cronExpression" label="cron表达式">
|
||||
<el-input v-model="dataForm.cronExpression" placeholder="如: 0 0 12 * * ?"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="备注">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessage } from "element-plus";
|
||||
const emit = defineEmits(["refreshDataList"]);
|
||||
|
||||
const visible = ref(false);
|
||||
const dataFormRef = ref();
|
||||
|
||||
const dataForm = reactive({
|
||||
id: "",
|
||||
beanName: "",
|
||||
params: "",
|
||||
cronExpression: "",
|
||||
remark: "",
|
||||
status: 0
|
||||
});
|
||||
|
||||
const rules = ref({
|
||||
beanName: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
cronExpression: [{ required: true, message: "必填项不能为空", trigger: "blur" }]
|
||||
});
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true;
|
||||
dataForm.id = "";
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields();
|
||||
}
|
||||
|
||||
if (id) {
|
||||
getInfo(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取信息
|
||||
const getInfo = (id: number) => {
|
||||
baseService.get(`/sys/schedule/${id}`).then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const dataFormSubmitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
const fn = !dataForm.id ? baseService.post("/sys/schedule", dataForm) : baseService.put("/sys/schedule", dataForm);
|
||||
fn.then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false;
|
||||
emit("refreshDataList");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.schedule-cron {
|
||||
&-popover {
|
||||
width: auto !important;
|
||||
min-width: 550px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="日志列表" :close-on-click-modal="false" :close-on-press-escape="false" width="75%">
|
||||
<el-form :inline="true" :model="state.dataForm" @keyup.enter="state.getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="state.dataForm.jobId" placeholder="任务ID" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="state.getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="state.dataListLoading" :data="state.dataList" border @sort-change="state.dataListSortChangeHandle" height="460" style="width: 100%">
|
||||
<el-table-column prop="jobId" label="任务ID" header-align="center" align="center" width="180"></el-table-column>
|
||||
<el-table-column prop="beanName" label="bean名称" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="params" label="参数" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" header-align="center" align="center">
|
||||
<template v-slot="scope">
|
||||
<el-tag v-if="scope.row.status === 1" size="small">成功</el-tag>
|
||||
<el-tag v-else type="danger" size="small" @click="showErrorInfo(scope.row.id)" style="cursor: pointer">失败</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="times" label="耗时(单位: 毫秒)" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="createDate" label="执行时间" header-align="center" align="center" width="180"></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useView from "@/hooks/useView";
|
||||
import { reactive, ref, toRefs } from "vue";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
const view = reactive({
|
||||
getDataListURL: "/sys/scheduleLog/page",
|
||||
getDataListIsPage: true,
|
||||
dataForm: {
|
||||
jobId: ""
|
||||
}
|
||||
});
|
||||
|
||||
const state = reactive({ ...useView(view), ...toRefs(view) });
|
||||
|
||||
const init = () => {
|
||||
visible.value = true;
|
||||
state.getDataList();
|
||||
};
|
||||
|
||||
// 失败信息
|
||||
const showErrorInfo = (id: string) => {
|
||||
baseService.get(`/sys/scheduleLog/${id}`).then((res) => {
|
||||
ElMessageBox.alert(res.data.error);
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="mod-job__schedule">
|
||||
<el-form :inline="true" :model="state.dataForm" @keyup.enter="state.getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="state.dataForm.beanName" placeholder="bean名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="state.getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:delete')" type="danger" @click="state.deleteHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:pause')" type="danger" @click="pauseHandle()">暂停</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:resume')" type="danger" @click="resumeHandle()">恢复</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:run')" type="danger" @click="runHandle()">执行</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:log')" type="success" @click="logHandle()">日志列表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="state.dataListLoading" :data="state.dataList" border @selection-change="state.dataListSelectionChangeHandle" @sort-change="state.dataListSortChangeHandle" style="width: 100%">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
|
||||
<el-table-column prop="beanName" label="bean名称" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="params" label="参数" show-overflow-tooltip header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="cronExpression" label="cron表达式" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" show-overflow-tooltip header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" sortable="custom" header-align="center" align="center">
|
||||
<template v-slot="scope">
|
||||
<el-tag v-if="scope.row.status === 1" size="small">正常</el-tag>
|
||||
<el-tag v-else size="small" type="danger">暂停</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="248">
|
||||
<template v-slot="scope">
|
||||
<el-button v-if="state.hasPermission('sys:schedule:update')" type="primary" link @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:pause')" type="primary" link @click="pauseHandle(scope.row.id)">暂停</el-button>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:resume')" type="primary" link @click="resumeHandle(scope.row.id)">恢复</el-button>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:run')" type="primary" link @click="runHandle(scope.row.id)">执行</el-button>
|
||||
<el-button v-if="state.hasPermission('sys:schedule:delete')" type="primary" link @click="state.deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update ref="addOrUpdateRef" @refreshDataList="state.getDataList"></add-or-update>
|
||||
<!-- 弹窗, 日志列表 -->
|
||||
<log ref="logRef"></log>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useView from "@/hooks/useView";
|
||||
import { reactive, ref, toRefs } from "vue";
|
||||
import baseService from "@/service/baseService";
|
||||
import { IObject } from "@/types/interface";
|
||||
import AddOrUpdate from "./schedule-add-or-update.vue";
|
||||
import Log from "./schedule-log.vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
const emit = defineEmits(["refreshDataList"]);
|
||||
|
||||
const logRef = ref();
|
||||
|
||||
const view = reactive({
|
||||
getDataListURL: "/sys/schedule/page",
|
||||
getDataListIsPage: true,
|
||||
deleteURL: "/sys/schedule",
|
||||
deleteIsBatch: true,
|
||||
dataForm: {
|
||||
beanName: ""
|
||||
}
|
||||
});
|
||||
|
||||
const state = reactive({ ...useView(view), ...toRefs(view) });
|
||||
|
||||
// 暂停
|
||||
const pauseHandle = (id?: string) => {
|
||||
if (!id && state.dataListSelections && state.dataListSelections.length <= 0) {
|
||||
return ElMessage({
|
||||
message: "请选择操作项",
|
||||
type: "warning",
|
||||
duration: 500
|
||||
});
|
||||
}
|
||||
ElMessageBox.confirm("确定进行[暂停]操作?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
baseService.put("/sys/schedule/pause", id ? [id] : state.dataListSelections && state.dataListSelections.map((item: IObject) => item.id)).then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
state.getDataList();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
});
|
||||
};
|
||||
|
||||
// 恢复
|
||||
const resumeHandle = (id?: string) => {
|
||||
if (!id && state.dataListSelections && state.dataListSelections.length <= 0) {
|
||||
return ElMessage({
|
||||
message: "请选择操作项",
|
||||
type: "warning",
|
||||
duration: 500
|
||||
});
|
||||
}
|
||||
ElMessageBox.confirm("确定进行[恢复]操作?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
baseService.put("/sys/schedule/resume", id ? [id] : state.dataListSelections && state.dataListSelections.map((item: IObject) => item.id)).then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
state.getDataList();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
});
|
||||
};
|
||||
|
||||
// 执行
|
||||
const runHandle = (id?: string) => {
|
||||
if (!id && state.dataListSelections && state.dataListSelections.length <= 0) {
|
||||
return ElMessage({
|
||||
message: "请选择操作项",
|
||||
type: "warning",
|
||||
duration: 500
|
||||
});
|
||||
}
|
||||
ElMessageBox.confirm("确定进行[执行]操作?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
baseService.put("/sys/schedule/run", id ? [id] : state.dataListSelections && state.dataListSelections.map((item: IObject) => item.id)).then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
state.getDataList();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
});
|
||||
};
|
||||
|
||||
// 日志列表
|
||||
const logHandle = () => {
|
||||
logRef.value.init();
|
||||
};
|
||||
|
||||
const addOrUpdateRef = ref();
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="rr-login">
|
||||
<div class="rr-login-wrap">
|
||||
<div class="rr-login-left hidden-sm-and-down">
|
||||
<p class="rr-login-left-title">气象数据管理系统</p>
|
||||
</div>
|
||||
|
||||
<div class="rr-login-right">
|
||||
<div class="rr-login-right-main">
|
||||
<h4 class="rr-login-right-main-title">登录</h4>
|
||||
<el-form ref="formRef" label-width="80px" :status-icon="true" :model="login" :rules="rules" @keyup.enter="onLogin">
|
||||
<el-form-item label-width="0" prop="username">
|
||||
<el-input v-model="login.username" placeholder="用户名" prefix-icon="user" autocomplete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0" prop="password">
|
||||
<el-input placeholder="密码" v-model="login.password" prefix-icon="lock" autocomplete="off" show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0" prop="captcha">
|
||||
<el-space class="rr-login-right-main-code">
|
||||
<el-input v-model="login.captcha" placeholder="验证码" prefix-icon="first-aid-kit"></el-input>
|
||||
<img style="vertical-align: middle; height: 40px; cursor: pointer" :src="state.captchaUrl" @click="onRefreshCode" alt="" />
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<el-button type="primary" size="small" :disabled="state.loading" @click="onLogin" class="rr-login-right-main-btn"> 登录 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-footer">
|
||||
<p><a href="https://www.renren.io/enterprise" target="_blank">企业版</a> | <a href="https://www.renren.io/cloud" target="_blank">微服务版</a></p>
|
||||
<p><a href="https://www.renren.io/" target="_blank">人人开源</a>{{ state.year }} © renren.io</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { CacheToken } from "@/constants/cacheKey";
|
||||
import baseService from "@/service/baseService";
|
||||
import { setCache } from "@/utils/cache";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { getUuid } from "@/utils/utils";
|
||||
import app from "@/constants/app";
|
||||
import SvgIcon from "@/components/base/svg-icon/index";
|
||||
import { useAppStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const store = useAppStore();
|
||||
const router = useRouter();
|
||||
|
||||
const state = reactive({
|
||||
captchaUrl: "",
|
||||
loading: false,
|
||||
year: new Date().getFullYear()
|
||||
});
|
||||
|
||||
const login = reactive({ username: "", password: "", captcha: "", uuid: "" });
|
||||
|
||||
onMounted(() => {
|
||||
//清理数据
|
||||
store.logout();
|
||||
getCaptchaUrl();
|
||||
});
|
||||
const formRef = ref();
|
||||
|
||||
const rules = ref({
|
||||
username: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
password: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
captcha: [{ required: true, message: "必填项不能为空", trigger: "blur" }]
|
||||
});
|
||||
|
||||
const getCaptchaUrl = () => {
|
||||
login.uuid = getUuid();
|
||||
state.captchaUrl = `${app.api}/captcha?uuid=${login.uuid}`;
|
||||
};
|
||||
|
||||
const onRefreshCode = () => {
|
||||
getCaptchaUrl();
|
||||
};
|
||||
|
||||
const onLogin = () => {
|
||||
formRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
state.loading = true;
|
||||
baseService
|
||||
.post("/login", login)
|
||||
.then((res) => {
|
||||
state.loading = false;
|
||||
if (res.code === 0) {
|
||||
setCache(CacheToken, res.data, true);
|
||||
ElMessage.success("登录成功");
|
||||
router.push("/");
|
||||
} else {
|
||||
ElMessage.error(res.msg);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
state.loading = false;
|
||||
onRefreshCode();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url("@/assets/theme/base.less");
|
||||
.rr-login {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #019ec4;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.rr-login-wrap {
|
||||
width: 96% !important;
|
||||
}
|
||||
.rr-login-right {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-wrap {
|
||||
margin: 0 auto;
|
||||
width: 1000px;
|
||||
box-shadow: -4px 5px 10px rgba(0, 0, 0, 0.4);
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: both;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-left {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: @--color-primary;
|
||||
color: #fff;
|
||||
float: left;
|
||||
width: 50%;
|
||||
|
||||
&-title {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
letter-spacing: 2px;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
border-left: none;
|
||||
color: #fff;
|
||||
background-color: #fff;
|
||||
width: 50%;
|
||||
float: left;
|
||||
|
||||
&-main {
|
||||
margin: 0 auto;
|
||||
width: 65%;
|
||||
&-title {
|
||||
color: #333;
|
||||
margin-bottom: 40px;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
&-lang .iconfont {
|
||||
font-size: 20px;
|
||||
color: #606266;
|
||||
font-weight: 800;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
&-code {
|
||||
width: 100%;
|
||||
.el-space__item:first-child {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
&-btn {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
font-size: 18px !important;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 300 !important;
|
||||
cursor: pointer;
|
||||
margin-top: 30px;
|
||||
font-family: neo, sans-serif;
|
||||
transition: 0.25s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 20px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
a {
|
||||
padding: 0 5px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
position: relative;
|
||||
min-height: 500px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes animate-down {
|
||||
0%,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -3000px, 0);
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 25px, 0);
|
||||
}
|
||||
75% {
|
||||
transform: translate3d(0, -10px, 0);
|
||||
}
|
||||
90% {
|
||||
transform: translate3d(0, 5px, 0);
|
||||
}
|
||||
to {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-down {
|
||||
animation-name: animate-down;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="云存储配置" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()" label-width="140px">
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="dataForm.type">
|
||||
<el-radio :label="1">七牛</el-radio>
|
||||
<el-radio :label="2">阿里云</el-radio>
|
||||
<el-radio :label="3">腾讯云</el-radio>
|
||||
<el-radio :label="4">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<template v-if="dataForm.type === 1">
|
||||
<el-form-item prop="qiniuDomain" label="域名">
|
||||
<el-input v-model="dataForm.qiniuDomain" placeholder="七牛绑定的域名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qiniuPrefix" label="路径前缀">
|
||||
<el-input v-model="dataForm.qiniuPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qiniuAccessKey" label="AccessKey">
|
||||
<el-input v-model="dataForm.qiniuAccessKey" placeholder="七牛AccessKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qiniuSecretKey" label="SecretKey">
|
||||
<el-input v-model="dataForm.qiniuSecretKey" placeholder="七牛SecretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qiniuBucketName" label="空间名">
|
||||
<el-input v-model="dataForm.qiniuBucketName" placeholder="七牛存储空间名"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="dataForm.type === 2">
|
||||
<el-form-item prop="aliyunDomain" label="域名">
|
||||
<el-input v-model="dataForm.aliyunDomain" placeholder="阿里云绑定的域名,如:http://cdn.renren.io"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunPrefix" label="路径前缀">
|
||||
<el-input v-model="dataForm.aliyunPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunEndPoint" label="EndPoint">
|
||||
<el-input v-model="dataForm.aliyunEndPoint" placeholder="阿里云EndPoint"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunAccessKeyId" label="AccessKeyId">
|
||||
<el-input v-model="dataForm.aliyunAccessKeyId" placeholder="阿里云AccessKeyId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunAccessKeySecret" label="AccessKeySecret">
|
||||
<el-input v-model="dataForm.aliyunAccessKeySecret" placeholder="阿里云AccessKeySecret"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunBucketName" label="BucketName">
|
||||
<el-input v-model="dataForm.aliyunBucketName" placeholder="阿里云BucketName"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="dataForm.type === 3">
|
||||
<el-form-item prop="qcloudDomain" label="域名">
|
||||
<el-input v-model="dataForm.qcloudDomain" placeholder="腾讯云绑定的域名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qcloudPrefix" label="路径前缀">
|
||||
<el-input v-model="dataForm.qcloudPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qcloudAppId" label="AppId">
|
||||
<el-input v-model="dataForm.qcloudAppId" placeholder="腾讯云AppId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qcloudSecretId" label="SecretId">
|
||||
<el-input v-model="dataForm.qcloudSecretId" placeholder="腾讯云SecretId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qcloudSecretKey" label="SecretKey">
|
||||
<el-input v-model="dataForm.qcloudSecretKey" placeholder="腾讯云SecretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qcloudBucketName" label="BucketName">
|
||||
<el-input v-model="dataForm.qcloudBucketName" placeholder="腾讯云BucketName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="qcloudRegion" label="所属地区">
|
||||
<el-select v-model="dataForm.qcloudRegion" clearable placeholder="请选择" class="w-percent-100">
|
||||
<el-option value="ap-beijing-1" label="北京一区(华北)"></el-option>
|
||||
<el-option value="ap-beijing" label="北京"></el-option>
|
||||
<el-option value="ap-shanghai" label="上海(华东)"></el-option>
|
||||
<el-option value="ap-guangzhou" label="广州(华南)"></el-option>
|
||||
<el-option value="ap-chengdu" label="成都(西南)"></el-option>
|
||||
<el-option value="ap-chongqing" label="重庆"></el-option>
|
||||
<el-option value="ap-singapore" label="新加坡"></el-option>
|
||||
<el-option value="ap-hongkong" label="香港"></el-option>
|
||||
<el-option value="na-toronto" label="多伦多"></el-option>
|
||||
<el-option value="eu-frankfurt" label="法兰克福"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="dataForm.type === 4">
|
||||
<el-form-item prop="aliyunDomain" label="域名">
|
||||
<el-input v-model="dataForm.aliyunDomain" placeholder="阿里云绑定的域名,如:http://cdn.renren.io"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunPrefix" label="路径前缀">
|
||||
<el-input v-model="dataForm.aliyunPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunEndPoint" label="EndPoint">
|
||||
<el-input v-model="dataForm.aliyunEndPoint" placeholder="阿里云EndPoint"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunAccessKeyId" label="AccessKeyId">
|
||||
<el-input v-model="dataForm.aliyunAccessKeyId" placeholder="阿里云AccessKeyId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunAccessKeySecret" label="AccessKeySecret">
|
||||
<el-input v-model="dataForm.aliyunAccessKeySecret" placeholder="阿里云AccessKeySecret"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="aliyunBucketName" label="BucketName">
|
||||
<el-input v-model="dataForm.aliyunBucketName" placeholder="阿里云BucketName"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import baseService from "@/service/baseService";
|
||||
import { ElMessage } from "element-plus";
|
||||
const emit = defineEmits(["refreshDataList"]);
|
||||
|
||||
const visible = ref(false);
|
||||
const dataFormRef = ref();
|
||||
|
||||
const dataForm = reactive({
|
||||
type: 0,
|
||||
qiniuDomain: "",
|
||||
qiniuPrefix: "",
|
||||
qiniuAccessKey: "",
|
||||
qiniuSecretKey: "",
|
||||
qiniuBucketName: "",
|
||||
aliyunDomain: "",
|
||||
aliyunPrefix: "",
|
||||
aliyunEndPoint: "",
|
||||
aliyunAccessKeyId: "",
|
||||
aliyunAccessKeySecret: "",
|
||||
aliyunBucketName: "",
|
||||
qcloudDomain: "",
|
||||
qcloudPrefix: "",
|
||||
qcloudAppId: 0,
|
||||
qcloudSecretId: "",
|
||||
qcloudSecretKey: "",
|
||||
qcloudBucketName: "",
|
||||
qcloudRegion: ""
|
||||
});
|
||||
|
||||
const rules = ref({
|
||||
qiniuDomain: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qiniuAccessKey: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qiniuSecretKey: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qiniuBucketName: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
aliyunDomain: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
aliyunEndPoint: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
aliyunAccessKeyId: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
aliyunAccessKeySecret: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
aliyunBucketName: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qcloudDomain: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qcloudAppId: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qcloudSecretId: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qcloudSecretKey: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qcloudBucketName: [{ required: true, message: "必填项不能为空", trigger: "blur" }],
|
||||
qcloudRegion: [{ required: true, message: "必填项不能为空", trigger: "blur" }]
|
||||
});
|
||||
|
||||
const init = () => {
|
||||
visible.value = true;
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields();
|
||||
}
|
||||
|
||||
getInfo();
|
||||
};
|
||||
|
||||
// 获取信息
|
||||
const getInfo = () => {
|
||||
baseService.get("/sys/oss/info").then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const dataFormSubmitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
baseService.post("/sys/oss", dataForm).then((res) => {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false;
|
||||
emit("refreshDataList");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="上传文件" :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-upload :action="url" :file-list="fileList" drag multiple :before-upload="beforeUploadHandle" :on-success="successHandle" class="text-center">
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或点击上传</div>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">只支持jpg、png、gif格式文件!</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { getToken } from "@/utils/cache";
|
||||
import { IObject } from "@/types/interface";
|
||||
import app from "@/constants/app";
|
||||
import { ElMessage } from "element-plus";
|
||||
const emit = defineEmits(["refreshDataList"]);
|
||||
|
||||
const visible = ref(false);
|
||||
const url = ref("");
|
||||
const num = ref(0);
|
||||
const fileList = ref<IObject[]>();
|
||||
|
||||
const init = () => {
|
||||
visible.value = true;
|
||||
url.value = `${app.api}/sys/oss/upload?token=${getToken()}`;
|
||||
num.value = 0;
|
||||
fileList.value = [];
|
||||
};
|
||||
|
||||
// 上传之前
|
||||
const beforeUploadHandle = (file: IObject) => {
|
||||
if (file.type !== "image/jpg" && file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "image/gif") {
|
||||
ElMessage.error("只支持jpg、png、gif格式文件!");
|
||||
return false;
|
||||
}
|
||||
num.value++;
|
||||
};
|
||||
|
||||
// 上传成功
|
||||
const successHandle = (res: IObject, file: IObject, list: IObject[]) => {
|
||||
if (res.code !== 0) {
|
||||
return ElMessage.error(res.msg);
|
||||
}
|
||||
|
||||
fileList.value = list;
|
||||
num.value--;
|
||||
if (num.value === 0) {
|
||||
ElMessage.success({
|
||||
message: "成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false;
|
||||
emit("refreshDataList");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="mod-oss__oss">
|
||||
<el-form :inline="true" :model="state.dataForm">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="configHandle()">云存储配置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="uploadHandle()">上传文件</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" @click="state.deleteHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="state.dataListLoading" :data="state.dataList" border @selection-change="state.dataListSelectionChangeHandle" @sort-change="state.dataListSortChangeHandle" style="width: 100%">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
|
||||
<el-table-column prop="url" label="URL地址" header-align="center" align="center"></el-table-column>
|
||||
<el-table-column prop="createDate" label="创建时间" sortable="custom" header-align="center" align="center" width="180"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="150">
|
||||
<template v-slot="scope">
|
||||
<el-button type="primary" link @click="state.deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
|
||||
<!-- 弹窗, 云存储配置 -->
|
||||
<config v-if="state.configVisible" ref="configRef"></config>
|
||||
<!-- 弹窗, 上传文件 -->
|
||||
<upload v-if="state.uploadVisible" ref="uploadRef" @refreshDataList="state.getDataList"></upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useView from "@/hooks/useView";
|
||||
import { nextTick, reactive, ref, toRefs } from "vue";
|
||||
import Config from "./oss-config.vue";
|
||||
import Upload from "./oss-upload.vue";
|
||||
|
||||
const configRef = ref();
|
||||
const uploadRef = ref();
|
||||
|
||||
const view = reactive({
|
||||
getDataListURL: "/sys/oss/page",
|
||||
getDataListIsPage: true,
|
||||
deleteURL: "/sys/oss",
|
||||
deleteIsBatch: true,
|
||||
dataForm: {},
|
||||
configVisible: false,
|
||||
uploadVisible: false
|
||||
});
|
||||
|
||||
const state = reactive({ ...useView(view), ...toRefs(view) });
|
||||
|
||||
// 云存储配置
|
||||
const configHandle = () => {
|
||||
state.configVisible = true;
|
||||
nextTick(() => {
|
||||
configRef.value.init();
|
||||
});
|
||||
};
|
||||
|
||||
// 上传文件
|
||||
const uploadHandle = () => {
|
||||
state.uploadVisible = true;
|
||||
nextTick(() => {
|
||||
uploadRef.value.init();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="region-viewer" style="padding: 20px;">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span style="font-weight: bold;">地区数据</span>
|
||||
<el-tag type="info">总数据量: {{ rawListCount }} 条</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 树形表格 -->
|
||||
<el-table
|
||||
v-if="treeData.length > 0"
|
||||
:data="treeData"
|
||||
style="width: 100%; max-height: 600px; overflow: auto;"
|
||||
row-key="id"
|
||||
border
|
||||
:tree-props="{ children: 'children' }"
|
||||
>
|
||||
<el-table-column prop="id" label="编号 (邮编)" width="220" />
|
||||
<el-table-column prop="name" label="地名" />
|
||||
</el-table>
|
||||
|
||||
<el-empty v-else :description="loadingStatus" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
|
||||
const rawList = ref<any[]>([]);
|
||||
const treeData = ref<any[]>([]);
|
||||
const loadingStatus = ref('正在解析数据...');
|
||||
|
||||
const rawListCount = computed(() => rawList.value.length);
|
||||
|
||||
// 优化后的构建树函数
|
||||
const buildTreeMap = (list: any[]) => {
|
||||
const map = new Map<number, any[]>();
|
||||
|
||||
// 构建 parentId -> children 的 Map
|
||||
list.forEach(item => {
|
||||
const pid = item.parentid;
|
||||
if (!map.has(pid)) map.set(pid, []);
|
||||
map.get(pid)!.push({ ...item });
|
||||
});
|
||||
|
||||
// 递归构建树
|
||||
const createTree = (pId: number): any[] => {
|
||||
const children = map.get(pId) || [];
|
||||
children.forEach(child => {
|
||||
const sub = createTree(child.id);
|
||||
if (sub.length > 0) child.children = sub;
|
||||
});
|
||||
return children;
|
||||
};
|
||||
|
||||
// 自动探测根节点
|
||||
const rootParentId = Math.min(...list.map(r => r.parentid));
|
||||
return createTree(rootParentId);
|
||||
};
|
||||
|
||||
const initRegionTree = async () => {
|
||||
try {
|
||||
const res = await fetch("/region.csv");
|
||||
if (!res.ok) throw new Error("无法读取数据,请检查文件");
|
||||
|
||||
const text = await res.text();
|
||||
const lines = text.split("\n").map(l => l.trim()).filter(l => l);
|
||||
if (lines.length < 2) throw new Error("内容为空或格式错误");
|
||||
|
||||
const headers = lines[0].split(",").map(h => h.trim().toLowerCase());
|
||||
|
||||
rawList.value = lines.slice(1).map(line => {
|
||||
const cols = line.split(",").map(c => c.trim());
|
||||
const obj: any = {};
|
||||
headers.forEach((h, i) => {
|
||||
obj[h] = h === 'name' ? cols[i] : Number(cols[i]);
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
|
||||
// 构建树
|
||||
const tree = buildTreeMap(rawList.value);
|
||||
if (tree.length === 0) {
|
||||
loadingStatus.value = '构建失败:根节点下无数据';
|
||||
} else {
|
||||
treeData.value = tree;
|
||||
loadingStatus.value = '加载完成';
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
loadingStatus.value = `错误: ${error.message}`;
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(initRegionTree);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.region-viewer .el-table {
|
||||
/* 虚拟滚动样式 */
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||