json-view的实现

前端页面展示json,组件封装如下:

先贴一下目录结构:

 

// index.vue

  1 <template>
  2     <div v-if="visible" :class="['json-view-container',theme,`deep-${currentDeep}`]">
  3         <div
  4             :class="['json-view', length ? 'closeable' : '']"
  5             :style="{fontSize: fontSize + 'px',lineHeight: lineHeight + 'px'}"
  6         >
  7             <!--icon-style-square-->
  8             <span
  9                 v-if="length && iconStyle === 'square'" class="angle"
 10                 @click="toggleClose"
 11             >
 12                 <svg
 13                     v-if="innerclosed" :fill="iconColors[0]"
 14                     width="1em" height="1em"
 15                     viewBox="0 0 1792 1792"
 16                     style="width: 1em; height: 1em; color: rgb(42, 161, 152); vertical-align: middle;"
 17                 >
 18                     <path
 19                         d="M1344 800v64q0 14-9 23t-23 9h-352v352q0 14-9 23t-23 9h-64q-14
 20                     0-23-9t-9-23v-352h-352q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h352v-352q0-14
 21                     9-23t23-9h64q14 0 23 9t9 23v352h352q14 0 23 9t9 23zm128
 22                     448v-832q0-66-47-113t-113-47h-832q-66 0-113 47t-47 113v832q0 66 47
 23                     113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5t-203.5
 24                     84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119
 25                     0 203.5 84.5t84.5 203.5z"
 26                     />
 27                 </svg>
 28                 <svg
 29                     v-if="!innerclosed" :fill="iconColors[1]"
 30                     width="1em" height="1em"
 31                     viewBox="0 0 1792 1792"
 32                     style="width: 1em; height: 1em; color: rgb(88, 110, 117); vertical-align: middle;"
 33                 >
 34                     <path
 35                         d="M1344 800v64q0 14-9 23t-23 9h-832q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14
 36                     0 23 9t9 23zm128 448v-832q0-66-47-113t-113-47h-832q-66 0-113 47t-47 113v832q0 66 47
 37                      113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5t-203.5 84.5h-832q-119
 38                      0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"
 39                     />
 40                 </svg>
 41             </span>
 42             <!--icon-style-circle-->
 43             <span
 44                 v-if="length && iconStyle === 'circle'" class="angle"
 45                 @click="toggleClose"
 46             >
 47                 <svg
 48                     v-if="!innerclosed" viewBox="0 0 24 24"
 49                     :fill="iconColors[0]" preserveAspectRatio="xMidYMid meet"
 50                     style=" width: 1em; height: 1em; color: rgb(1, 160, 228); vertical-align: middle;"
 51                 >
 52                     <path
 53                         d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,
 54                     20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7"
 55                     />
 56                 </svg>
 57                 <svg
 58                     v-if="innerclosed" viewBox="0 0 24 24"
 59                     :fill="iconColors[1]" preserveAspectRatio="xMidYMid meet"
 60                     style=" width: 1em; height: 1em; color: rgb(161, 106, 148); vertical-align: middle;"
 61                 >
 62                     <path
 63                         d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,
 64                         16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,
 65                         12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"
 66                     />
 67                 </svg>
 68             </span>
 69             <!--icon-style-triangle-->
 70             <span
 71                 v-if="length && iconStyle === 'triangle'" class="angle"
 72                 @click="toggleClose"
 73             >
 74                 <svg
 75                     v-if="!innerclosed"
 76                     viewBox="0 0 15 15"
 77                     :fill="iconColors[0]"
 78                     style=" width: 1em; height: 1em; padding-left: 2px; color: #3c4047; vertical-align: top;"
 79                 >
 80                     <path d="M0 5l6 6 6-6z"/>
 81                 </svg>
 82                 <svg
 83                     v-if="innerclosed" viewBox="0 0 15 15"
 84                     :fill="iconColors[1]"
 85                     style="width: 1em; height: 1em; padding-left: 2px; color: #3c4047; vertical-align: top;"
 86                 >
 87                     <path d="M0 14l6-6-6-6z"/>
 88                 </svg>
 89             </span>
 90             <div class="content-wrap">
 91                 <p :class="['first-line',length > 0 ? 'pointer' : '']" @click="toggleClose">
 92                     <span v-if="jsonKey" class="json-key">"{{ jsonKey }}": </span>
 93                     <span v-if="length">{{ prefix }}{{ innerclosed ? ('...' + subfix) : '' }}
 94                         <span class="json-note">{{ innerclosed ? (length + ' items') : '' }}</span>
 95                     </span>
 96                     <span v-if="!length">{{ `${isArray ? '[]' : '{}'}${isLast ? '' : ','}` }}</span>
 97                 </p>
 98                 <div v-if="!innerclosed && length" class="json-body">
 99                     <template v-for="(item, index) in items">
