|
@@ -0,0 +1,423 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div
|
|
|
|
|
+ style="height:100%; position: relative; font-size: 16px;"
|
|
|
|
|
+ v-loading="loading"
|
|
|
|
|
+ class="d-flex align-center justify-center"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="legend d-flex pa-3 justify-space-between">
|
|
|
|
|
+ <!-- <div class="select pointerEvents">
|
|
|
|
|
+ <v-select
|
|
|
|
|
+ v-model="type"
|
|
|
|
|
+ :items="items"
|
|
|
|
|
+ dense
|
|
|
|
|
+ outlined
|
|
|
|
|
+ hide-details
|
|
|
|
|
+ style="background-color: #fff; width: 150px;"
|
|
|
|
|
+ @change="changeType"
|
|
|
|
|
+ ></v-select>
|
|
|
|
|
+ </div> -->
|
|
|
|
|
+ <!-- 图例 -->
|
|
|
|
|
+ <div class="d-flex align-center">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="item in legend"
|
|
|
|
|
+ :key="item.title"
|
|
|
|
|
+ class="d-flex mr-5"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="pa-3 mr-3 rounded-circle" :style="`background-color: ${item.color};`"></div>
|
|
|
|
|
+ {{ item.title }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <v-switch
|
|
|
|
|
+ v-if="!meta"
|
|
|
|
|
+ class="pointerEvents mt-0 ml-3"
|
|
|
|
|
+ hide-details
|
|
|
|
|
+ v-model="showMeta"
|
|
|
|
|
+ :false-value="false"
|
|
|
|
|
+ :true-value="true"
|
|
|
|
|
+ label="显示元数据"
|
|
|
|
|
+ @change="handleChangeMeta"
|
|
|
|
|
+ ></v-switch>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <MEmpty v-if="empty"></MEmpty>
|
|
|
|
|
+ <relation-graph
|
|
|
|
|
+ v-show="!empty"
|
|
|
|
|
+ ref="graphRef"
|
|
|
|
|
+ :options="graphOptions"
|
|
|
|
|
+ :on-node-click="onNodeClick"
|
|
|
|
|
+ :on-line-click="onLineClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #node="{node}">
|
|
|
|
|
+ <div @contextmenu.prevent="handleContextmenu($event, node)">
|
|
|
|
|
+ <div
|
|
|
|
|
+ :style="{ width: node.width + 'px', height: node.height + 'px' }"
|
|
|
|
|
+ :class="{ 'node-active': menu.activeId === +node.id }"
|
|
|
|
|
+ class="rounded-circle"
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ :style="{ 'background-color': node.color + '44', width: 20 * ((node.text || '').length) + 'px' }"
|
|
|
|
|
+ class="node-text"
|
|
|
|
|
+ :class="{ 'node-active': menu.activeId === +node.id }"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ node.text || '' }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template #graph-plug>
|
|
|
|
|
+ <v-menu v-model="menu.show" attach :position-x="menu.x" :position-y="menu.y" absolute offset-y min-width="200">
|
|
|
|
|
+ <v-list dense>
|
|
|
|
|
+ <v-list-item v-for="(k, i) in menu.items" :key="i" @click="k.handle">
|
|
|
|
|
+ <v-list-item-title>{{ k.title }}</v-list-item-title>
|
|
|
|
|
+ </v-list-item>
|
|
|
|
|
+ </v-list>
|
|
|
|
|
+ </v-menu>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </relation-graph>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import RelationGraph from 'relation-graph'
|
|
|
|
|
+import MEmpty from '@/components/Common/empty'
|
|
|
|
|
+import { api } from '@/api/dataGovernance'
|
|
|
|
|
+const defaultNodeColor = 'rgba(238, 178, 94, 1)'
|
|
|
|
|
+const NODES_SIZE = {
|
|
|
|
|
+ width: 30,
|
|
|
|
|
+ height: 30
|
|
|
|
|
+}
|
|
|
|
|
+// const LINE_COLOR_MAP = {
|
|
|
|
|
+// 包含: '#F44336', // red
|
|
|
|
|
+// 影响: '#E91E63', // pink
|
|
|
|
|
+// 依赖: '#3F51B5', // indigo
|
|
|
|
|
+// 来源: '#4CAF50', // green
|
|
|
|
|
+// 引用: '#9C27B0', // purple
|
|
|
|
|
+// 继承: '#673AB7', // deep-purple
|
|
|
|
|
+// 标记: '#2196F3', // blue
|
|
|
|
|
+// 使用: '#03A9F4', // light-blue
|
|
|
|
|
+// 关联: '#00BCD4', // cyan
|
|
|
|
|
+// 拥有: '#009688', // teal
|
|
|
|
|
+// 下级: '#8BC34A', // light-green
|
|
|
|
|
+// 上级: '#CDDC39', // lime
|
|
|
|
|
+// 联动: '#FF9800' // orange
|
|
|
|
|
+// }
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'details-graph',
|
|
|
|
|
+ components: { RelationGraph, MEmpty },
|
|
|
|
|
+ props: {
|
|
|
|
|
+ toApi: {
|
|
|
|
|
+ type: Function,
|
|
|
|
|
+ default: api.getResourceGraph
|
|
|
|
|
+ },
|
|
|
|
|
+ meta: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ default: false
|
|
|
|
|
+ },
|
|
|
|
|
+ query: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ default: () => ({})
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ data () {
|
|
|
|
|
+ return {
|
|
|
|
|
+ menu: {
|
|
|
|
|
+ x: 0,
|
|
|
|
|
+ y: 0,
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ items: [
|
|
|
|
|
+ { title: '查看', handle: this.handleView }
|
|
|
|
|
+ ],
|
|
|
|
|
+ activeId: +this.$route.params.id,
|
|
|
|
|
+ item: {}
|
|
|
|
|
+ },
|
|
|
|
|
+ empty: true,
|
|
|
|
|
+ items: [
|
|
|
|
|
+ { text: '全链关系', value: 'all' },
|
|
|
|
|
+ { text: '血缘关系', value: 'kinship' },
|
|
|
|
|
+ { text: '影响关系', value: 'impact' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ type: 'all',
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ graphOptions: {
|
|
|
|
|
+ // defaultJunctionPoint: 'lr',
|
|
|
|
|
+ // 这里可以参考"Graph 图谱"中的参数进行设置 https://www.relation-graph.com/#/docs/graph
|
|
|
|
|
+ debug: false, // 是否开始调试模式,调试模式下会在控制台打印额外的日志信息
|
|
|
|
|
+ showDebugPanel: false, // 是否显示调试按钮,通过此按钮可以打印配置、数据等
|
|
|
|
|
+ backgroundImage: '', // 图谱水印url,如:https://ssl.relation-graph.com/images/relatioon-graph-canvas-bg.png
|
|
|
|
|
+ downloadImageFileName: '', // 下载图片时,图片的名称
|
|
|
|
|
+ disableZoom: false, // 是否禁用图谱的缩放功能
|
|
|
|
|
+ disableDragNode: false, // 是否禁用图谱中节点的拖动
|
|
|
|
|
+ moveToCenterWhenRefresh: true, // 当图谱刷新后(调用setJsonData或refresh方法都会触发),让图谱根据节点居中(图片会默认将根节点作为中心展示,此选项会根据节点分布寻找中心)
|
|
|
|
|
+ zoomToFitWhenRefresh: true, // 当图谱刷新后(调用setJsonData或refresh方法都会触发),是否让图谱缩放到适合可见区域大小,此选项不适用于fixed和force布局
|
|
|
|
|
+ useAnimationWhenRefresh: true, // 当图谱刷新后(调用setJsonData或refresh方法都会触发),使用动画让图居中、缩放
|
|
|
|
|
+ useAnimationWhenExpanded: true,
|
|
|
|
|
+ defaultFocusRootNode: true, // 默认为根节点添加一个被选中的样式
|
|
|
|
|
+ disableNodeClickEffect: false, // 是否禁用节点默认的点击效果(选中、闪烁)
|
|
|
|
|
+ disableLineClickEffect: false, // 是否禁用线条默认的点击效果(选中、闪烁)
|
|
|
|
|
+ allowShowZoomMenu: true, // 是否在右侧菜单栏显示放大缩小的按钮,此设置和disableZoom不冲突
|
|
|
|
|
+ allowAutoLayoutIfSupport: true, // 是否在工具栏中显示【自动布局】按钮(只有在布局支持且此选项为true时才会显示的按钮)
|
|
|
|
|
+ allowShowRefreshButton: true, // 是否在工具栏中显示【刷新】按钮
|
|
|
|
|
+ allowShowDownloadButton: true, // 是否在工具栏中显示【下载图片】按钮
|
|
|
|
|
+ backgroundImageNoRepeat: false, // 只在右下角显示水印,不重复显示水印
|
|
|
|
|
+ allowSwitchLineShape: true, // 是否在工具栏中显示切换线条形状的按钮
|
|
|
|
|
+ allowSwitchJunctionPoint: true, // 是否在工具栏中显示切换连接点位置的按钮
|
|
|
|
|
+ isMoveByParentNode: false, // 是否在拖动节点后让子节点跟随
|
|
|
|
|
+ defaultExpandHolderPosition: 'hide', // 默认的节点展开/关闭按钮位置(left/top/right/bottom/hide)
|
|
|
|
|
+ defaultNodeColor, // 默认的节点背景颜色
|
|
|
|
|
+ checkedLineColor: '#FD8B37', // 当线条被选中时的颜色
|
|
|
|
|
+ defaultNodeFontColor: '#ffffff', // 默认的节点文字颜色
|
|
|
|
|
+ defaultNodeBorderColor: '#90EE90', // 默认的节点边框颜色
|
|
|
|
|
+ defaultNodeBorderWidth: 0, // 默认的节点边框粗细(像素)
|
|
|
|
|
+ defaultLineColor: '#cccccc', // 默认的线条颜色
|
|
|
|
|
+ defaultLineWidth: 2, // 默认的线条粗细(像素)
|
|
|
|
|
+ defaultLineShape: 2, // 默认的线条样式(1:直线/2:样式2/3:样式3/4:折线/5:样式5/6:样式6)使用示例
|
|
|
|
|
+ defaultNodeShape: 0, // 默认的节点形状,0:圆形;1:矩形
|
|
|
|
|
+ defaultShowLineLabel: true, // 默认是否显示连线文字,v2版本此选项已无效,主要是这个选项没什么用
|
|
|
|
|
+ hideNodeContentByZoom: true, // 是否根据缩放比例隐藏节点内容
|
|
|
|
|
+ // disableDragCanvas: false,
|
|
|
|
|
+ // lineUseTextPath: false,
|
|
|
|
|
+ defaultLineMarker: { // 默认的线条箭头样式,示例参考:配置工具中的选项:连线箭头样式
|
|
|
|
|
+ markerWidth: 24,
|
|
|
|
|
+ markerHeight: 24,
|
|
|
|
|
+ refX: 6,
|
|
|
|
|
+ refY: 6,
|
|
|
|
|
+ data: 'M2,2 L10,6 L2,10 L6,6 L2,2'
|
|
|
|
|
+ },
|
|
|
|
|
+ layouts: [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '自动布局',
|
|
|
|
|
+ layoutName: 'center', // 布局方式(tree树状布局/center中心布局/force自动布局)
|
|
|
|
|
+ from: 'left',
|
|
|
|
|
+ maxLayoutTimes: 20,
|
|
|
|
|
+ layoutClassName: 'seeks-layout-force',
|
|
|
|
|
+ useLayoutStyleOptions: false,
|
|
|
|
|
+ defaultNodeColor: '#FFC5A6',
|
|
|
|
|
+ defaultNodeFontColor: '#000000',
|
|
|
|
|
+ defaultNodeBorderColor: '#efefef',
|
|
|
|
|
+ defaultNodeBorderWidth: 1,
|
|
|
|
|
+ defaultLineColor: '#FD8B37',
|
|
|
|
|
+ defaultLineWidth: 1,
|
|
|
|
|
+ defaultShowLineLabel: true,
|
|
|
|
|
+ defaultLineMarker: {
|
|
|
|
|
+ markerWidth: 12,
|
|
|
|
|
+ markerHeight: 12,
|
|
|
|
|
+ refX: 6,
|
|
|
|
|
+ refY: 6,
|
|
|
|
|
+ data: 'M2,2 L10,6 L2,10 L6,6 L2,2'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ config: {
|
|
|
|
|
+ BusinessDomain: {
|
|
|
|
|
+ color: '#9FA8DA',
|
|
|
|
|
+ title: '业务域',
|
|
|
|
|
+ className: 'sourceNode',
|
|
|
|
|
+ ...NODES_SIZE
|
|
|
|
|
+ },
|
|
|
|
|
+ DataSource: {
|
|
|
|
|
+ color: '#4CAF50',
|
|
|
|
|
+ title: '数据源',
|
|
|
|
|
+ className: 'modelNode',
|
|
|
|
|
+ ...NODES_SIZE
|
|
|
|
|
+ },
|
|
|
|
|
+ // DataResource: { // 资源
|
|
|
|
|
+ // color: '#9FA8DA',
|
|
|
|
|
+ // title: '数据资源',
|
|
|
|
|
+ // className: 'sourceNode',
|
|
|
|
|
+ // ...NODES_SIZE
|
|
|
|
|
+ // },
|
|
|
|
|
+ // DataModel: { // 模型
|
|
|
|
|
+ // color: '#EF9A9A',
|
|
|
|
|
+ // title: '数据模型',
|
|
|
|
|
+ // className: 'modelNode',
|
|
|
|
|
+ // ...NODES_SIZE
|
|
|
|
|
+ // },
|
|
|
|
|
+ // DataMetric: { // 指标
|
|
|
|
|
+ // color: '#00BCD4',
|
|
|
|
|
+ // title: '数据指标',
|
|
|
|
|
+ // className: 'metricNode',
|
|
|
|
|
+ // ...NODES_SIZE
|
|
|
|
|
+ // },
|
|
|
|
|
+ data_standard: { // 标准
|
|
|
|
|
+ color: '#009688',
|
|
|
|
|
+ title: '数据标准',
|
|
|
|
|
+ className: 'standardNode',
|
|
|
|
|
+ ...NODES_SIZE
|
|
|
|
|
+ },
|
|
|
|
|
+ DataLabel: { // 标签
|
|
|
|
|
+ color: '#9C27B0',
|
|
|
|
|
+ title: '数据标签',
|
|
|
|
|
+ className: 'labelNode',
|
|
|
|
|
+ ...NODES_SIZE
|
|
|
|
|
+ },
|
|
|
|
|
+ DataMeta: { // 元数据
|
|
|
|
|
+ color: defaultNodeColor,
|
|
|
|
|
+ title: '元数据',
|
|
|
|
|
+ className: '',
|
|
|
|
|
+ ...NODES_SIZE
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ showMeta: this.meta
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ legend () {
|
|
|
|
|
+ return Object.values(this.config)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted () {
|
|
|
|
|
+ this.init()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ handleContextmenu (v, node) {
|
|
|
|
|
+ const { left, top } = this.$refs.graphRef.$el.getBoundingClientRect()
|
|
|
|
|
+ this.menu.x = v.clientX - left
|
|
|
|
|
+ this.menu.y = v.clientY - top
|
|
|
|
|
+ this.menu.item = node
|
|
|
|
|
+ this.menu.show = true
|
|
|
|
|
+ },
|
|
|
|
|
+ handleView () {
|
|
|
|
|
+ this.draw({ id: +this.menu.item.id }, () => {
|
|
|
|
|
+ this.menu.activeId = +this.menu.item.id
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ handleChangeMeta (val) {
|
|
|
|
|
+ this.showMeta = val
|
|
|
|
|
+ this.init()
|
|
|
|
|
+ },
|
|
|
|
|
+ changeType () {
|
|
|
|
|
+ this.init()
|
|
|
|
|
+ },
|
|
|
|
|
+ handleClick (node) {
|
|
|
|
|
+ console.log(node)
|
|
|
|
|
+ },
|
|
|
|
|
+ async init () {
|
|
|
|
|
+ const query = {
|
|
|
|
|
+ ...this.query
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.$route.params.id) {
|
|
|
|
|
+ Object.assign(query, {
|
|
|
|
|
+ id: +this.$route.params.id
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ this.draw(query)
|
|
|
|
|
+ },
|
|
|
|
|
+ async draw (query, successCallback = () => {}) {
|
|
|
|
|
+ // 清空再渲染
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ this.empty = false
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { data } = await this.toApi({
|
|
|
|
|
+ ...query,
|
|
|
|
|
+ type: this.type,
|
|
|
|
|
+ meta: this.showMeta
|
|
|
|
|
+ })
|
|
|
|
|
+ if (!data.nodes || !data.nodes.length) {
|
|
|
|
|
+ this.empty = true
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.graphOptions.downloadImageFileName = data.rootId ?? ''
|
|
|
|
|
+
|
|
|
|
|
+ data.nodes.forEach(ele => {
|
|
|
|
|
+ if (!this.config[ele.node_type]) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ ele.text = ele.name_zh || ele.name_en
|
|
|
|
|
+ Object.assign(ele, this.config[ele.node_type])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // data.lines.forEach(ele => {
|
|
|
|
|
+ // ele.color = LINE_COLOR_MAP[ele.text]
|
|
|
|
|
+ // })
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.$refs.graphRef.setOptions(this.graphOptions, async (graphInstance) => {
|
|
|
|
|
+ if (!this.$refs.graphRef || !this.$refs.graphRef.setJsonData) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$refs.graphRef.setJsonData(data, async (_graphInstance) => {
|
|
|
|
|
+ await _graphInstance.setZoom(75)
|
|
|
|
|
+ successCallback()
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ this.empty = true
|
|
|
|
|
+ this.$snackbar.error(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onNodeClick (nodeObject, $event) {
|
|
|
|
|
+ console.log('onNodeClick:', nodeObject)
|
|
|
|
|
+ },
|
|
|
|
|
+ onLineClick (lineObject, $event) {
|
|
|
|
|
+ console.log('onLineClick:', lineObject)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.legend {
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+ -moz-user-select: none;
|
|
|
|
|
+ -webkit-user-select: none;
|
|
|
|
|
+ -ms-user-select: none;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ z-index: 10;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ .rounded-circle {
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.pointerEvents {
|
|
|
|
|
+ pointer-events: auto;
|
|
|
|
|
+}
|
|
|
|
|
+// ::v-deep .sourceNode .rel-node-checked {
|
|
|
|
|
+// box-shadow: 0 0 0 8px #C5CAE9 !important;
|
|
|
|
|
+// }
|
|
|
|
|
+// ::v-deep .modelNode .rel-node-checked {
|
|
|
|
|
+// box-shadow: 0 0 0 8px #ffd9d9 !important;
|
|
|
|
|
+// }
|
|
|
|
|
+// ::v-deep .metricNode .rel-node-checked {
|
|
|
|
|
+// box-shadow: 0 0 0 8px #a7f3fd !important;
|
|
|
|
|
+// }
|
|
|
|
|
+// ::v-deep .standardNode .rel-node-checked {
|
|
|
|
|
+// box-shadow: 0 0 0 8px #58c1b7 !important;
|
|
|
|
|
+// }
|
|
|
|
|
+// ::v-deep .labelNode .rel-node-checked {
|
|
|
|
|
+// box-shadow: 0 0 0 8px #f4bdff !important;
|
|
|
|
|
+// }
|
|
|
|
|
+::v-deep .rel-node-checked {
|
|
|
|
|
+ box-shadow: unset !important;
|
|
|
|
|
+}
|
|
|
|
|
+.node-active {
|
|
|
|
|
+ box-shadow: 0 0 0px 6px #f3f900 !important;
|
|
|
|
|
+ // background: #000 !important;
|
|
|
|
|
+ // border: 2px solid #000;
|
|
|
|
|
+}
|
|
|
|
|
+.node-text {
|
|
|
|
|
+ color: #000;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ height:25px;
|
|
|
|
|
+ transform: translate(-50%, 0);
|
|
|
|
|
+ line-height: 25px;
|
|
|
|
|
+ left: 50%;
|
|
|
|
|
+ margin-top:5px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+::v-deep .rel-toolbar {
|
|
|
|
|
+ background-color: #f39930;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ .c-current-zoom {
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|