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>
// 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 };
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 }
// 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 }
// 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 }
使用示例:
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 />
效果展示:
参数: