push
This commit is contained in:
101
lib/paas/view/service/add-form.vue
Normal file
101
lib/paas/view/service/add-form.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div :key="'addForm_' + $route.name">
|
||||
<a-spin :spinning="isLoading">
|
||||
<page-title :title="title" />
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<!-- todo 隐藏取消-->
|
||||
<a-button @click="navigateBack">返回</a-button>
|
||||
<a-button type="primary" @click="submit" :disabled="!nsFormElRef?.validateResult">
|
||||
保存</a-button
|
||||
>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="add-form">
|
||||
<ns-form ref="nsFormElRef" v-bind="getBindValue">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-form>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { formProps } from '/nerv-lib/component/form/form/props';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
import { useApi, HttpRequestConfig } from '/nerv-lib/use/use-api';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewAddForm',
|
||||
props: {
|
||||
...formProps,
|
||||
api: PropTypes.string | PropTypes.object,
|
||||
title: PropTypes.string,
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsFormElRef = ref();
|
||||
const isLoading = ref(false);
|
||||
const { navigateBack } = useNavigate();
|
||||
const { httpRequest } = useApi();
|
||||
const route = useRoute();
|
||||
|
||||
function submit() {
|
||||
nsFormElRef.value
|
||||
.triggerSubmit()
|
||||
.then((data: Recordable) => {
|
||||
isLoading.value = true;
|
||||
const { api } = props;
|
||||
const { params } = route;
|
||||
const requestConfig: HttpRequestConfig = { method: 'POST' };
|
||||
httpRequest({ api, params: data, pathParams: params, requestConfig })
|
||||
.then(() => {
|
||||
isLoading.value = false;
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
navigateBack();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => ({}));
|
||||
}
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
}));
|
||||
return { nsFormElRef, submit, getBindValue, isLoading, navigateBack };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ant-page-header {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
:deep(.ant-page-header-heading-extra) {
|
||||
margin-right: auto !important;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.add-form {
|
||||
padding: 0 24px;
|
||||
}
|
||||
:deep(
|
||||
.ant-table-thead
|
||||
> tr
|
||||
> th:not(:last-child):not(.ant-table-selection-column):not(
|
||||
.ant-table-row-expand-icon-cell
|
||||
):not([colspan])::before
|
||||
) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
303
lib/paas/view/service/detail.vue
Normal file
303
lib/paas/view/service/detail.vue
Normal file
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div class="ns-skeleton" :key="'detail_' + $route.name">
|
||||
<page-title :title="getTitle" />
|
||||
<a-page-header v-if="showBack">
|
||||
<template #extra>
|
||||
<a-button @click="navigateBack">返回</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<Skeleton
|
||||
active
|
||||
v-for="(detailGroup, index) in getDetail"
|
||||
:key="index"
|
||||
:loading="loading"
|
||||
:paragraph="detailGroup.SkeletonParagraphProps"
|
||||
:title="detailGroup.SkeletonTitleProps">
|
||||
<div class="ns-detail">
|
||||
<a-descriptions :title="detailGroup.title">
|
||||
<template v-if="detailGroup.items">
|
||||
<template v-for="(item, index) in detailGroup.items" :key="index">
|
||||
<a-descriptions-item v-if="item.ifShow">
|
||||
<template #label v-if="item.label">
|
||||
<div v-if="item.showTip">
|
||||
<a-popover title="" class="detailLabelProver">
|
||||
<template #content>
|
||||
<span style="margin-top: 10px; white-space: pre-wrap">{{
|
||||
item.showTip
|
||||
}}</span>
|
||||
</template>
|
||||
<span style="margin-right: 8px">{{ item.label }}</span>
|
||||
<question-circle-outlined style="option: 0.8; cursor: pointer" /> </a-popover
|
||||
></div>
|
||||
<div v-else>{{ item.label }}</div>
|
||||
</template>
|
||||
|
||||
<!--默认-->
|
||||
|
||||
<template v-if="!item.type">
|
||||
<span class="ns-detail-text">{{ item.value }}</span>
|
||||
</template>
|
||||
<!--富文本-->
|
||||
<template v-if="item.type == 'html'">
|
||||
<div class="ns-detail-html" v-html="item.value"></div>
|
||||
</template>
|
||||
<!--链接-->
|
||||
|
||||
<template v-if="item.type == 'link'">
|
||||
<a class="ns-detail-link" target="_blank" :href="item.value">发票链接 </a>
|
||||
</template>
|
||||
<!--图片 值为数组则为图片列表 可继续扩展-->
|
||||
|
||||
<template v-if="item.type === 'image'">
|
||||
<template v-if="typeof item.value == 'object'">
|
||||
<div
|
||||
class="ns-detail-image-list"
|
||||
v-for="src in item.value"
|
||||
:key="src"
|
||||
style="display: inline-block; margin: 0 5px 5px 0">
|
||||
<a-image
|
||||
:width="100"
|
||||
:src="`/api/op/objs/ParkPic/${src}`"
|
||||
fallback="data:image/png;base64,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" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-image
|
||||
class="ns-detail-image"
|
||||
:width="100"
|
||||
:src="item.value"
|
||||
fallback="data:image/png;base64,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" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!--表格-->
|
||||
<template v-else-if="item.type === 'table'">
|
||||
<ns-basic-table
|
||||
class="ns-detail-table"
|
||||
:dataSource="dataRef[item.props.listField]"
|
||||
:columns="item.props.columns"
|
||||
:pagination="false"
|
||||
:rowKey="item.props.rowKey" />
|
||||
</template>
|
||||
|
||||
<template v-for="slot in Object.keys($slots)" :key="slot">
|
||||
<div v-if="item.type === slot" :class="`ns-detail-${slot}`">
|
||||
<slot :name="slot" v-bind="item || {}" :dataRef="dataRef" :basicInfo="item">
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
</a-descriptions-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, PropType, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, get, isFunction, isUndefined } from 'lodash-es';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import PageTitle from '/nerv-lib/paas/component/page-title/page-title.vue';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { useApi } from '/nerv-lib/use/use-api';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
|
||||
export interface DetailItem {
|
||||
label: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
ifShow?: Boolean | Function;
|
||||
format?: Function;
|
||||
type?: 'image' | 'table';
|
||||
props?: Recordable;
|
||||
}
|
||||
|
||||
export interface DetailGroup {
|
||||
title: string;
|
||||
items: Array<DetailItem>;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewDetail',
|
||||
props: {
|
||||
detail: {
|
||||
type: Array as PropType<DetailGroup[]>,
|
||||
default: () => [],
|
||||
},
|
||||
api: {
|
||||
type: [String, Object, Function] as PropType<string | Function | AxiosRequestConfig>,
|
||||
default: undefined,
|
||||
},
|
||||
title: PropTypes.string,
|
||||
showBack: PropTypes.bool.def(false),
|
||||
dataHandle: Function,
|
||||
},
|
||||
components: { PageTitle, Skeleton, QuestionCircleOutlined },
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
const dataRef = ref([]);
|
||||
const { navigateBack } = useNavigate();
|
||||
|
||||
let loading = ref<boolean>(true);
|
||||
|
||||
function request() {
|
||||
const { api } = props;
|
||||
if (api) {
|
||||
const { query, params } = route;
|
||||
const { httpRequest } = useApi();
|
||||
|
||||
httpRequest({ api, params: query, pathParams: params }).then((res) => {
|
||||
dataRef.value = props.dataHandle ? props.dataHandle(res) : res;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getTitle = computed(() => {
|
||||
const {
|
||||
params: { pageTitle },
|
||||
} = route;
|
||||
if (pageTitle) return pageTitle;
|
||||
const { title } = props;
|
||||
return title;
|
||||
});
|
||||
request();
|
||||
|
||||
const getDetail = computed(() => {
|
||||
const detail = cloneDeep(props.detail);
|
||||
return (detail as Array<DetailGroup>).map((group: DetailGroup) => {
|
||||
const SkeletonWidth = [];
|
||||
for (let i = 0; i < group.items.length; i++) {
|
||||
if (group.items[i].type === 'table') {
|
||||
SkeletonWidth.push('82%');
|
||||
} else {
|
||||
SkeletonWidth.push('20%');
|
||||
}
|
||||
}
|
||||
const { title, items } = group;
|
||||
return {
|
||||
title: title,
|
||||
items: items.map((item) => {
|
||||
const { ifShow } = item;
|
||||
item.value = get(dataRef.value, item.name);
|
||||
item.ifShow = isFunction(ifShow)
|
||||
? ifShow(dataRef.value)
|
||||
: isUndefined(ifShow)
|
||||
? true
|
||||
: ifShow;
|
||||
if (item.format) {
|
||||
item.value = item.format(item.value, dataRef.value);
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
SkeletonParagraphProps: {
|
||||
rows: items.length,
|
||||
width: SkeletonWidth,
|
||||
},
|
||||
SkeletonTitleProps: {
|
||||
width: 150,
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
dataRef,
|
||||
getDetail,
|
||||
loading,
|
||||
getTitle,
|
||||
navigateBack,
|
||||
request,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-page-header {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
:deep(.ant-page-header-heading-extra) {
|
||||
margin-right: auto !important;
|
||||
margin-left: 0;
|
||||
}
|
||||
:deep(.ant-skeleton-paragraph) {
|
||||
display: inline;
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph li:nth-child(n)) {
|
||||
float: left;
|
||||
display: inline;
|
||||
margin-right: 130px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph li:nth-child(3n + 1)) {
|
||||
clear: both;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-content) {
|
||||
padding: 0 8px 10px 10px;
|
||||
}
|
||||
|
||||
.ns-detail {
|
||||
padding: 0 24px 24px 24px;
|
||||
}
|
||||
|
||||
.ns-detail-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-table-wrapper) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ns-detail-html {
|
||||
:deep(table) {
|
||||
border-top: 1px solid #ffffff;
|
||||
border-left: 1px solid #ffffff;
|
||||
}
|
||||
:deep(th) {
|
||||
border-right: 1px solid #ffffff;
|
||||
font-size: 13px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
font-weight: normal;
|
||||
background: #eff0f2;
|
||||
}
|
||||
:deep(td) {
|
||||
border-top: 1px solid #ffffff;
|
||||
border-right: 1px solid #ffffff;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
font-size: 12px;
|
||||
color: #606060;
|
||||
text-align: center;
|
||||
:deep(text) {
|
||||
border-bottom: 1px solid #ffffff;
|
||||
}
|
||||
background: rgba(240, 242, 245, 0.5);
|
||||
}
|
||||
:deep(p) {
|
||||
font-size: 12px;
|
||||
color: #898e91;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
102
lib/paas/view/service/edit-form.vue
Normal file
102
lib/paas/view/service/edit-form.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div :key="'edieForm_' + $route.name">
|
||||
<page-title :title="title" />
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<!-- todo 隐藏取消-->
|
||||
<a-button @click="navigateBack">返回</a-button>
|
||||
<a-button type="primary" @click="submit" :disabled="!nsFormElRef?.validateResult"
|
||||
>保存</a-button
|
||||
>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="add-form">
|
||||
<ns-form ref="nsFormElRef" v-bind="getBindValue" :model="data">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { formProps } from '/nerv-lib/component/form/form/props';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { useApi, HttpRequestConfig } from '/nerv-lib/use/use-api';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewEditForm',
|
||||
props: {
|
||||
...formProps,
|
||||
api: PropTypes.string | PropTypes.object,
|
||||
getApi: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsFormElRef = ref();
|
||||
const router = useRouter();
|
||||
const { httpRequest } = useApi();
|
||||
const route = useRoute();
|
||||
const { navigateBack } = useNavigate();
|
||||
|
||||
const data = ref<Recordable>({});
|
||||
|
||||
const request = () => {
|
||||
const { api, getApi } = props;
|
||||
const { params, query } = route;
|
||||
const requestConfig: HttpRequestConfig = { method: 'get' };
|
||||
httpRequest({
|
||||
api: getApi ? getApi : api,
|
||||
params: query,
|
||||
pathParams: params,
|
||||
requestConfig,
|
||||
}).then((res: Recordable) => {
|
||||
data.value = res;
|
||||
});
|
||||
};
|
||||
|
||||
request();
|
||||
function submit() {
|
||||
nsFormElRef.value
|
||||
.triggerSubmit()
|
||||
.then((data: Recordable) => {
|
||||
const { api } = props;
|
||||
const { params } = route;
|
||||
const requestConfig: HttpRequestConfig = { method: 'PUT' };
|
||||
httpRequest({ api, params: data, pathParams: params, requestConfig }).then(() => {
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
navigateBack();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => ({}));
|
||||
}
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
}));
|
||||
return { nsFormElRef, submit, data, getBindValue, navigateBack };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ant-page-header {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
:deep(.ant-page-header-heading-extra) {
|
||||
margin-right: auto !important;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.add-form {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
74
lib/paas/view/service/empty.vue
Normal file
74
lib/paas/view/service/empty.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div :key="'empty_' + $route.name">
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<a-button @click="onBack">返回</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="page-404">
|
||||
<h2></h2>
|
||||
<h3>该页面还在开发中</h3>
|
||||
<p>请点击链接继续浏览</p>
|
||||
<div class="op-list">
|
||||
<router-link :to="{ name: 'root' }">返回首页</router-link>
|
||||
<span>|</span>
|
||||
<ns-button type="link" @click="onBack">返回上页</ns-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewEmpty',
|
||||
|
||||
setup() {},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-404 {
|
||||
margin: 150px auto 0;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-size: 160px;
|
||||
letter-spacing: 20px;
|
||||
color: @primary-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: 10px 0;
|
||||
font-size: 36px;
|
||||
letter-spacing: 3px;
|
||||
color: @text-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
|
||||
.op-list {
|
||||
border-top: 1px solid @border-color-base;
|
||||
width: 76%;
|
||||
margin: 0 auto;
|
||||
|
||||
span {
|
||||
padding-left: 16px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
lib/paas/view/service/error-403.vue
Normal file
74
lib/paas/view/service/error-403.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div :key="'error403_' + $route.name">
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<a-button @click="onBack">返回</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="page-404">
|
||||
<h2>403</h2>
|
||||
<h3>您没有该页面的权限</h3>
|
||||
<p>请联系管理员,或者点击链接继续浏览</p>
|
||||
<div class="op-list">
|
||||
<router-link :to="{ name: 'root' }">返回首页</router-link>
|
||||
<span>|</span>
|
||||
<ns-button type="link" @click="onBack">返回上页</ns-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewError403',
|
||||
|
||||
setup() {},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-404 {
|
||||
margin: 150px auto 0;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-size: 160px;
|
||||
letter-spacing: 20px;
|
||||
color: @primary-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: 10px 0;
|
||||
font-size: 36px;
|
||||
letter-spacing: 3px;
|
||||
color: @text-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
|
||||
.op-list {
|
||||
border-top: 1px solid @border-color-base;
|
||||
width: 76%;
|
||||
margin: 0 auto;
|
||||
|
||||
span {
|
||||
padding-left: 16px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
lib/paas/view/service/error-404.vue
Normal file
74
lib/paas/view/service/error-404.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div :key="'error404_' + $route.name">
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<a-button @click="onBack">返回</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="page-404">
|
||||
<h2>404</h2>
|
||||
<h3>您来到了一块未知区域</h3>
|
||||
<p>请检查您输入的网址是否正确,或者点击链接继续浏览</p>
|
||||
<div class="op-list">
|
||||
<router-link :to="{ name: 'root' }">返回首页</router-link>
|
||||
<span>|</span>
|
||||
<ns-button type="link" @click="onBack">返回上页</ns-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewError404',
|
||||
|
||||
setup() {},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-404 {
|
||||
margin: 150px auto 0;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-size: 160px;
|
||||
letter-spacing: 20px;
|
||||
color: @primary-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: 10px 0;
|
||||
font-size: 36px;
|
||||
letter-spacing: 3px;
|
||||
color: @text-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
|
||||
.op-list {
|
||||
border-top: 1px solid @border-color-base;
|
||||
width: 76%;
|
||||
margin: 0 auto;
|
||||
|
||||
span {
|
||||
padding-left: 16px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
246
lib/paas/view/service/list-table.vue
Normal file
246
lib/paas/view/service/list-table.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div :key="'table_' + $route.name">
|
||||
<page-title :title="getTitle" v-if="showTitle" />
|
||||
<ns-table ref="nsTableRef" v-bind="getBindValue">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
|
||||
<slot :name="item" v-bind="data"> </slot>
|
||||
</template>
|
||||
</ns-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, unref, ref } from 'vue';
|
||||
import { tableProps } from '/nerv-lib/component/table/props';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { cloneDeep, isArray, isUndefined } from 'lodash-es';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewListTable',
|
||||
props: {
|
||||
...tableProps,
|
||||
title: PropTypes.string,
|
||||
tableTitle: PropTypes.func,
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsTableRef = ref();
|
||||
const route = useRoute();
|
||||
|
||||
const formatFilterSearch = (key, filter) => {
|
||||
let selector = filter.filter((item) => {
|
||||
return item.name.startsWith(key);
|
||||
});
|
||||
let selectorValue = selector.map((item) => {
|
||||
item.name = item.name.replace(key, '');
|
||||
console.log('item.value', item.value);
|
||||
if (item.name) return `${item.name}=${item.value.join('|')}`;
|
||||
return `${item.value.join('|')}`;
|
||||
});
|
||||
return selectorValue.join(',');
|
||||
};
|
||||
|
||||
const getFormConfig = computed(() => {
|
||||
const formConfig = cloneDeep(props.formConfig);
|
||||
|
||||
if (formConfig) {
|
||||
if (isUndefined(formConfig.showAction)) {
|
||||
formConfig.showAction = false;
|
||||
}
|
||||
if (!isArray(formConfig.schemas)) {
|
||||
formConfig.schemas = [];
|
||||
}
|
||||
formConfig['formLayout'] = 'flex';
|
||||
//过滤标签搜索组件
|
||||
if (formConfig.keyFilter === true) {
|
||||
formConfig.schemas.push(
|
||||
{
|
||||
field: 'selector',
|
||||
component: 'NsInputText',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'labels',
|
||||
component: 'NsInputText',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'search',
|
||||
component: 'NsInputText',
|
||||
show: false,
|
||||
},
|
||||
);
|
||||
formConfig.schemas.push({
|
||||
field: 'filter',
|
||||
label: '关键字',
|
||||
component: 'NsInputFilter',
|
||||
rules: [],
|
||||
componentProps: {
|
||||
maxlength: 50,
|
||||
allowClear: true,
|
||||
api: formConfig.api,
|
||||
terraceType:
|
||||
typeof formConfig.terraceType != 'undefined'
|
||||
? formConfig.terraceType
|
||||
: 'dataSource',
|
||||
isNeedHttpGet:
|
||||
typeof formConfig.isNeedHttpGet != 'undefined' ? formConfig.isNeedHttpGet : true,
|
||||
filterList: formConfig.filterList ? formConfig.filterList : [],
|
||||
type: typeof formConfig.type != 'undefined' ? formConfig.type : '',
|
||||
defaultFilterList:
|
||||
typeof formConfig.defaultFilterList != 'undefined'
|
||||
? formConfig.defaultFilterList
|
||||
: false,
|
||||
onSearch: () => {
|
||||
let filter = unref(nsTableRef)?.formElRef.model.filter;
|
||||
if (!filter) {
|
||||
let list = ['selector', 'labels', 'search'];
|
||||
list.map((elem) => {
|
||||
unref(nsTableRef).formElRef.setFormModel(elem, null);
|
||||
});
|
||||
unref(nsTableRef)?.formElRef?.triggerSubmit();
|
||||
return;
|
||||
}
|
||||
filter = JSON.parse(decodeURIComponent(filter));
|
||||
let selectorValue = formatFilterSearch('selector=', filter);
|
||||
let labelValue = formatFilterSearch('labels=', filter);
|
||||
let searchValue = formatFilterSearch('search=', filter);
|
||||
unref(nsTableRef).formElRef.model.selector = selectorValue || null;
|
||||
unref(nsTableRef).formElRef.model.labels = labelValue || null;
|
||||
unref(nsTableRef).formElRef.model.search = searchValue || null;
|
||||
unref(nsTableRef).formElRef.model.filter = null;
|
||||
console.log('www',unref(nsTableRef))
|
||||
unref(nsTableRef)?.formElRef?.triggerSubmit();
|
||||
},
|
||||
onChange: (text) => {},
|
||||
placeholder: '请输入关键字',
|
||||
},
|
||||
});
|
||||
} else if (formConfig.keySearch !== false) {
|
||||
formConfig.schemas.push({
|
||||
field: 'search',
|
||||
label: '关键字',
|
||||
component: 'NsInputSearch',
|
||||
rules: [
|
||||
{
|
||||
message: '请输入关键字',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
componentProps: {
|
||||
maxlength: 50,
|
||||
onSearch: () => {
|
||||
unref(nsTableRef)?.formElRef?.triggerSubmit();
|
||||
},
|
||||
onKeydown: (event: KeyboardEvent) => {
|
||||
//fix 单个input回车会提交表单 造成重复提交
|
||||
if (event.key === 'Enter' || event.code === 'Enter') {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
placeholder: '请输入关键字',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return formConfig;
|
||||
});
|
||||
const getBindValue = computed(() => ({
|
||||
// scroll: { x: 1000 },
|
||||
...attrs,
|
||||
...props,
|
||||
enableTableSession: true,
|
||||
formLayout: 'flex',
|
||||
title: null,
|
||||
formConfig: getFormConfig.value,
|
||||
}));
|
||||
const getTitle = computed(() => {
|
||||
const {
|
||||
params: { pageTitle },
|
||||
} = route;
|
||||
if (pageTitle) return pageTitle;
|
||||
const { title } = props;
|
||||
if (title) return title;
|
||||
});
|
||||
return { getBindValue, nsTableRef, getTitle };
|
||||
},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ns-form-item) {
|
||||
min-width: 220px !important;
|
||||
}
|
||||
|
||||
:deep(.ns-operate-expand) {
|
||||
display: inline-block;
|
||||
padding: 4px 2px !important;
|
||||
border: unset !important;
|
||||
}
|
||||
|
||||
:deep(.ns-table) {
|
||||
position: relative;
|
||||
.ns-table-main {
|
||||
padding: 0 24px;
|
||||
}
|
||||
.ns-table-search {
|
||||
padding: 18px 24px 0 24px;
|
||||
}
|
||||
//tableList组件
|
||||
.ns-table-header {
|
||||
width: auto;
|
||||
min-width: 100px;
|
||||
user-select: none;
|
||||
margin-bottom: 12px;
|
||||
margin-top: 18px;
|
||||
text-align: left;
|
||||
position: absolute !important;
|
||||
left: 0;
|
||||
top: -80px;
|
||||
|
||||
.ant-btn {
|
||||
margin-left: 0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
.ns-form-item label {
|
||||
display: none;
|
||||
}
|
||||
&.ns-table-no-search {
|
||||
.ns-table-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ns-table-header {
|
||||
position: relative !important;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ns-line) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(
|
||||
.ant-table-thead
|
||||
> tr
|
||||
> th:not(:last-child):not(.ant-table-selection-column):not(
|
||||
.ant-table-row-expand-icon-cell
|
||||
):not([colspan])::before
|
||||
) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
198
lib/paas/view/service/side-nav-multilevel.vue
Normal file
198
lib/paas/view/service/side-nav-multilevel.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="nav-side" :key="'navSide_' + $route.name">
|
||||
<div class="nav-sub" v-show="isShow">
|
||||
<ul>
|
||||
<li v-show="!backTo"
|
||||
><span class="nav-sub-head ng-scope" translate="COMPANY">{{ title }}</span></li
|
||||
>
|
||||
<li v-show="backTo">
|
||||
<span
|
||||
class="nav-sub-head h5 ng-scope nav-sub-head-back"
|
||||
style="box-sizing: border-box; cursor: pointer"
|
||||
@click="navigation(backTo)">
|
||||
<i class="iconfont"></i>
|
||||
<span>返回</span>
|
||||
</span>
|
||||
</li>
|
||||
<li v-for="(menu, index) in menus" :key="index">
|
||||
<router-link
|
||||
class="nav-sub-item"
|
||||
active-class="nav-sub-current"
|
||||
:to="{ name: menu.name }"
|
||||
append>
|
||||
<span>{{ menu.label }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="content-main">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { SideMenu, configRegist } from '/nerv-lib/paas/store/modules/config-service';
|
||||
import { defineComponent } from 'vue';
|
||||
// import { storeToRefs, mapWritableState } from 'pinia';
|
||||
import { findIndex } from 'lodash-es';
|
||||
export default defineComponent({
|
||||
name: 'SideNavMultilevel',
|
||||
components: {},
|
||||
setup() {
|
||||
const configReg = configRegist();
|
||||
return {
|
||||
configReg,
|
||||
};
|
||||
},
|
||||
data(): {
|
||||
title: string;
|
||||
menus: Array<any>;
|
||||
isShow: boolean;
|
||||
backTo: string;
|
||||
currName: string;
|
||||
} {
|
||||
return {
|
||||
title: '',
|
||||
menus: [],
|
||||
isShow: true,
|
||||
backTo: '', //是否需要返回按钮
|
||||
currName: '', //标记自己当前的路由配置取值key
|
||||
};
|
||||
},
|
||||
beforeCreate() {},
|
||||
mounted() {
|
||||
let sideMenu: SideMenu;
|
||||
let currUrl = this.$route.name;
|
||||
let activeUrl = '';
|
||||
let urlMap: any[] = this.$route.path.split('/');
|
||||
//订阅路由激活状态。如果有更深层级的side-nav激活,就隐藏当前的菜单
|
||||
this.configReg.$subscribe((mutation, state) => {
|
||||
console.log('sub');
|
||||
this.isShow = this.configReg.isLaster(this.currName);
|
||||
});
|
||||
|
||||
try {
|
||||
const routeMatched = this.$route.matched.reverse();
|
||||
console.log(routeMatched);
|
||||
routeMatched.some((el) => {
|
||||
if (el.meta['sideMenus'] && !this.configReg.hasUrl(el.name)) {
|
||||
sideMenu = el.meta['sideMenus'];
|
||||
this.currName = el.name || '';
|
||||
this.backTo = sideMenu['backTo'] || '';
|
||||
this.title = sideMenu.title || '';
|
||||
this.menus = sideMenu.menus;
|
||||
this.isShow = true;
|
||||
this.menus.forEach((item) => {
|
||||
if (item.name === currUrl) {
|
||||
activeUrl = item.name;
|
||||
}
|
||||
});
|
||||
// let index = -1;
|
||||
// urlMap.some((el, ind) => {
|
||||
// if (el === el.name) {
|
||||
// index = ind;
|
||||
// }
|
||||
// });
|
||||
let index = findIndex(urlMap, function (o) {
|
||||
return o === el.name;
|
||||
});
|
||||
this.configReg.setUrl(el.name, index);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
//如果没有激活的路由,而且是当前side-nav的默认路由,就取第一个跳转
|
||||
if (!activeUrl && sideMenu.name === currUrl) {
|
||||
activeUrl = this.menus[0]['name'];
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
if (activeUrl) {
|
||||
this.navigation(activeUrl);
|
||||
} else {
|
||||
this.navigation2(this.$route.path);
|
||||
}
|
||||
},
|
||||
unmounted() {
|
||||
// configReg.activateUrls = configReg.activateUrls.filter((el) => {
|
||||
// return el !== this.currName;
|
||||
this.configReg.removeUrl(this.currName);
|
||||
},
|
||||
methods: {
|
||||
navigation(url: string) {
|
||||
this.$router.push({ name: url });
|
||||
},
|
||||
navigation2(url: string) {
|
||||
this.$router.push({ path: url });
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nav-side {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
.nav-sub {
|
||||
float: left;
|
||||
// height: 100%;
|
||||
overflow: auto;
|
||||
width: 149px;
|
||||
background-color: #f7f9fb;
|
||||
.nav-sub-head {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-weight: 700;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
}
|
||||
.nav-sub-head-back {
|
||||
.iconfont {
|
||||
color: #00acff !important;
|
||||
display: inline-block;
|
||||
transform: scale(0.7);
|
||||
font-size: 12px;
|
||||
}
|
||||
span {
|
||||
color: #00acff !important;
|
||||
}
|
||||
}
|
||||
.nav-sub-item {
|
||||
color: #212529;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sub-current {
|
||||
background-color: #fff;
|
||||
color: @layout-nav-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content-main {
|
||||
width: calc(100% - 149px);
|
||||
background-color: #ffffff;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
403
lib/paas/view/service/side-nav.vue
Normal file
403
lib/paas/view/service/side-nav.vue
Normal file
@@ -0,0 +1,403 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="nav-side" :key="'navSide_' + $route.name">
|
||||
<div class="nav-sub" v-show="isShow && (showLayout || backTo)">
|
||||
<ul style="margin: 0">
|
||||
<li v-show="!backTo"
|
||||
><span class="nav-sub-head ng-scope" translate="COMPANY">{{ title }}</span></li
|
||||
>
|
||||
<li v-show="backTo">
|
||||
<span
|
||||
class="nav-sub-head h5 ng-scope nav-sub-head-back"
|
||||
style="box-sizing: border-box; cursor: pointer"
|
||||
@click="navigation(backTo)">
|
||||
<i class="iconfont"></i>
|
||||
<span>返回</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- {{ menus }} -->
|
||||
<a-menu
|
||||
mode="inline"
|
||||
class="nav-menu"
|
||||
:open-keys="openKeys"
|
||||
v-model:selectedKeys="selectedKeys">
|
||||
<template v-for="menu in menus">
|
||||
<a-sub-menu v-if="menu.submenus" :key="menu.name">
|
||||
<template #title>{{ menu.label }}</template>
|
||||
<a-menu-item
|
||||
v-for="subMenu in menu.submenus"
|
||||
:key="subMenu.name"
|
||||
:class="{ 'nav-current': fullPath.indexOf(subMenu.url) != -1 }">
|
||||
<router-link
|
||||
class="nav-sub-item"
|
||||
active-class="nav-sub-current"
|
||||
:class="{ 'nav-sub-current': fullPath.indexOf(subMenu.url) != -1 }"
|
||||
:to="{ name: subMenu.name }"
|
||||
@click="navChange(subMenu)">
|
||||
<span>{{ subMenu.label }}</span>
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-menu-item
|
||||
v-else
|
||||
:key="menu.name"
|
||||
:class="{ 'nav-current': fullPath.indexOf(`${menu.url}`) != -1 }">
|
||||
<router-link
|
||||
class="nav-sub-item"
|
||||
active-class="nav-sub-current"
|
||||
:class="{ 'nav-sub-current': dealRouter(`${menu.url}`) }"
|
||||
@click="navChange(menu)"
|
||||
:to="{ name: menu.name }">
|
||||
<span>{{ menu.label }}</span>
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div class="content-main">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { SideMenu, configRegist } from '/nerv-lib/paas/store/modules/config-service';
|
||||
import { computed, defineComponent, inject, reactive, ref, toRefs } from 'vue';
|
||||
// import { storeToRefs, mapWritableState } from 'pinia';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { authorizationService } from '/nerv-lib/paas/store/modules/authorization-service';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SideNav',
|
||||
components: {},
|
||||
setup() {
|
||||
const configReg = configRegist();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const menus = ref<Array<any>>([]);
|
||||
const backTo = ref<String>('');
|
||||
let backToHandle = ref<Function>;
|
||||
const isThreeSider = ref(false);
|
||||
const state = reactive({
|
||||
selectedKeys: [],
|
||||
});
|
||||
const showLayout = inject('showLayout', true);
|
||||
const dealRouter = (val: any) => {
|
||||
return router.currentRoute.value.fullPath.indexOf(val) !== -1;
|
||||
};
|
||||
|
||||
const menuNameList = computed(() => {
|
||||
const { sideMenus } = route?.meta || {};
|
||||
return sideMenus.menus.map((item) => item.name);
|
||||
});
|
||||
|
||||
function navChange(url: string) {
|
||||
Object.keys(sessionStorage).forEach((key) => {
|
||||
const tableSession = JSON.parse(sessionStorage[key] || '{}');
|
||||
if (tableSession && tableSession.name && menuNameList.value.includes(tableSession.name)) {
|
||||
delete sessionStorage[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
dealRouter,
|
||||
menus,
|
||||
backTo,
|
||||
router,
|
||||
isThreeSider,
|
||||
configReg,
|
||||
...toRefs(state),
|
||||
navChange,
|
||||
showLayout,
|
||||
backToHandle,
|
||||
};
|
||||
},
|
||||
data(): {
|
||||
title: string;
|
||||
// menus: Array<any>;
|
||||
isShow: boolean;
|
||||
//backTo: string;
|
||||
currName: string;
|
||||
fullPath: string;
|
||||
openKeys: string[];
|
||||
} {
|
||||
return {
|
||||
title: '',
|
||||
// menus: [],
|
||||
isShow: true,
|
||||
//backTo: '', //是否需要返回按钮
|
||||
currName: '', //标记自己当前的路由配置取值key
|
||||
fullPath: '', //当前激活路由的url
|
||||
openKeys: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
router: {
|
||||
handler() {
|
||||
this.initSider();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initSider();
|
||||
},
|
||||
unmounted() {
|
||||
this.configReg.removeUrl(this.currName);
|
||||
},
|
||||
methods: {
|
||||
initSider() {
|
||||
if (this.$route.meta.sideMenus) {
|
||||
const authService = authorizationService();
|
||||
|
||||
const modulesRes = authService.ModulesRes;
|
||||
|
||||
const appInfo = cloneDeep(modulesRes.find((item) => item.app === authService.getApp()));
|
||||
const moduleName = this.$route.matched[0].name as string;
|
||||
|
||||
const sideMenus = cloneDeep(this.$route.meta.sideMenus as SideMenu);
|
||||
|
||||
const name = this.$route.name;
|
||||
|
||||
this.fullPath = this.$route.fullPath;
|
||||
this.title = sideMenus.title as string;
|
||||
this.backTo = sideMenus.backTo || '';
|
||||
this.backToHandle = sideMenus?.backToHandle;
|
||||
this.menus = [];
|
||||
this.openKeys = [];
|
||||
const { projectName } = this.$route.params;
|
||||
function getMenuItem(name: string) {
|
||||
console.log('---', name);
|
||||
const menu = sideMenus.menus.find((item) => item.name === name);
|
||||
|
||||
if (
|
||||
menu &&
|
||||
authService.checkPermission(
|
||||
moduleName,
|
||||
menu.name as string,
|
||||
undefined,
|
||||
projectName as string,
|
||||
)
|
||||
) {
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
function getMenuItemcustom(record: object, name: string) {
|
||||
const menu = sideMenus.menus.find((item) => item.name === name);
|
||||
if (
|
||||
menu &&
|
||||
authService.checkPermission(
|
||||
record.useOtherAuthority.app,
|
||||
(record.useOtherAuthority.bindView
|
||||
? record.useOtherAuthority.bindView
|
||||
: menu.name) as string,
|
||||
undefined,
|
||||
projectName as string,
|
||||
)
|
||||
) {
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
|
||||
if (sideMenus.root) {
|
||||
this.selectedKeys = [];
|
||||
|
||||
appInfo?.menus?.forEach((item: Recordable) => {
|
||||
if (item.isDir) {
|
||||
const _item = {
|
||||
name: item.name,
|
||||
label: item.label,
|
||||
submenus: [],
|
||||
};
|
||||
let open = false;
|
||||
//处理 模块受其他项目权限控制的情况
|
||||
if (item.useOtherAuthority) {
|
||||
item.submenus.forEach((subItem) => {
|
||||
const _subItem = getMenuItemcustom(item, subItem.name);
|
||||
!!_subItem && _item.submenus.push(_subItem);
|
||||
if (this.fullPath.includes(subItem.url ? subItem.url : subItem.path)) {
|
||||
this.selectedKeys = [subItem.name]; // 通过输入路由匹配到文件夹下的菜单,一开始须手动加入
|
||||
open = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
item.submenus.forEach((subItem) => {
|
||||
const _subItem = getMenuItem(subItem.name);
|
||||
!!_subItem && _item.submenus.push(_subItem);
|
||||
if (this.fullPath.includes(subItem.url ? subItem.url : subItem.path)) {
|
||||
this.selectedKeys = [subItem.name]; // 通过输入路由匹配到文件夹下的菜单,一开始须手动加入
|
||||
open = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if(item.submenus.length===0){
|
||||
|
||||
// }
|
||||
if (open) this.openKeys.push(item.name);
|
||||
console.log('_item1', _item);
|
||||
if (_item.submenus && _item.submenus.length !== 0) {
|
||||
this.menus.push(_item);
|
||||
}
|
||||
} else {
|
||||
//如果存在isGlobal
|
||||
if (item.isGlobal) {
|
||||
let globalList = [];
|
||||
sideMenus.menus.forEach((subItem) => {
|
||||
if (subItem.app === item.name) {
|
||||
globalList.push(subItem);
|
||||
}
|
||||
});
|
||||
globalList.forEach((global) => {
|
||||
const _item = authService.checkPermission(
|
||||
item.name,
|
||||
global.name,
|
||||
undefined,
|
||||
projectName as string,
|
||||
);
|
||||
!!_item && this.menus.push(global);
|
||||
});
|
||||
} else {
|
||||
if (item.useOtherAuthority) {
|
||||
const _item = getMenuItemcustom(item, item.name);
|
||||
!!_item && this.menus.push(_item);
|
||||
} else {
|
||||
const _item = getMenuItem(item.name);
|
||||
!!_item && this.menus.push(_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.menus = sideMenus.menus.filter((menu) => {
|
||||
return (
|
||||
authService.checkPermission(
|
||||
menu.app ? menu.app : moduleName,
|
||||
menu.bindView ? menu.bindView : (menu.name as string),
|
||||
undefined,
|
||||
projectName as string,
|
||||
) || menu.openPermission
|
||||
);
|
||||
});
|
||||
}
|
||||
console.log('--------', this.menus);
|
||||
|
||||
this.isShow = true;
|
||||
if (name === sideMenus.name && this.menus.length > 0) {
|
||||
this.$router.push({ name: this.menus[0].name });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
navigation(url: string) {
|
||||
if (this.backToHandle && Function(this.backToHandle)) {
|
||||
this.backToHandle();
|
||||
return;
|
||||
}
|
||||
//this.navChange(url);
|
||||
|
||||
this.$router.push({ name: url });
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.nav-side {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
.nav-sub {
|
||||
float: left;
|
||||
// height: 100%;
|
||||
overflow: auto;
|
||||
width: 149px;
|
||||
min-width: 149px;
|
||||
background: @layout-sider-background !important;
|
||||
background-size: cover;
|
||||
|
||||
.nav-sub-head {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-weight: 700;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
}
|
||||
|
||||
.nav-sub-head-back {
|
||||
.iconfont {
|
||||
color: #00acff !important;
|
||||
display: inline-block;
|
||||
transform: scale(0.7);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #00acff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sub-item {
|
||||
color: #212529;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sub-current {
|
||||
background-color: #fff !important;
|
||||
color: @link-color !important;
|
||||
}
|
||||
}
|
||||
:deep(.ant-menu) {
|
||||
background-color: unset;
|
||||
.ant-menu-item {
|
||||
background-color: unset;
|
||||
margin-top: 0;
|
||||
|
||||
&.nav-current {
|
||||
background-color: #fff;
|
||||
color: #00acff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
color: #00acff;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&.ant-menu-item-selected::after {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-main {
|
||||
// width: calc(100% - 149px);
|
||||
width: 100%;
|
||||
min-width: 650px;
|
||||
background-color: #ffffff;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user