100                         <json-view
101                             v-if="item.isJSON"
102                             :key="index"
103                             :closed="isClose()"
104                             :data="item.value"
105                             :json-key="item.key"
106                             :current-deep="templateDeep + 1"
107                             :deep="deep"
108                             :icon-style="iconStyle"
109                             :theme="theme"
110                             :font-size="fontSize"
111                             :line-height="lineHeight"
112                             :icon-color="iconColors"
113                             :is-last="index === items.length - 1"
114                             :has-siblings="item.hasSiblings"
115                         />
116                         <p
117                             v-else :key="index"
118                             class="json-item"
119                         >
120                             <span class="json-key">
121                                 {{ (isArray ? '' : '"' + item.key + '":') }}
122                             </span>
123                             <span :class="['json-value',getDataType(item.value)]">
124                                 {{
125                                     `${
126                                         getDataType(item.value) === 'string' ? '"' : ''
127                                     }${
128                                         formatValue(item.value)}${getDataType(item.value) === 'string' ? '"' : ''
129                                     }
130                                     ${index === items.length - 1 ? '' : ','}`
131                                 }}
132                             </span>
133                         </p>
134                     </template>
135                     <span v-if="!innerclosed" class="base-line"></span>
136                 </div>
137                 <p v-if="!innerclosed " class="last-line">
138                     <span>{{ subfix }}</span>
139                 </p>
140             </div>
141         </div>
142 
143     </div>
144 </template>
145 <script src="./json-view.js"></script>
146 <style lang="less" scoped>
147 @import "./style/index";
148 </style>
View Code

 

