push
This commit is contained in:
285
lib/saas/view/service/add-form-v2.vue
Normal file
285
lib/saas/view/service/add-form-v2.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div class="ns-view">
|
||||
<a-spin class="ns-view-spinning" :spinning="isLoading" :key="'addForm_' + $route.name">
|
||||
<ns-page-header class="ns-page-header" :sticky="sticky">
|
||||
<template #title>
|
||||
<div class="title" @click="navigateBack" v-if="showBack">
|
||||
<!-- <left-outlined style="color: rgba(0, 0, 0, 0.4)" /> {{ title }} -->
|
||||
<ns-icon name="left" /> <div class="text">{{ title }}</div>
|
||||
</div>
|
||||
<div class="title" v-else>
|
||||
{{ title }}
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<ns-button v-if="okText" type="primary" @click="submit" :disabled="!validateResult">{{
|
||||
okText
|
||||
}}</ns-button>
|
||||
<template v-for="item in getActions" :key="item.name">
|
||||
<template v-if="item.children">
|
||||
<template v-if="getDropdownActions(item.children).length > 0">
|
||||
<a-dropdown :trigger="['hover']" :dropMenuList="item.children">
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
{{ item.label }}
|
||||
<CaretDownOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
style="padding: 2px 5px"
|
||||
v-for="itemChild in getDropdownActions(item.children)"
|
||||
:key="itemChild.name"
|
||||
@click="itemChild.finalHandle(data, itemChild.name)">
|
||||
<a-button type="link" :disabled="itemChild.dynamicDisabled">
|
||||
{{ itemChild.label }}
|
||||
</a-button>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ns-button
|
||||
:type="item.type || 'default'"
|
||||
:disabled="item.dynamicDisabled"
|
||||
@click="item.finalHandle(data, item.name)">
|
||||
<ns-v-node :content="item.label" />
|
||||
</ns-button>
|
||||
</template>
|
||||
</template>
|
||||
<ns-button v-if="cancelText" @click="navigateBack">{{ cancelText }}</ns-button>
|
||||
</template>
|
||||
</ns-page-header>
|
||||
<div class="ns-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>
|
||||
<slot name="nsCustomFormItem"></slot>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
import { cloneDeep, isEmpty } from 'lodash-es';
|
||||
import { Action, useAction } from '/nerv-lib/use';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { CaretDownOutlined, LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { useApi, HttpRequestConfig } from '/nerv-lib/use/use-api';
|
||||
|
||||
interface ColumnActions {
|
||||
back: Boolean;
|
||||
actions: Action[];
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NsViewAddFormV2',
|
||||
components: {
|
||||
CaretDownOutlined,
|
||||
LeftOutlined,
|
||||
},
|
||||
props: {
|
||||
api: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
sticky: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
title: String,
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
dataHandle: Function,
|
||||
okText: {
|
||||
type: String,
|
||||
default: '确 定',
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取 消',
|
||||
},
|
||||
headActions: {
|
||||
type: Object as PropType<ColumnActions>,
|
||||
default: () => ({
|
||||
actions: [],
|
||||
}),
|
||||
},
|
||||
customDataApi: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsFormElRef = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const { navigateBackV2: navigateBack } = useNavigate();
|
||||
const route = useRoute();
|
||||
const { httpRequest } = useApi();
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...route.query,
|
||||
...route.params,
|
||||
formRef: nsFormElRef.value,
|
||||
};
|
||||
});
|
||||
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
title: '',
|
||||
}));
|
||||
|
||||
const validateResult = computed(() => {
|
||||
return nsFormElRef.value?.validateResult;
|
||||
});
|
||||
|
||||
//提交
|
||||
function submit() {
|
||||
nsFormElRef.value
|
||||
.triggerSubmit()
|
||||
.then((data: any) => {
|
||||
isLoading.value = true;
|
||||
data = props.dataHandle ? props.dataHandle(data) : data;
|
||||
const { api } = props;
|
||||
const requestConfig: HttpRequestConfig = { method: 'POST' };
|
||||
const { params } = route;
|
||||
|
||||
httpRequest({ api, params: data, pathParams: params, requestConfig })
|
||||
.then(() => {
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
isLoading.value = false;
|
||||
navigateBack();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// function request() {
|
||||
// const { query } = route;
|
||||
// if (!props.detailApi) return;
|
||||
// if (props.customDataApi) {
|
||||
// props.customDataApi(props.detailApi, query).then((res) => {
|
||||
// getBindValue.value.data = res.data;
|
||||
// isLoading.value = false;
|
||||
// });
|
||||
// } else {
|
||||
// http.get(props.detailApi, query).then((res) => {
|
||||
// getBindValue.value.data = res.data;
|
||||
// isLoading.value = false;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// request();
|
||||
|
||||
const { transformAction } = useAction();
|
||||
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(props.headActions.actions);
|
||||
actions = actions.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
|
||||
const getDropdownActions = (actions) => {
|
||||
actions = actions.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
};
|
||||
|
||||
return {
|
||||
nsFormElRef,
|
||||
validateResult,
|
||||
submit,
|
||||
getBindValue,
|
||||
isLoading,
|
||||
navigateBack,
|
||||
getActions,
|
||||
getDropdownActions,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ns-view {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
background: #e5ebf0;
|
||||
}
|
||||
:deep(.ant-spin-nested-loading) {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.ant-spin-container) {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.ant-divider) {
|
||||
display: none;
|
||||
}
|
||||
.ns-page-header {
|
||||
margin-bottom: 0 !important;
|
||||
padding: 7px 16px !important;
|
||||
width: calc(100% + 32px);
|
||||
margin-left: -16px;
|
||||
.title {
|
||||
cursor: pointer;
|
||||
font-size: 18px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ns-add-form {
|
||||
border-top: 16px solid #e5ebf0;
|
||||
padding: 16px 21px;
|
||||
background: #fff;
|
||||
height: calc(100% - 47px);
|
||||
:deep(.ns-form) {
|
||||
.ns-form-item .ns-form-body .ns-child-form-title {
|
||||
padding-top: 0;
|
||||
}
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:deep(.ns-child-form-title) {
|
||||
&:after {
|
||||
content: '';
|
||||
width: 75px;
|
||||
height: 7px;
|
||||
display: block;
|
||||
background: linear-gradient(90deg, @primary-color 0%, #fff 82.67%);
|
||||
margin-left: 2px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ns-form.ns-vertical-form) {
|
||||
padding-top: 16px !important;
|
||||
}
|
||||
</style>
|
||||
282
lib/saas/view/service/add-form.vue
Normal file
282
lib/saas/view/service/add-form.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div class="ns-view">
|
||||
<a-spin class="ns-view-spinning" :spinning="isLoading" :key="'addForm_' + $route.name">
|
||||
<ns-page-header class="ns-page-header" :sticky="sticky">
|
||||
<template #title>
|
||||
<div class="title" @click="onBack" v-if="showBack">
|
||||
<!-- <left-outlined style="color: rgba(0, 0, 0, 0.4)" /> {{ title }} -->
|
||||
<ns-icon name="left" /> <div class="text">{{ title }}</div>
|
||||
</div>
|
||||
<div class="title" v-else>
|
||||
{{ title }}
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<ns-button v-if="okText" type="primary" @click="submit" :disabled="!validateResult">{{
|
||||
okText
|
||||
}}</ns-button>
|
||||
<template v-for="item in getActions" :key="item.name">
|
||||
<template v-if="item.children">
|
||||
<template v-if="getDropdownActions(item.children).length > 0">
|
||||
<a-dropdown :trigger="['hover']" :dropMenuList="item.children">
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
{{ item.label }}
|
||||
<CaretDownOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
style="padding: 2px 5px"
|
||||
v-for="itemChild in getDropdownActions(item.children)"
|
||||
:key="itemChild.name"
|
||||
@click="itemChild.finalHandle(data, itemChild.name)">
|
||||
<a-button type="link" :disabled="itemChild.dynamicDisabled">
|
||||
{{ itemChild.label }}
|
||||
</a-button>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ns-button
|
||||
:type="item.type || 'default'"
|
||||
:disabled="item.dynamicDisabled"
|
||||
@click="item.finalHandle(data, item.name)">
|
||||
<ns-v-node :content="item.label" />
|
||||
</ns-button>
|
||||
</template>
|
||||
</template>
|
||||
<ns-button v-if="cancelText" @click="navigateBack">{{ cancelText }}</ns-button>
|
||||
</template>
|
||||
</ns-page-header>
|
||||
<div class="ns-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>
|
||||
<slot name="nsCustomFormItem"></slot>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { Action, useAction } from '/nerv-lib/use';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { CaretDownOutlined, LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { useApi, HttpRequestConfig } from '/nerv-lib/use/use-api';
|
||||
|
||||
interface ColumnActions {
|
||||
back: Boolean;
|
||||
actions: Action[];
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NsViewAddForm',
|
||||
components: {
|
||||
CaretDownOutlined,
|
||||
LeftOutlined,
|
||||
},
|
||||
props: {
|
||||
api: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
detailApi: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sticky: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
title: String,
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
dataHandle: Function,
|
||||
okText: {
|
||||
type: String,
|
||||
default: '确 定',
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取 消',
|
||||
},
|
||||
headActions: {
|
||||
type: Object as PropType<ColumnActions>,
|
||||
default: () => ({
|
||||
actions: [],
|
||||
}),
|
||||
},
|
||||
customBack: {
|
||||
type: String || Function,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsFormElRef = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const { navigateBackV2: navigateBack } = useNavigate();
|
||||
const route = useRoute();
|
||||
const { httpRequest } = useApi();
|
||||
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...route.query,
|
||||
...route.params,
|
||||
formRef: nsFormElRef.value,
|
||||
};
|
||||
});
|
||||
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
title: '',
|
||||
}));
|
||||
|
||||
const validateResult = computed(() => {
|
||||
return nsFormElRef.value?.validateResult;
|
||||
});
|
||||
|
||||
function submit() {
|
||||
nsFormElRef.value
|
||||
.triggerSubmit()
|
||||
.then((data: any) => {
|
||||
isLoading.value = true;
|
||||
data = props.dataHandle ? props.dataHandle(data) : data;
|
||||
const { api } = props;
|
||||
const requestConfig: HttpRequestConfig = { method: 'POST' };
|
||||
const { params } = route;
|
||||
|
||||
httpRequest({ api, params: data, pathParams: params, requestConfig })
|
||||
.then(() => {
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
isLoading.value = false;
|
||||
navigateBack();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
const { transformAction } = useAction();
|
||||
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(props.headActions.actions);
|
||||
actions = actions.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
|
||||
const getDropdownActions = (actions) => {
|
||||
actions = actions.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
};
|
||||
|
||||
return {
|
||||
nsFormElRef,
|
||||
validateResult,
|
||||
submit,
|
||||
getBindValue,
|
||||
isLoading,
|
||||
navigateBack,
|
||||
getActions,
|
||||
getDropdownActions,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onBack() {
|
||||
//debugger;
|
||||
|
||||
if (this.customBack) {
|
||||
this.customBack();
|
||||
} else {
|
||||
this.navigateBack();
|
||||
}
|
||||
// this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ns-view {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
background: #e5ebf0;
|
||||
}
|
||||
:deep(.ant-spin-nested-loading) {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.ant-spin-container) {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.ant-divider) {
|
||||
display: none;
|
||||
}
|
||||
.ns-page-header {
|
||||
margin-bottom: 0 !important;
|
||||
padding: 7px 16px !important;
|
||||
width: calc(100% + 32px);
|
||||
margin-left: -16px;
|
||||
.title {
|
||||
cursor: pointer;
|
||||
font-size: 18px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ns-add-form {
|
||||
border-top: 16px solid #e5ebf0;
|
||||
padding: 16px 21px;
|
||||
background: #fff;
|
||||
height: calc(100% - 47px);
|
||||
:deep(.ns-form) {
|
||||
.ns-form-item .ns-form-body .ns-child-form-title {
|
||||
padding-top: 0;
|
||||
}
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:deep(.ns-child-form-title) {
|
||||
&:after {
|
||||
content: '';
|
||||
width: 75px;
|
||||
height: 7px;
|
||||
display: block;
|
||||
background: linear-gradient(90deg, @primary-color 0%, #fff 82.67%);
|
||||
margin-left: 2px;
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ns-form.ns-vertical-form) {
|
||||
padding-top: 16px !important;
|
||||
}
|
||||
</style>
|
||||
577
lib/saas/view/service/detail.vue
Normal file
577
lib/saas/view/service/detail.vue
Normal file
@@ -0,0 +1,577 @@
|
||||
<template>
|
||||
<div class="ns-view">
|
||||
<ns-page-header class="ns-page-header">
|
||||
<template #title>
|
||||
<div class="title" @click="onBack">
|
||||
<!-- <left-outlined style="color: rgba(0, 0, 0, 0.4)" /> {{ title }} -->
|
||||
<ns-icon name="left" /> <div class="text">{{ title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<template v-for="item in getActions" :key="item.name">
|
||||
<template v-if="item.children">
|
||||
<template v-if="getDropdownActions(item.children).length > 0">
|
||||
<a-dropdown :trigger="['hover']" :dropMenuList="item.children">
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
{{ item.label }}
|
||||
<CaretDownOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
style="padding: 2px 5px"
|
||||
v-for="itemChild in getDropdownActions(item.children)"
|
||||
:key="itemChild.name"
|
||||
@click="itemChild.finalHandle(data, itemChild.name)">
|
||||
<a-button type="link" :disabled="itemChild.dynamicDisabled">
|
||||
{{ itemChild.label }}
|
||||
</a-button>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ns-button
|
||||
:type="item.type || 'default'"
|
||||
:danger="item.danger"
|
||||
:disabled="item.dynamicDisabled"
|
||||
@click="item.finalHandle(data, item.name)">
|
||||
<ns-v-node :content="item.label" />
|
||||
</ns-button>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ns-page-header>
|
||||
<div class="ns-detail-content">
|
||||
<Skeleton
|
||||
active
|
||||
v-for="(detailGroup, index) in getDetail"
|
||||
:key="index"
|
||||
:loading="loading"
|
||||
:paragraph="detailGroup.SkeletonParagraphProps"
|
||||
:title="SkeletonTitleProps">
|
||||
<div class="ns-detail">
|
||||
<a-descriptions>
|
||||
<template #title>
|
||||
<div class="descriptions-title" v-if="detailGroup.title">
|
||||
<span>{{ detailGroup.title }}</span>
|
||||
<div class="titleFloor"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="detailGroup.items">
|
||||
<template v-for="(item, index) in detailGroup.items" :key="index">
|
||||
<a-descriptions-item v-bind="item" v-if="item.ifShow">
|
||||
<!--默认-->
|
||||
|
||||
<template v-if="!item.type">
|
||||
<span class="ns-detail-text">{{ item.value ? 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"
|
||||
v-if="src"
|
||||
:src="src"
|
||||
fallback="data:image/png;base64,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" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-image
|
||||
class="ns-detail-image"
|
||||
:width="64"
|
||||
v-if="item.value"
|
||||
:src="item.value"
|
||||
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!--表格-->
|
||||
<template v-else-if="item.type === 'table'">
|
||||
<ns-basic-table
|
||||
:scroll="{ x: '100%' }"
|
||||
class="ns-detail-table"
|
||||
:dataSource="dataRef[item.props.listField]"
|
||||
:columns="item.props.columns"
|
||||
:pagination="false"
|
||||
:rowKey="item.props.rowKey">
|
||||
<template #bodyCell="data" v-if="!Object.keys($slots).includes('bodyCell')">
|
||||
<template v-if="data.column.textEllipsis">
|
||||
<!-- :style="{ width: `${data.column.width}px` }" -->
|
||||
<span class="tool-tips">
|
||||
<ns-tooltip
|
||||
placement="top"
|
||||
v-if="
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex]
|
||||
">
|
||||
<template #title>
|
||||
<span>{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
<span class="text-ellipsis">{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</ns-tooltip>
|
||||
<span class="text-ellipsis" v-else> - </span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="data.column.edit">
|
||||
<ns-table-cell
|
||||
:value="data.text"
|
||||
:record="data.record"
|
||||
:column="data.column"
|
||||
:index="data.index" />
|
||||
</template>
|
||||
</template>
|
||||
</ns-basic-table>
|
||||
</template>
|
||||
|
||||
<!--表格-->
|
||||
<template v-else-if="item.type === 'NsViewListTable'">
|
||||
<NsViewListTable
|
||||
class="ns-detail-NsViewListTable"
|
||||
v-bind="item.props.tableConfig" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.type === 'map'">
|
||||
<NsMapV2 :value="item['value']" :viewOnly="true" />
|
||||
</template>
|
||||
|
||||
<template v-for="slot in Object.keys($slots)" :key="slot">
|
||||
<div v-if="item.type === slot" :style="item.style" :class="`ns-detail-${slot}`">
|
||||
<slot :name="slot" v-bind="item || {}"> </slot>
|
||||
</div>
|
||||
</template>
|
||||
</a-descriptions-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch, PropType, reactive } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { cloneDeep, get, isFunction, isUndefined } from 'lodash-es';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { Action, useAction } from '/nerv-lib/use';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
import { any } from 'vue-types';
|
||||
export interface DetailItem {
|
||||
label: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
ifShow?: Boolean | Function;
|
||||
format?: Function;
|
||||
type?: string; // 现支持image
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export interface DetailGroup {
|
||||
title: string;
|
||||
items: Array<DetailItem>;
|
||||
}
|
||||
interface ColumnActions {
|
||||
back: Boolean;
|
||||
actions: Action[];
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NsViewDetail',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '查看',
|
||||
},
|
||||
detail: {
|
||||
type: Array as PropType<DetailGroup[]>,
|
||||
default: () => [],
|
||||
},
|
||||
api: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
headActions: {
|
||||
type: Object as PropType<ColumnActions>,
|
||||
default: () => ({
|
||||
actions: [],
|
||||
}),
|
||||
},
|
||||
dataHandle: Function,
|
||||
customDataApi: {
|
||||
type: Function,
|
||||
},
|
||||
requersConfig: {
|
||||
type: any,
|
||||
},
|
||||
customBack: {
|
||||
type: String || Function,
|
||||
},
|
||||
},
|
||||
components: { Skeleton, LeftOutlined },
|
||||
setup(props) {
|
||||
const { navigateBackV2: navigateBack } = useNavigate();
|
||||
const SkeletonTitleProps = ref({
|
||||
width: 150,
|
||||
});
|
||||
const route = useRoute();
|
||||
const dataRef = ref();
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...route.query,
|
||||
...route.params,
|
||||
dataRef: dataRef,
|
||||
};
|
||||
});
|
||||
|
||||
let loading = ref<boolean>(true);
|
||||
|
||||
function request() {
|
||||
const { query } = route;
|
||||
|
||||
if (props.customDataApi) {
|
||||
props.customDataApi(props.api, query).then((res) => {
|
||||
dataRef.value = res.data;
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
http.get(props.api, query, props.requersConfig).then((res) => {
|
||||
dataRef.value = props.dataHandle ? props.dataHandle(res.data) : res.data;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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('80%');
|
||||
} else {
|
||||
SkeletonWidth.push('30%');
|
||||
}
|
||||
}
|
||||
const { title, items } = group;
|
||||
return {
|
||||
title: title,
|
||||
items: items.map((item) => {
|
||||
if (!loading.value) {
|
||||
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,
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
//支持自定义按钮
|
||||
const { transformAction, filterActionNoAuth } = useAction();
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(props.headActions.actions);
|
||||
actions = actions
|
||||
.filter((action: Action) => {
|
||||
return filterActionNoAuth(action, getData.value);
|
||||
})
|
||||
.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
const getDropdownActions = (actions) => {
|
||||
actions = actions.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
};
|
||||
return {
|
||||
getDetail,
|
||||
loading,
|
||||
dataRef,
|
||||
SkeletonTitleProps,
|
||||
getActions,
|
||||
getDropdownActions,
|
||||
navigateBack,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
//debugger;
|
||||
|
||||
if (this.customBack) {
|
||||
this.customBack();
|
||||
} else {
|
||||
this.navigateBack();
|
||||
}
|
||||
// this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-view {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
background: #e5ebf0;
|
||||
}
|
||||
.ns-detail-content {
|
||||
border-top: 16px solid #e5ebf0;
|
||||
padding: 16px 21px;
|
||||
background: #fff;
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph li:nth-child(n)) {
|
||||
display: block;
|
||||
margin-right: 4%;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph li:nth-child(3n + 3)) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-content) {
|
||||
padding: 0 8px 10px 10px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label) {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label),
|
||||
:deep(.ant-descriptions-item-content) {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-view) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item) {
|
||||
padding-right: 20px;
|
||||
|
||||
&:nth-child(2n) {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
&:nth-child(3n) {
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.descriptions-title {
|
||||
&:after {
|
||||
content: '';
|
||||
width: 75px;
|
||||
height: 7px;
|
||||
display: block;
|
||||
background: linear-gradient(90deg, @primary-color 0%, #fff 82.67%);
|
||||
margin-left: 2px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-page-header {
|
||||
margin-bottom: 0 !important;
|
||||
padding: 7px 16px !important;
|
||||
width: calc(100% + 32px);
|
||||
margin-left: -16px;
|
||||
.title {
|
||||
cursor: pointer;
|
||||
font-size: 18px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ns-detail {
|
||||
border-bottom: 1px solid #ecedef;
|
||||
|
||||
&:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
&:first-child {
|
||||
:deep(.ant-descriptions-header) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-header) {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.ant-descriptions-title {
|
||||
line-height: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-image) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-detail-html {
|
||||
:deep(table) {
|
||||
border-top: 1px solid #ffffff;
|
||||
border-left: 1px solid #ffffff;
|
||||
|
||||
:deep(p) {
|
||||
font-size: 12px;
|
||||
color: #898e91;
|
||||
}
|
||||
}
|
||||
|
||||
: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);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-detail-content {
|
||||
.ns-detail-table {
|
||||
:deep(.ns-basic-table) {
|
||||
padding-top: 0;
|
||||
.ant-table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ns-detail-NsViewListTable {
|
||||
background-color: #fff !important;
|
||||
:deep(.ns-table-search) {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
:deep(.ns-table-header) {
|
||||
// margin-bottom: 16px;
|
||||
padding-top: 0;
|
||||
height: auto;
|
||||
}
|
||||
:deep(.ns-table-main) {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ns-detail-tableConfig) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-row > td) {
|
||||
padding-bottom: 8px !important;
|
||||
}
|
||||
|
||||
:deep(.ns-detail-tablefullcontent) {
|
||||
.ant-descriptions-item-content {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
.tool-tips {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
468
lib/saas/view/service/detailV2.vue
Normal file
468
lib/saas/view/service/detailV2.vue
Normal file
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<div class="ns-view">
|
||||
<ns-page-header class="ns-page-header">
|
||||
<template #title>
|
||||
<div class="title" @click="onBack">
|
||||
<!-- <left-outlined style="color: rgba(0, 0, 0, 0.4)" /> {{ title }} -->
|
||||
<ns-icon name="left" /> <div class="text">{{ title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<template v-for="item in getActions" :key="item.name">
|
||||
<template v-if="item.children">
|
||||
<template v-if="getDropdownActions(item.children).length > 0">
|
||||
<a-dropdown :trigger="['hover']" :dropMenuList="item.children">
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
{{ item.label }}
|
||||
<CaretDownOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
style="padding: 2px 5px"
|
||||
v-for="itemChild in getDropdownActions(item.children)"
|
||||
:key="itemChild.name"
|
||||
@click="itemChild.finalHandle(data, itemChild.name)">
|
||||
<a-button type="link" :disabled="itemChild.dynamicDisabled">
|
||||
{{ itemChild.label }}
|
||||
</a-button>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ns-button
|
||||
:type="item.type || 'default'"
|
||||
:danger="item.danger"
|
||||
:disabled="item.dynamicDisabled"
|
||||
@click="item.finalHandle(data, item.name)">
|
||||
<ns-v-node :content="item.label" />
|
||||
</ns-button>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ns-page-header>
|
||||
<div class="ns-detail-content">
|
||||
<Skeleton
|
||||
active
|
||||
v-for="(detailGroup, index) in getDetail"
|
||||
:key="index"
|
||||
:loading="loading"
|
||||
:paragraph="detailGroup.SkeletonParagraphProps"
|
||||
:title="SkeletonTitleProps">
|
||||
<div class="ns-detail">
|
||||
<a-descriptions>
|
||||
<template #title>
|
||||
<div class="descriptions-title" v-if="detailGroup.title">
|
||||
{{ detailGroup.title }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="detailGroup.items">
|
||||
<template v-for="(item, index) in detailGroup.items" :key="index">
|
||||
<a-descriptions-item v-bind="item" v-if="item.ifShow">
|
||||
<!--默认-->
|
||||
|
||||
<template v-if="!item.type">
|
||||
<span class="ns-detail-text">{{ item.value ? 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"
|
||||
v-if="src"
|
||||
:src="src"
|
||||
fallback="data:image/png;base64,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" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-image
|
||||
class="ns-detail-image"
|
||||
:width="64"
|
||||
v-if="item.value"
|
||||
: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-else-if="item.type === 'NsViewListTable'">
|
||||
<NsViewListTable v-bind="item.props.tableConfig" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.type === 'map'">
|
||||
<NsMapV2 :value="item['value']" :viewOnly="true" />
|
||||
</template>
|
||||
|
||||
<template v-for="slot in Object.keys($slots)" :key="slot">
|
||||
<div v-if="item.type === slot" :style="item.style" :class="`ns-detail-${slot}`">
|
||||
<slot :name="slot" v-bind="item || {}"> </slot>
|
||||
</div>
|
||||
</template>
|
||||
</a-descriptions-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watch, PropType, reactive } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { cloneDeep, get, isFunction, isUndefined } from 'lodash-es';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { Action, useAction } from '/nerv-lib/use';
|
||||
export interface DetailItem {
|
||||
label: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
ifShow?: Boolean | Function;
|
||||
format?: Function;
|
||||
type?: string; // 现支持image
|
||||
}
|
||||
|
||||
export interface DetailGroup {
|
||||
title: string;
|
||||
items: Array<DetailItem>;
|
||||
}
|
||||
interface ColumnActions {
|
||||
back: Boolean;
|
||||
actions: Action[];
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NsViewDetailV2',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '查看',
|
||||
},
|
||||
detail: {
|
||||
type: Array as PropType<DetailGroup[]>,
|
||||
default: () => [],
|
||||
},
|
||||
api: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
headActions: {
|
||||
type: Object as PropType<ColumnActions>,
|
||||
default: () => ({
|
||||
actions: [],
|
||||
}),
|
||||
},
|
||||
dataHandle: Function,
|
||||
},
|
||||
components: { Skeleton, LeftOutlined },
|
||||
setup(props) {
|
||||
const SkeletonTitleProps = ref({
|
||||
width: 150,
|
||||
});
|
||||
const route = useRoute();
|
||||
const dataRef = ref();
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...route.query,
|
||||
...route.params,
|
||||
dataRef: dataRef,
|
||||
};
|
||||
});
|
||||
|
||||
let loading = ref<boolean>(true);
|
||||
|
||||
function request() {
|
||||
const { query } = route;
|
||||
http.get(props.api, query).then((res) => {
|
||||
// dataRef.value = res.data;
|
||||
dataRef.value = props.dataHandle ? props.dataHandle(res.data) : res.data;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
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('80%');
|
||||
} else {
|
||||
SkeletonWidth.push('30%');
|
||||
}
|
||||
}
|
||||
const { title, items } = group;
|
||||
return {
|
||||
title: title,
|
||||
items: items.map((item) => {
|
||||
if (!loading.value) {
|
||||
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,
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
//支持自定义按钮
|
||||
const { transformAction, filterActionNoAuth } = useAction();
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(props.headActions.actions);
|
||||
actions = actions
|
||||
.filter((action: Action) => {
|
||||
return filterActionNoAuth(action, getData.value);
|
||||
})
|
||||
.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
const getDropdownActions = (actions) => {
|
||||
actions = actions.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
};
|
||||
return {
|
||||
getDetail,
|
||||
loading,
|
||||
dataRef,
|
||||
SkeletonTitleProps,
|
||||
getActions,
|
||||
getDropdownActions,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onBack() {
|
||||
//debugger;
|
||||
this.$router.go(-1);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-view {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
background: #e5ebf0;
|
||||
}
|
||||
.ns-detail-content {
|
||||
border-top: 16px solid #e5ebf0;
|
||||
padding: 16px 21px;
|
||||
background: #fff;
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph li:nth-child(n)) {
|
||||
display: block;
|
||||
margin-right: 4%;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-paragraph li:nth-child(3n + 3)) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-skeleton-content) {
|
||||
padding: 0 8px 10px 10px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label) {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label),
|
||||
:deep(.ant-descriptions-item-content) {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-view) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item) {
|
||||
padding-right: 20px;
|
||||
|
||||
&:nth-child(2n) {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
&:nth-child(3n) {
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.descriptions-title {
|
||||
&:after {
|
||||
content: '';
|
||||
width: 75px;
|
||||
height: 7px;
|
||||
display: block;
|
||||
background: linear-gradient(90deg, @primary-color 0%, #fff 82.67%);
|
||||
margin-left: 2px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-page-header {
|
||||
margin-bottom: 0 !important;
|
||||
padding: 7px 16px !important;
|
||||
width: calc(100% + 32px);
|
||||
margin-left: -16px;
|
||||
.title {
|
||||
cursor: pointer;
|
||||
font-size: 18px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ns-detail {
|
||||
border-bottom: 1px solid #ecedef;
|
||||
|
||||
&:last-child {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
&:first-child {
|
||||
:deep(.ant-descriptions-header) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-header) {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.ant-descriptions-title {
|
||||
line-height: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-image) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-detail-html {
|
||||
:deep(table) {
|
||||
border-top: 1px solid #ffffff;
|
||||
border-left: 1px solid #ffffff;
|
||||
|
||||
:deep(p) {
|
||||
font-size: 12px;
|
||||
color: #898e91;
|
||||
}
|
||||
}
|
||||
|
||||
: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);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-detail-content {
|
||||
:deep(.ns-basic-table) {
|
||||
padding-top: 0;
|
||||
.ant-table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
:deep(.ns-detail-tableConfig) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-row > td) {
|
||||
padding-bottom: 8px !important;
|
||||
}
|
||||
</style>
|
||||
101
lib/saas/view/service/edit-form.vue
Normal file
101
lib/saas/view/service/edit-form.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div :key="'edieForm_' + $route.name" class="ns-view">
|
||||
<ns-page-header class="ns-page-header" :sticky="sticky" :title="title">
|
||||
<template #extra>
|
||||
<!-- todo 隐藏取消-->
|
||||
<a-button type="primary" @click="submit" :disabled="!nsFormElRef?.validateResult"
|
||||
>提交</a-button
|
||||
>
|
||||
<a-button style="margin-left: 10px" @click="navigateBack">返回</a-button>
|
||||
</template>
|
||||
</ns-page-header>
|
||||
<div class="ns-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 } from 'vue';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { useRoute } 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,
|
||||
getApi: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsFormElRef = ref();
|
||||
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>
|
||||
:deep(.ns-page-header) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.ns-add-form {
|
||||
:deep(.ns-form) {
|
||||
padding: 32px 24px;
|
||||
// 第一个子表单Title距离顶部为0
|
||||
&.ns-flex-form-vertical
|
||||
.ns-form-body:first-child
|
||||
.ns-form-item:first-child
|
||||
.ns-form-body
|
||||
.ns-child-form-title {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
51
lib/saas/view/service/error-403.vue
Normal file
51
lib/saas/view/service/error-403.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="ns-view">
|
||||
<a-layout class="ns-application" style="height: 100%">
|
||||
<ns-header class="ns-header-menu" />
|
||||
<a-layout>
|
||||
<ns-sider class="ns-left-menu" />
|
||||
<a-layout>
|
||||
<a-layout-content class="content" :style="{ minHeight: '400px', overflow: 'auto' }">
|
||||
<div>
|
||||
<ns-icon name="noResource" size="250" />
|
||||
</div>
|
||||
<!-- <img src="/asset/image/noResource.png" /> -->
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import NsHeader from '/nerv-base/view/system/layout/header.vue';
|
||||
import NsSider from '/nerv-base/view/system/layout/sider.vue';
|
||||
export default defineComponent({
|
||||
name: 'Error403View',
|
||||
components: { NsHeader, NsSider },
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
created() {},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.application {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
background-color: #f4f7f9;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content {
|
||||
height: calc(100% - 64px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
143
lib/saas/view/service/list-table.vue
Normal file
143
lib/saas/view/service/list-table.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="ns-list-table ns-view">
|
||||
<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, ref, unref, nextTick } from 'vue';
|
||||
import { tableProps } from '/nerv-lib/component/table/props';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { cloneDeep, get, isArray } from 'lodash-es';
|
||||
tableProps.expand = PropTypes.bool.def(false);
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewListTable',
|
||||
props: {
|
||||
...tableProps,
|
||||
title: PropTypes.string,
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsTableRef = ref();
|
||||
const getPageParams = (dataRef, curepage?) => {
|
||||
let pageCount = get(dataRef.value, props.pageCountField);
|
||||
|
||||
let pageSize = get(dataRef.value, props.sizeField);
|
||||
let total = 0;
|
||||
let page = 0;
|
||||
const tableData = get(unref(dataRef), props.listField);
|
||||
if (pageCount) {
|
||||
let totalMax = pageCount * pageSize;
|
||||
if (get(dataRef.value, props.totalField)) {
|
||||
total =
|
||||
totalMax > get(dataRef.value, props.totalField)
|
||||
? get(dataRef.value, props.totalField)
|
||||
: totalMax;
|
||||
} else {
|
||||
total = totalMax;
|
||||
}
|
||||
} else {
|
||||
total = get(dataRef.value, props.totalField);
|
||||
}
|
||||
|
||||
if (isArray(tableData) ? !tableData.length : false && curepage) {
|
||||
//后端从0开始
|
||||
if (curepage !== 0) {
|
||||
page =
|
||||
curepage > pageCount - props.pageFieldOffset
|
||||
? pageCount - props.pageFieldOffset
|
||||
: curepage;
|
||||
}
|
||||
} else {
|
||||
page = curepage;
|
||||
}
|
||||
|
||||
return { total, page };
|
||||
};
|
||||
const formConfig = cloneDeep(props.formConfig);
|
||||
|
||||
const getFormConfig = computed(() => {
|
||||
const formConfig = cloneDeep(props.formConfig);
|
||||
|
||||
if (formConfig) {
|
||||
formConfig['formLayout'] = 'flexv2';
|
||||
}
|
||||
return formConfig;
|
||||
});
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
title: props.tableTitle,
|
||||
tableTitle: props.title,
|
||||
getPageParams: getPageParams,
|
||||
formConfig: getFormConfig.value,
|
||||
sticky:
|
||||
props.sticky === false
|
||||
? false
|
||||
: { offsetHeader: props.headerActions.length === 0 && !props.title ? 0 : 89 },
|
||||
}));
|
||||
|
||||
if (formConfig) {
|
||||
nextTick(() => {
|
||||
unref(nsTableRef)?.formElRef?.triggerSubmit();
|
||||
});
|
||||
}
|
||||
|
||||
return { getBindValue, nsTableRef };
|
||||
},
|
||||
//缓存界面重新调用reload
|
||||
activated() {
|
||||
if (unref(this.nsTableRef)) {
|
||||
unref(this.nsTableRef).reload();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
//关闭 sticky 模式 底部滚动条
|
||||
|
||||
:deep(.ns-table) {
|
||||
.ns-table-main {
|
||||
padding: 0px 16px;
|
||||
border-top: 16px solid #e5ebf0;
|
||||
// margin: 16px;
|
||||
// background-color: #fff;
|
||||
}
|
||||
.ns-table-search {
|
||||
padding: 16px 16px 0;
|
||||
// margin: 16px;
|
||||
background: #fff;
|
||||
// border-width: 16px 0 16px 0px;
|
||||
// border-color: #e5ebf0;
|
||||
// border-style: solid;
|
||||
border-top: 16px solid #e5ebf0;
|
||||
}
|
||||
.ns-table-header {
|
||||
position: sticky;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: @white;
|
||||
}
|
||||
.ant-table-sticky-scroll {
|
||||
display: none !important;
|
||||
}
|
||||
.ns-basic-table {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ns-form::after) {
|
||||
background-color: #f0f2f5;
|
||||
display: none;
|
||||
margin: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
</style>
|
||||
434
lib/saas/view/service/updatePassWord.vue
Normal file
434
lib/saas/view/service/updatePassWord.vue
Normal file
@@ -0,0 +1,434 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="signUp ns-view">
|
||||
<a-layout>
|
||||
<a-layout-header class="home_header" style="position: relative">
|
||||
<img
|
||||
v-if="themeConfig.logoUrl"
|
||||
:src="themeConfig.logoUrl"
|
||||
style="width: 192px; height: 48px; object-fit: contain" />
|
||||
<ns-icon v-else name="login" style="width: auto; height: 40px" />
|
||||
</a-layout-header>
|
||||
<a-layout-content>
|
||||
<div class="content">
|
||||
<h1>{{ title }}</h1>
|
||||
<div class="lg_card">
|
||||
<h2>{{ themeConfig.webSiteTitle ? themeConfig.webSiteTitle : subTitle }}</h2>
|
||||
<div class="form">
|
||||
<ns-form
|
||||
style="width: 100%; margin: auto"
|
||||
ref="formOneRef"
|
||||
formLayout="修改"
|
||||
:schemas="formSchema"
|
||||
:model="data" />
|
||||
<div class="step-box">
|
||||
<a-button @click="navigateBack">取消</a-button>
|
||||
<a-button @click="submit" type="primary" :disabled="!formOneRef?.validateResult"
|
||||
>确认修改</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
<a class="toLogin" @click="toLogin" v-show="step === 0">登录已有豪恩账号 ></a>
|
||||
<!-- <a-layout-footer>Copyright 2021 鼎云科技 All Rights Reserved</a-layout-footer> -->
|
||||
</a-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { Cookies } from '/nerv-lib/util/cookie';
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
export default defineComponent({
|
||||
name: 'UpdatePassWord',
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const imageUrl = ref('/asset/image/login/logo.png');
|
||||
const title = ref('修改密码');
|
||||
const subTitle = ref('sass平台');
|
||||
const { uuid } = router.currentRoute.value.query;
|
||||
const formOneRef = ref();
|
||||
const configStore = appConfigStore();
|
||||
|
||||
const { getThemeConfig: themeConfig } = storeToRefs(configStore);
|
||||
if (configStore.updatePassWordInfo.title) {
|
||||
title.value = configStore.updatePassWordInfo.title;
|
||||
subTitle.value = configStore.updatePassWordInfo.subtitle;
|
||||
}
|
||||
let data = reactive({});
|
||||
const authorizationStore = authorizationService();
|
||||
const { navigateBackV2: navigateBack } = useNavigate();
|
||||
const formSchema = reactive([
|
||||
{
|
||||
field: 'oldPassword',
|
||||
label: '原密码',
|
||||
component: 'NsInputPassword',
|
||||
componentProps: {
|
||||
autocomplete: 'new-password',
|
||||
placeholder: '请输入原密码',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '原密码不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
field: 'newPassword',
|
||||
label: '新密码',
|
||||
component: 'NsInputPassword',
|
||||
componentProps: {
|
||||
autocomplete: 'new-password',
|
||||
placeholder: '密码不少于6位,支持字母、数字',
|
||||
onchange: () => {
|
||||
data.confirmPassword = '';
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '新密码不能为空',
|
||||
trigger: 'change',
|
||||
},
|
||||
{
|
||||
validator: async (rule, value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (!/^([a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*])[a-z\d!@#$%^&*]{5,17}$/.test(value)) {
|
||||
let strArray = value.split('');
|
||||
if (strArray.indexOf(' ') !== -1) {
|
||||
return Promise.reject('不能包含空格');
|
||||
} else {
|
||||
if (/[\u4E00-\u9FA5\uF900-\uFA2D]{1,}/.test(value)) {
|
||||
return Promise.reject('不支持中文');
|
||||
} else {
|
||||
return Promise.reject(
|
||||
'需同时包含数字,字母及特殊字符(!@#$%^&*),且只能以字母开头,长度为6-18个字符',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'confirmPassword',
|
||||
label: '确认密码',
|
||||
component: 'NsInputPassword',
|
||||
viewOnly: true,
|
||||
componentProps: {
|
||||
autocomplete: 'new-password',
|
||||
placeholder: '请再次输入密码',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '不能为空',
|
||||
trigger: 'change',
|
||||
},
|
||||
{
|
||||
trigger: 'change',
|
||||
validator: async (rule, value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (value !== data.newPassword) {
|
||||
return Promise.reject('两次密码不一致');
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// const title = ref<string>('修改密码');
|
||||
|
||||
const submit = (): void => {
|
||||
formOneRef.value.triggerSubmit().then((data) => {
|
||||
loading.value = true;
|
||||
async function update() {
|
||||
try {
|
||||
const res = await http.post(configStore.updatePassWordInfo?.api, data);
|
||||
if (res.success) {
|
||||
NsMessage.success('修改成功,需重新登陆', 1, () => {
|
||||
Cookies.remove('nervsid');
|
||||
sessionStorage.clear();
|
||||
router.push('/login');
|
||||
authorizationStore.clearAuthorization();
|
||||
});
|
||||
}
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
update();
|
||||
});
|
||||
};
|
||||
return {
|
||||
themeConfig,
|
||||
submit,
|
||||
formSchema,
|
||||
data,
|
||||
subTitle,
|
||||
formOneRef,
|
||||
imageUrl,
|
||||
router,
|
||||
uuid,
|
||||
navigateBack,
|
||||
title,
|
||||
loading,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const _this = this;
|
||||
window.sessionStorage.clear();
|
||||
document.onkeydown = function (e) {
|
||||
const key = window.event === undefined ? e.keyCode : window.event.keyCode;
|
||||
key === 13 ? _this.submit() : '';
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.onkeydown = function (e) {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-form.ns-vertical-form {
|
||||
width: 80% !important;
|
||||
}
|
||||
.ns-form::after {
|
||||
background-color: #f0f2f5;
|
||||
display: none;
|
||||
margin: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
.finalSubmitDiv {
|
||||
width: 100%;
|
||||
margin-top: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.finalSubmit {
|
||||
width: 90px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.verifyImgCode .ant-form-item) {
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
.signUp {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
line-height: 44px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.lg_card {
|
||||
width: 80%;
|
||||
min-width: 800px;
|
||||
height: 70%;
|
||||
min-height: 400px;
|
||||
background: #fff;
|
||||
// box-shadow: 0 2px 2px 0 rgb(0 0 0 / 5%), 0 0 18px 0 rgb(0 0 0 / 8%);
|
||||
border-radius: 6px;
|
||||
margin: auto !important;
|
||||
// padding: 15px 35px 35px;
|
||||
.form {
|
||||
width: 50%;
|
||||
min-width: 400px;
|
||||
margin: auto;
|
||||
padding-top: 40px;
|
||||
}
|
||||
h2 {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid rgba(23, 46, 61, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
.toLogin {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
color: #3aa7f2;
|
||||
display: block;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.icon {
|
||||
color: @primary-color;
|
||||
// background: #000;
|
||||
// min-height: 25px;
|
||||
// min-width: 25px;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
// border-radius: 50%;
|
||||
}
|
||||
.ant-layout-header {
|
||||
height: 64px !important;
|
||||
background: #fff;
|
||||
line-height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ant-layout-content {
|
||||
border-top: 1px solid #ecedef;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
justify-content: center;
|
||||
height: calc(100vh - 128px) !important;
|
||||
}
|
||||
|
||||
.ant-layout-footer {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #8f8f8f;
|
||||
}
|
||||
|
||||
.lg_card_title {
|
||||
width: 360px;
|
||||
margin: 60px auto 0;
|
||||
height: 48px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #172e3d;
|
||||
text-align: left;
|
||||
line-height: 20px;
|
||||
span {
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
color: #808d96;
|
||||
font-weight: lighter;
|
||||
strong {
|
||||
color: #1d2d3d;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #ff1744;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
:deep(#app .ant-form-item-label > label) {
|
||||
color: #606060;
|
||||
}
|
||||
:deep(.lg_card .ant-col-5) {
|
||||
width: 30%;
|
||||
flex: 0 0 30%;
|
||||
text-align: right;
|
||||
}
|
||||
:deep(.ant-layout-content) {
|
||||
margin: 0;
|
||||
}
|
||||
.success-box {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 50px 0 4px;
|
||||
h3 {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
h3,
|
||||
span {
|
||||
color: #ff4919;
|
||||
}
|
||||
p {
|
||||
margin: auto;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
color: #1d2d3d;
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
:deep(.step-box) {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
// margin-right: 50px;
|
||||
position: relative;
|
||||
left: 20px;
|
||||
button {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
:deep(.ant-steps-item-title::after) {
|
||||
background: transparent !important;
|
||||
border-top-width: 1px;
|
||||
border-top-style: dashed;
|
||||
border-top-color: #ecedef;
|
||||
}
|
||||
:deep(.ant-steps-item-process
|
||||
> .ant-steps-item-container
|
||||
> .ant-steps-item-content
|
||||
> .ant-steps-item-title::after) {
|
||||
border-top-color: #ff4919;
|
||||
}
|
||||
|
||||
:deep(.ant-layout-header) {
|
||||
max-height: 64px !important;
|
||||
min-height: 64px !important;
|
||||
}
|
||||
|
||||
:deep(.ant-layout-footer) {
|
||||
max-height: 64px !important;
|
||||
min-height: 64px !important;
|
||||
height: 64px !important;
|
||||
}
|
||||
.home_header {
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// img {
|
||||
// height: 32px !important;
|
||||
// width: 220px !important;
|
||||
// transform: translateY(0px) translateX(0px);
|
||||
// }
|
||||
}
|
||||
:deep(.ant-layout) {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
134
lib/saas/view/service/view-form.vue
Normal file
134
lib/saas/view/service/view-form.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="ns-view">
|
||||
<a-spin :spinning="isLoading">
|
||||
<a-page-header class="ns-page-header" :title="title">
|
||||
<template #extra>
|
||||
<!-- todo 隐藏取消-->
|
||||
<ns-button @click="navigateBack">返 回</ns-button>
|
||||
<ns-button
|
||||
style="margin-left: 10px"
|
||||
type="primary"
|
||||
@click="submit"
|
||||
:disabled="!nsFormElRef?.validateResult"
|
||||
>保 存</ns-button
|
||||
>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="ns-add-form">
|
||||
<ns-form ref="nsFormElRef" v-bind="getBindValue" :model="formModel">
|
||||
<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, watch } 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 { HttpRequestConfig, useApi } from '/nerv-lib/use/use-api';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsViewForm',
|
||||
props: {
|
||||
...formProps,
|
||||
api: PropTypes.string,
|
||||
editApi: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
const nsFormElRef = ref();
|
||||
const { httpRequest } = useApi();
|
||||
const isLoading = ref(false);
|
||||
const { navigateBack } = useNavigate();
|
||||
const route = useRoute();
|
||||
const formModel = ref<Recordable>(props.model || {});
|
||||
|
||||
const request = () => {
|
||||
const { api, editApi } = props;
|
||||
const { params, query } = route;
|
||||
const requestConfig: HttpRequestConfig = { method: 'get' };
|
||||
httpRequest({
|
||||
api: editApi ? editApi : api,
|
||||
params: query,
|
||||
pathParams: params,
|
||||
requestConfig,
|
||||
}).then((res: Recordable) => {
|
||||
formModel.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => props.api, () => props.editApi],
|
||||
() => {
|
||||
console.log('editApi', props.editApi);
|
||||
request();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
}));
|
||||
function submit() {
|
||||
nsFormElRef.value
|
||||
.triggerSubmit()
|
||||
.then((data: any) => {
|
||||
isLoading.value = true;
|
||||
http
|
||||
.post(props.api, data)
|
||||
.then(() => {
|
||||
isLoading.value = false;
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
navigateBack();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
return { nsFormElRef, submit, getBindValue, isLoading, navigateBack, formModel };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
//.ns-page-header {
|
||||
// margin: 0px 16px 20px 0;
|
||||
// border-bottom: 1px solid #f0f2f5;
|
||||
//}
|
||||
.ns-add-form .ns-vertical-form {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
:deep(.ant-spin-nested-loading) {
|
||||
height: 100%;
|
||||
}
|
||||
.ant-spin-nested-loading {
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.ant-spin-container) {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.ns-add-form) {
|
||||
height: calc(100% - 83px) !important;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user