|
1 | 1 | <template> |
2 | 2 | <div |
3 | | - class="vjs__tree" |
4 | | - :style="{ |
5 | | - 'background-color': treeContentBackground, |
6 | | - 'position': currentDeep > 1 ? '' : 'relative', |
7 | | - 'margin-left': currentDeep === 1 && existCheckbox ? '30px' : '' |
| 3 | + :class="{ |
| 4 | + 'vjs-tree': true, |
| 5 | + 'has-selectable-control': existCheckbox || existRadio, |
| 6 | + 'is-root': currentDeep === 1, |
| 7 | + 'is-selectable': selectable, |
| 8 | + 'is-mouseover': isMouseover |
8 | 9 | }" |
9 | 10 | @click.stop="handleClick($event)" |
10 | 11 | @mouseover.stop="handleMouseover" |
11 | 12 | @mouseout.stop="handleMouseout"> |
12 | | - <template v-if="selectable && existCheckbox" class="vjs-checkbox"> |
13 | | - <checkbox v-model="checkboxVal" @change="handleClick($event, true)"></checkbox> |
| 13 | + <template v-if="selectable"> |
| 14 | + <vue-checkbox v-if="existCheckbox" v-model="currentCheckboxVal" @change="handleClick($event, 'checkbox')"></vue-checkbox> |
| 15 | + <vue-radio v-else-if="existRadio" v-model="model" @change="handleClick($event, 'radio')" :path="path"></vue-radio> |
14 | 16 | </template> |
15 | 17 |
|
16 | 18 | <template v-if="Array.isArray(data) || isObject(data)"> |
|
19 | 21 | :visible.sync="visible" |
20 | 22 | :data="data" |
21 | 23 | :show-length="showLength" |
22 | | - :not-last-key="notLastKey"> |
| 24 | + :show-comma="notLastKey"> |
23 | 25 | <span v-if="currentDeep > 1 && !Array.isArray(parentData)">{{ keyFormatter(currentKey) }}:</span> |
24 | 26 | </brackets-left> |
25 | 27 |
|
26 | 28 | <!-- 数据内容, data 为对象时, key 表示键名, 为数组时表示索引 --> |
27 | 29 | <div |
28 | 30 | v-for="(item, key) in data" |
29 | 31 | v-show="visible" |
30 | | - class="vjs__tree__content" |
| 32 | + class="vjs-tree__content" |
31 | 33 | :key="key"> |
32 | 34 | <vue-json-pretty |
33 | | - v-model="value" |
| 35 | + v-model="model" |
34 | 36 | :parent-data="data" |
35 | 37 | :data="item" |
36 | 38 | :deep="deep" |
|
51 | 53 | <brackets-right |
52 | 54 | :visible.sync="visible" |
53 | 55 | :data="data" |
54 | | - :not-last-key="notLastKey"> |
| 56 | + :show-comma="notLastKey"> |
55 | 57 | </brackets-right> |
56 | 58 | </template> |
57 | 59 |
|
58 | 60 | <simple-text |
59 | 61 | v-else |
60 | 62 | :show-double-quotes="showDoubleQuotes" |
61 | | - :parent-data-type="getDataType(parentData)" |
62 | | - :data-type="getDataType(data)" |
63 | | - :text="data + ''" |
64 | | - :not-last-key="notLastKey" |
65 | | - :currentKey="currentKey"> |
| 63 | + :show-comma="notLastKey" |
| 64 | + :parent-data="parentData" |
| 65 | + :data="data" |
| 66 | + :current-key="currentKey"> |
| 67 | + <span v-if="!Array.isArray(parentData)">{{ keyFormatter(currentKey) }}:</span> |
66 | 68 | </simple-text> |
67 | 69 | </div> |
68 | 70 | </template> |
69 | 71 |
|
70 | 72 | <script> |
71 | 73 | import SimpleText from './simple-text' |
72 | | - import Checkbox from './checkbox' |
| 74 | + import VueCheckbox from './checkbox' |
| 75 | + import VueRadio from './radio' |
73 | 76 | import BracketsLeft from './brackets-left' |
74 | 77 | import BracketsRight from './brackets-right' |
| 78 | + import { getDataType } from 'src/utils' |
75 | 79 |
|
76 | 80 | export default { |
77 | 81 | name: 'vue-json-pretty', |
78 | 82 | components: { |
79 | 83 | SimpleText, |
80 | | - Checkbox, |
| 84 | + VueCheckbox, |
| 85 | + VueRadio, |
81 | 86 | BracketsLeft, |
82 | 87 | BracketsRight |
83 | 88 | }, |
84 | 89 | props: { |
85 | | - /* 外部可用 START */ |
| 90 | + /* outer props */ |
86 | 91 | // 当前树的数据 |
87 | 92 | data: {}, |
88 | 93 | // 定义树的深度, 大于该深度的子树将不被展开 |
|
115 | 120 | type: String, |
116 | 121 | default: '' // radio, checkbox |
117 | 122 | }, |
118 | | - // 存在选择功能时,定义已选中的数据层级 |
| 123 | + // 存在选择功能时, 定义已选中的数据层级 |
| 124 | + // 多选时为数组['root.a', 'root.b'], 单选时为字符串'root.a' |
119 | 125 | value: { |
120 | | - type: Array, |
121 | | - default: () => [] |
| 126 | + type: [Array, String], |
| 127 | + default: () => '' |
122 | 128 | }, |
123 | 129 | // 定义某个数据层级是否支持选中操作 |
124 | 130 | pathSelectable: { |
125 | 131 | type: Function, |
126 | 132 | default: () => true |
127 | 133 | }, |
128 | | - /* 外部可用 END */ |
| 134 | + /* outer props */ |
129 | 135 |
|
130 | | - /* 内部使用 */ |
| 136 | + /* inner props */ |
131 | 137 | // 当前树的父级数据 |
132 | 138 | parentData: {}, |
133 | 139 | // 当前树的深度, 以根节点作为0开始, 所以第一层树的深度为1, 递归逐次递增 |
|
137 | 143 | }, |
138 | 144 | // 当前树的数据 data 为数组时 currentKey 表示索引, 为对象时表示键名 |
139 | 145 | currentKey: [Number, String] |
| 146 | + /* outer props */ |
140 | 147 | }, |
141 | 148 | data () { |
142 | 149 | return { |
143 | 150 | visible: this.currentDeep <= this.deep, |
144 | | - treeContentBackground: 'transparent', |
145 | | - checkboxVal: this.value.includes(this.path) // 复选框的值 |
| 151 | + isMouseover: false, |
| 152 | + currentCheckboxVal: Array.isArray(this.value) ? this.value.includes(this.path) : false |
146 | 153 | } |
147 | 154 | }, |
148 | 155 | computed: { |
149 | | - model () { |
150 | | - return this.value || [] |
| 156 | + model: { |
| 157 | + get () { |
| 158 | + const defaultVal = this.selectableType === 'checkbox' ? [] : this.selectableType === 'radio' ? '' : null |
| 159 | + return this.value || defaultVal |
| 160 | + }, |
| 161 | + set (val) { |
| 162 | + this.$emit('input', val) |
| 163 | + } |
151 | 164 | }, |
| 165 | +
|
152 | 166 | // 获取当前 data 中最后一项的 key 或 索引, 便于界面判断是否添加 "," |
153 | 167 | lastKey () { |
154 | 168 | if (Array.isArray(this.parentData)) { |
|
158 | 172 | return arr[arr.length - 1] |
159 | 173 | } |
160 | 174 | }, |
| 175 | +
|
161 | 176 | // 是否不是最后一项 |
162 | 177 | notLastKey () { |
163 | 178 | return this.currentKey !== this.lastKey |
164 | 179 | }, |
| 180 | +
|
165 | 181 | // 当前的树是否支持选中功能 |
166 | 182 | selectable () { |
167 | 183 | return this.pathSelectable(this.path, this.data) |
168 | 184 | }, |
| 185 | +
|
169 | 186 | // 存在复选框 |
170 | 187 | existCheckbox () { |
171 | 188 | return this.selectableType === 'checkbox' |
| 189 | + }, |
| 190 | +
|
| 191 | + existRadio () { |
| 192 | + return this.selectableType === 'radio' |
172 | 193 | } |
173 | 194 | }, |
174 | 195 | methods: { |
175 | 196 | /** |
176 | | - * 触发组件的 click 事件 |
177 | | - * @param {Boolean} fromSelect 是否来自复选框的事件 |
| 197 | + * emit click event |
| 198 | + * @param {Boolean} emitType |
178 | 199 | */ |
179 | | - handleClick (e, fromSelect = false) { |
180 | | - // 由于 checkbox 也依赖该函数, 因此通过 fromSelect 进行排除 |
| 200 | + handleClick (e, emitType = '') { |
181 | 201 | this.$emit('click', this.path, this.data) |
182 | | - if (fromSelect) { |
| 202 | + if (emitType === 'checkbox') { |
183 | 203 | const index = this.model.findIndex(item => item === this.path) |
184 | 204 | if (index !== -1) { |
185 | 205 | this.model.splice(index, 1) |
186 | 206 | } else { |
187 | 207 | this.model.push(this.path) |
188 | 208 | } |
189 | | - this.$emit('input', this.model) |
190 | | - this.$emit('change', this.checkboxVal) |
| 209 | + this.$emit('change', this.currentCheckboxVal) |
| 210 | + } else if (emitType === 'radio') { |
| 211 | + if (this.model !== this.path) { |
| 212 | + this.model = this.path |
| 213 | + this.$emit('change', this.model) |
| 214 | + } |
191 | 215 | } |
192 | 216 | }, |
193 | | - // 处理子树触发的 click 事件, 并传递到顶层 |
194 | | - handleItemClick (path, data, checked) { |
195 | | - this.$emit('click', path, data, checked) |
| 217 | +
|
| 218 | + // handle children's click, and propagation |
| 219 | + handleItemClick (path, data) { |
| 220 | + this.$emit('click', path, data) |
196 | 221 | }, |
| 222 | +
|
| 223 | + // handle children's change, and propagation |
197 | 224 | handleItemChange (val) { |
198 | 225 | // 不存在选择的时候change事件无意义 |
199 | 226 | if (this.existCheckbox) { |
200 | 227 | this.$emit('change', val) |
201 | 228 | } |
202 | 229 | }, |
| 230 | +
|
203 | 231 | handleMouseover () { |
204 | | - this.showMouseOver && this.selectable && (this.treeContentBackground = '#eee') |
| 232 | + this.showMouseOver && this.selectable && (this.isMouseover = true) |
205 | 233 | }, |
| 234 | +
|
206 | 235 | handleMouseout () { |
207 | | - this.showMouseOver && this.selectable && (this.treeContentBackground = 'transparent') |
| 236 | + this.showMouseOver && this.selectable && (this.isMouseover = false) |
208 | 237 | }, |
| 238 | +
|
209 | 239 | // 是否对象 |
210 | 240 | isObject (value) { |
211 | | - return this.getDataType(value) === 'object' |
212 | | - }, |
213 | | - // 获取数据类型 |
214 | | - getDataType (value) { |
215 | | - // 若使用 typeof 会影响 webpack 压缩后体积变大 |
216 | | - return Object.prototype.toString.call(value).slice(8, -1).toLowerCase() |
| 241 | + return getDataType(value) === 'object' |
217 | 242 | }, |
| 243 | +
|
218 | 244 | keyFormatter (key) { |
219 | 245 | return this.showDoubleQuotes ? `"${key}"` : key |
220 | 246 | } |
|
0 commit comments