// json-view.js

  1 export default {
  2     name: 'json-view',
  3     props: {
  4         data: { // 传入的json数据
  5             type: [Object, Array],
  6             required: true
  7         },
  8         jsonKey: { // json的key值,用于第二层及二层以上的组件的key值
  9             type: String,
 10             default: ''
 11         },
 12         closed: { // 是否折叠
 13             type: Boolean,
 14             default: false
 15         },
 16         isLast: { // 是否是最后一行
 17             type: Boolean,
 18             default: true
 19         },
 20         fontSize: { // 字体大小
 21             type: Number,
 22             default: 14
 23         },
 24         lineHeight: { // 行高
 25             type: Number,
 26             default: 24
 27         },
 28         deep: { // 展开深度
 29             type: Number,
 30             default: 3
 31         },
 32         currentDeep: { // 当前为递归的第几层
 33             type: Number,
 34             default: 1
 35         },
 36         iconStyle: { // 折叠icon样式
 37             type: String,
 38             default: 'square'
 39         },
 40         iconColor: { // icon颜色
 41             type: Array,
 42             default() {
 43                 return [];
 44             }
 45         },
 46         theme: { // 主题
 47             type: String,
 48             default: ''
 49         },
 50         hasSiblings: { // 是否有兄弟节点
 51             type: Boolean,
 52             default: true
 53         }
 54     },
 55     data() {
 56         return {
 57             innerclosed: this.closed,
 58             templateDeep: this.currentDeep,
 59             visible: false
 60         };
 61     },
 62     computed: {
 63         isArray() {
 64             return this.getDataType(this.data) === 'array';
 65         },
 66         length() {
 67             return this.isArray ? this.data.length : Object.keys(this.data).length;
 68         },
 69         subfix() {
 70             const data = this.data;
 71             if (this.isEmptyArrayOrObject(data)) { // 如果是空数组或空对象
 72                 return '';
 73             } else {
 74                 return (this.isArray ? ']' : '}') + (this.isLast ? '' : ',');
 75             }
 76         },
 77         prefix() {
 78             return this.isArray ? '[' : '{';
 79         },
 80         items() {
 81             const json = this.data;
 82 
 83             if (this.isArray) {
 84                 return json.map(item => {
 85                     const isJSON = this.isObjectOrArray(item);
 86                     return {
 87                         value: item,
 88                         isJSON,
 89                         key: ''
 90                     };
 91                 });
 92             }
 93             return Object.keys(json).map(key => {
 94                 const item = json[key];
 95                 const isJSON = this.isObjectOrArray(item);
 96                 return {
 97                     value: item,
 98                     isJSON,
 99                     key
100                 };
101             });
102         },
103         iconColors() {
104             const {theme, iconColor} = this;
105             if (iconColor.length === 2) {
106                 return iconColor;
107             } else if (theme === 'one-dark') {
108                 return ['#747983', '#747983'];
109             } else if (theme === 'vs-code') {
110                 return ['#c6c6c6', '#c6c6c6'];
111             } else {
112                 return ['#747983', '#747983'];
113             }
114         }
115     },
116     mounted() {
117         setTimeout(() => {
118             this.visible = true;
119         }, 0);
120     },
121     methods: {
122         formatValue(data) {
123             if (data && data.isBigNumber) {
124                 return data.toString(10);
125             }
126             return data;
127         },
128         getDataType(data) {
129             return data && data.isBigNumber
130                 ? 'number'
131                 : Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
132         },
133         isObjectOrArray(source) {
134             return ['array', 'object'].includes(this.getDataType(source));
135         },
136         toggleClose() {
137             if (this.length === 0) {
138                 return;
139             }
140             if (this.innerclosed) {
141                 this.innerclosed = false;
142             } else {
143                 this.innerclosed = true;
144             }
145         },
146         isClose() {
147             return this.templateDeep + 1 > this.deep;
148         },
149         isEmptyArrayOrObject(data) { // 空数组或者空对象
150             return [
151                 {},
152                 []
153             ].map(item => JSON.stringify(item)).includes(JSON.stringify(data));
154         }
155     },
156     watch: {
157         closed() {
158             this.innerclosed = this.closed;
159         }
160     }
161 };
View Code

 

style下的文件:

// index.less

  1 // default
  2 @import "./on-dark";
  3 @import "./vs-code";
  4 
  5 .json-view-container {
  6     background-color: #fff;
  7 
  8     &.deep-1 {
  9         // overflow: auto;
 10         padding-right: 10px;
 11     }
 12 
 13     .json-view {
 14         position: relative;
 15         box-sizing: border-box;
 16         display: block;
 17         width: 100%;
 18         height: 100%;
 19         padding-left: 2rem;
 20         font-family: Consolas !important;
 21         white-space: nowrap;
 22         cursor: default;
 23 
 24         .json-note {
 25             font-size: 12px;
 26             font-style: italic;
 27             color: #909399;
 28         }
 29 
 30         .json-key {
 31             color: #8c6325;
 32         }
 33 
 34         .json-value {
 35             display: inline-block;
 36             color: #57b73b;
 37             word-break: break-all;
 38             white-space: normal;
 39 
 40             &.number {
 41                 color: #2d8cf0;
 42             }
 43 
 44             &.string {
 45                 color: #57b73b;
 46             }
 47 
 48             &.boolean {
 49                 color: #eb3324;
 50             }
 51 
 52             &.null {
 53                 color: #eb3324;
 54             }
 55         }
 56 
 57         .json-item {
 58             display: flex;
 59             padding-left: 2rem;
 60             margin: 0;
 61         }
 62 
 63         .first-line {
 64             padding: 0;
 65             margin: 0;
 66 
 67             &.pointer {
 68                 cursor: pointer !important;
 69             }
 70         }
 71 
 72         .json-body {
 73             position: relative;
 74             padding: 0;
 75             margin: 0;
 76 
 77             .base-line {
 78                 position: absolute;
 79                 top: 0;
 80                 left: 2px;
 81                 height: 100%;
 82                 border-left: 1px dashed #bbb;
 83             }
 84         }
 85 
 86         .last-line {
 87             padding: 0;
 88             margin: 0;
 89         }
 90 
 91         .angle {
 92             position: absolute;
 93 
 94             /* left: ~"calc(2rem - 18px)"; */
 95             left: 12px;
 96             display: block;
 97             // eslint-disable-next-line
 98             //float: left; // eslint-disable-line
 99             width: 20px;
100             text-align: center;
101             cursor: pointer;
102         }
103     }
104 }
View Code

 

// on-dark.less

 1 // dark
 2 .json-view-container {
 3     &.one-dark {
 4         background-color: #292c33;
 5 
 6         .json-view {
 7             font-family: Menlo, Consolas, "Courier New", Courier, FreeMono, monospace !important;
 8 
 9             .json-note {
10                 font-size: 12px;
11                 font-style: italic;
12                 color: #909399;
13             }
14 
15             .json-key {
16                 color: #d27277;
17             }
18 
19             .json-value {
20                 color: #c6937c;
21 
22                 &.number {
23                     color: #bacdab;
24                 }
25 
26                 &.string {
27                     color: #c6937c;
28                 }
29 
30                 &.boolean {
31                     color: #659bd1;
32                 }
33 
34                 &.null {
35                     color: #659bd1;
36                 }
37             }
38 
39             .first-line {
40                 color: #acb2be;
41             }
42 
43             .json-body {
44                 .base-line {
45                     border-left: 1px solid #3c4047;
46                 }
47             }
48 
49             .last-line {
50                 color: #acb2be;
51             }
52 
53             .json-item {
54                 color: #acb2be;
55             }
56         }
57     }
58 }
View Code

 

 

// vs.code.less

 1 // vs-code
 2 .json-view-container {
 3     &.vs-code {
 4         background-color: #1e1e1e;
 5 
 6         .json-view {
 7             font-family: Menlo, Consolas, "Courier New", Courier, FreeMono, monospace !important;
 8 
 9             .json-note {
10                 font-size: 12px;
11                 font-style: italic;
12                 color: #909399;
13             }
14 
15             .json-key {
16                 color: #a9dbfb;
17             }
18 
19             .json-value {
20                 color: #c6937c;
21             }
22 
23             .first-line {
24                 color: #d4d4d4;
25             }
26 
27             .json-body {
28                 .base-line {
29                     border-left: 1px solid #404040;
30                 }
31             }
32 
33             .last-line {
34                 color: #d4d4d4;
35             }
36 
37             .json-item {
38                 color: #d4d4d4;
39             }
40         }
41     }
42 }
View Code

 

使用示例:

 1 <json-view
 2                 :data="json"
 3                 :theme="theme"
 4                 :deep="deep"
 5                 :icon-style="iconStyle"
 6                 :font-size="fontSize"
 7                 :line-height="lineHeight"
 8                 :closed="closed"
 9                 :icon-color="iconColor"
10             />

 

效果展示:

 

 

参数:

 

posted @ 2021-12-17 17:29  十盏  阅读(567)  评论(0编辑  收藏  举报