objectAnalysis.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. <template>
  2. <view
  3. class="objectAnalysis"
  4. :style="{
  5. width: width + 'rpx',
  6. }"
  7. >
  8. <view
  9. class="objectTitle"
  10. :class="canLongpress ? 'objectTitleActive' : ''"
  11. @click="titleClick"
  12. @longpress.stop="faLongpress"
  13. >
  14. <image class="foldItem" v-if="isOpen" src="@/devTools/page/static/fold.png" />
  15. <image class="foldItem" v-else src="@/devTools/page/static/unfold.png" />
  16. <text
  17. class="title"
  18. :style="{
  19. width: width - 50 + 'rpx',
  20. }"
  21. >
  22. {{ title }}
  23. </text>
  24. </view>
  25. <view
  26. class="objectList"
  27. v-if="isOpen"
  28. :style="{
  29. width: width + 'rpx',
  30. }"
  31. >
  32. <view
  33. v-for="(item, index) in list"
  34. :key="item.i"
  35. class="listItem"
  36. :style="{
  37. marginLeft: item.l + 'rpx',
  38. }"
  39. @click="rowClick(item, index)"
  40. @longpress.stop="rowLongpress($event, item, index)"
  41. >
  42. <template v-if="item.t == 'array' || item.t == 'object'">
  43. <image class="foldItem" v-if="item.o" src="@/devTools/page/static/fold.png" />
  44. <image class="foldItem" v-else src="@/devTools/page/static/unfold.png" />
  45. </template>
  46. <view
  47. v-else
  48. class="emptyFold"
  49. ></view>
  50. <text
  51. class="objKey"
  52. :class="'t-' + item.t"
  53. :style="{
  54. maxWidth: (width - item.l - 40) / 2 + 'rpx',
  55. }"
  56. >
  57. {{ item.k }}:
  58. </text>
  59. <text
  60. class="objValue"
  61. :class="'t-' + item.t"
  62. >
  63. {{ item.vt }}
  64. </text>
  65. </view>
  66. </view>
  67. </view>
  68. </template>
  69. <script>
  70. import jsonCompress from "../../../core/libs/jsonCompress";
  71. function getType(v) {
  72. return Object.prototype.toString.call(v).slice(8, -1).toLocaleLowerCase();
  73. }
  74. function randId() {
  75. return Math.ceil(Math.random() * 1000000000000000);
  76. }
  77. export default {
  78. props: {
  79. /**
  80. * 需要解析的对象数据
  81. */
  82. data: "",
  83. /**
  84. * 组件宽度 rpx
  85. */
  86. width: {
  87. type: Number,
  88. default: 610,
  89. },
  90. /**
  91. * 是否默认展开第一层
  92. */
  93. isOpenFirst: {
  94. type: Boolean,
  95. default: false,
  96. },
  97. /**
  98. * 是否自定义长按的菜单
  99. */
  100. isDiyMenu: {
  101. type: Boolean,
  102. default: false,
  103. },
  104. /**
  105. * 是否响应最外层的长按事件
  106. */
  107. canLongpress: {
  108. type: Boolean,
  109. default: true,
  110. },
  111. /**
  112. * 是否展示完整的对象类型
  113. */
  114. showObjProto: {
  115. type: Boolean,
  116. default: false,
  117. },
  118. },
  119. data() {
  120. return {
  121. /**
  122. * 对象类型 array object map unknown
  123. */
  124. type: "unknown",
  125. /**
  126. * 对象标题
  127. */
  128. title: "",
  129. /**
  130. * 是否为开启节点状态
  131. */
  132. isOpen: false,
  133. /**
  134. * 渲染的列表
  135. */
  136. list: [
  137. {
  138. t: "text", // 类型
  139. k: "键名", // key
  140. v: "名称", // value
  141. vt: "", //view层渲染的文字
  142. i: "s", //节点id
  143. p: "0", //父节点id
  144. o: true, //是否开启下级
  145. l: 0, // 距离左侧长度
  146. d: null, //原对象
  147. },
  148. ],
  149. };
  150. },
  151. mounted() {
  152. let that = this;
  153. try {
  154. let { title } = that.getObjType(this.data);
  155. that.list = [];
  156. that.title = title;
  157. if (that.isOpenFirst) {
  158. that.titleClick();
  159. }
  160. } catch (error) {
  161. console.log("objectAnalysis error", error);
  162. }
  163. },
  164. methods: {
  165. /**
  166. * 标题点击事件
  167. */
  168. titleClick() {
  169. if (this.isOpen) {
  170. this.isOpen = false;
  171. } else {
  172. if (this.list.length == 0) {
  173. this.analysisData(this.data);
  174. }
  175. this.isOpen = true;
  176. }
  177. },
  178. /**
  179. * 解析渲染数组
  180. */
  181. analysisData(data, pid = 0) {
  182. let list = [];
  183. let l = this.getParentNum(pid);
  184. let keys = [];
  185. keys = Reflect.ownKeys(data);
  186. for (let i = 0; i < keys.length; i++) {
  187. let key = keys[i];
  188. let value = data[key];
  189. if (key == "__proto__" || key == "__ob__") {
  190. continue;
  191. }
  192. let o = {
  193. t: typeof value,
  194. k: key,
  195. v: value,
  196. vt: "",
  197. i: randId(),
  198. p: pid,
  199. o: false,
  200. l: l * 5,
  201. d: value,
  202. };
  203. try {
  204. let t = typeof value;
  205. if (t == "function") {
  206. try {
  207. o.vt = value.toString();
  208. } catch (error) {
  209. if (error && error.message) {
  210. o.vt = "[js:function]" + error.message;
  211. } else {
  212. o.vt = "[js:function]";
  213. }
  214. }
  215. o.v = o.vt;
  216. o.d = "";
  217. } else if (t == "object") {
  218. if (this.showObjProto) {
  219. let str = "unknown";
  220. if (value === null) {
  221. o.t = "null";
  222. str = "null";
  223. } else if (Array.isArray(value)) {
  224. o.t = "array";
  225. let l = 0;
  226. try {
  227. l = value.length;
  228. } catch (error) {}
  229. str = Object.prototype.toString.call(value).slice(8, -1) + (l > 0 ? ` (${l})[...]` : " (0)[]");
  230. } else {
  231. o.t = "object";
  232. let childList = [];
  233. try {
  234. childList = Reflect.ownKeys(value);
  235. } catch (error) {}
  236. str = Object.prototype.toString.call(value).slice(8, -1) + (childList.length == 0 ? " {}" : " {...}");
  237. }
  238. o.vt = str;
  239. } else {
  240. let type = getType(value);
  241. let title = "";
  242. try {
  243. title = JSON.stringify(value);
  244. if (title.length > 50) {
  245. title = title.slice(0, 50) + "...";
  246. }
  247. if (type == "array" && value.length > 0) {
  248. title = "(" + value.length + ")" + title;
  249. }
  250. } catch (error) {
  251. title = "对象解析失败:" + error;
  252. }
  253. o.t = type;
  254. o.vt = title;
  255. o.v = value;
  256. }
  257. } else if (t == "symbol") {
  258. o.t = "symbol";
  259. try {
  260. if (value.toString) {
  261. o.vt = value.toString();
  262. } else {
  263. o.vt = "[js:symbol]";
  264. }
  265. } catch (error) {
  266. let msg = "";
  267. if (error && error.message) {
  268. msg = error.message;
  269. } else {
  270. msg = "[js:symbol解析失败]";
  271. }
  272. o.vt = msg;
  273. }
  274. } else if (t == "string") {
  275. if (value.length > 200) {
  276. o.vt = `"` + value.slice(0, 200) + "..." + '"';
  277. } else {
  278. o.vt = `"${value}"`;
  279. }
  280. } else if (t == "number") {
  281. if (Number.isFinite(value)) {
  282. o.vt = value.toString();
  283. } else {
  284. o.vt = isNaN(value) ? "NaN" : "Infinity";
  285. }
  286. } else if (t == "boolean") {
  287. o.vt = value ? "true" : "false";
  288. } else if (t == "undefined") {
  289. o.vt = "undefined";
  290. } else {
  291. o.vt = "[js:unknown type]";
  292. }
  293. } catch (error) {
  294. let msg = "";
  295. if (error && error.message) {
  296. msg = error.message;
  297. } else {
  298. msg = "[js对象解析失败]";
  299. }
  300. o.vt = msg;
  301. }
  302. list.push(o);
  303. }
  304. if (pid == 0) {
  305. this.list = list;
  306. } else {
  307. let faIndex = this.list.findIndex((x) => x.i == pid) + 1;
  308. for (let i = 0; i < list.length; i++) {
  309. this.list.splice(faIndex, 0, list[i]);
  310. faIndex++;
  311. }
  312. }
  313. },
  314. /**
  315. * 获取节点的父类数量
  316. */
  317. getParentNum(pid) {
  318. let that = this;
  319. let count = 0;
  320. if (pid == 0) {
  321. return count;
  322. } else {
  323. let p = Number(pid);
  324. while (true) {
  325. count = count + 1;
  326. let fa = that.list.find((x) => x.i == p);
  327. if (!fa || fa.i == 0) {
  328. break;
  329. } else {
  330. p = Number(fa.p);
  331. }
  332. }
  333. }
  334. return count;
  335. },
  336. /**
  337. * 行对象点击事件
  338. */
  339. rowClick(item, index) {
  340. let that = this;
  341. const nodeItem = that.list[index];
  342. if (item.t == "object" || item.t == "array") {
  343. if (item.o) {
  344. nodeItem.o = false;
  345. that.hideListByPid(item.i);
  346. } else {
  347. nodeItem.o = true;
  348. that.analysisData(nodeItem.d, item.i);
  349. }
  350. } else if (item.t == "string" && item.v.length > 100) {
  351. // 长文本点击时默认打开文本编辑器
  352. uni.$emit("devTools_showTextEditDialog", {
  353. title: item.k,
  354. canSave: false,
  355. isFileEdit: false,
  356. value: item.v,
  357. });
  358. }
  359. },
  360. /**
  361. * 根据父类id删除数组内元素
  362. */
  363. hideListByPid(pid = 0) {
  364. let that = this;
  365. while (true) {
  366. let i = that.list.findIndex((x) => x.p == pid);
  367. if (i == -1) {
  368. break;
  369. }
  370. if (that.list[i].o) {
  371. that.hideListByPid(that.list[i].i);
  372. }
  373. that.list.splice(i, 1);
  374. }
  375. },
  376. /**
  377. * 长按事件
  378. */
  379. rowLongpress(e, item, index) {
  380. // #ifdef APP-PLUS
  381. if (e && e.stopPropagation) {
  382. e.stopPropagation();
  383. }
  384. // #endif
  385. let that = this;
  386. if (that.isDiyMenu) {
  387. that.$emit("diyMenu", { item, index });
  388. } else {
  389. let k = this.toString(item.k);
  390. if (k.length > 20) {
  391. k = k.slice(0, 20) + "...";
  392. }
  393. let v = this.toString(item.v);
  394. if (v.length > 20) {
  395. v = v.slice(0, 20) + "...";
  396. }
  397. uni.showActionSheet({
  398. itemList: [`复制键(${k})`, `复制值(${v})`],
  399. success({ tapIndex }) {
  400. if (tapIndex == 0) {
  401. uni.setClipboardData({
  402. data: that.toString(item.k),
  403. });
  404. } else {
  405. uni.setClipboardData({
  406. data: that.toString(item.v),
  407. });
  408. }
  409. },
  410. });
  411. }
  412. },
  413. /**
  414. * 尝试转字符串
  415. */
  416. toString(data) {
  417. try {
  418. if (data === undefined) return "undefined";
  419. if (data === null) return "null";
  420. if (typeof data == "boolean") return data ? "true" : "false";
  421. if (typeof data == "object") {
  422. return JSON.stringify(data);
  423. }
  424. return data.toString();
  425. } catch (error) {
  426. return "尝试解析失败!" + error;
  427. }
  428. },
  429. /**
  430. * 获取列表
  431. */
  432. getList() {
  433. return this.list;
  434. },
  435. /**
  436. * 获取父级key类别
  437. */
  438. getFaKeyList(itemId) {
  439. let keyList = [];
  440. let item = this.list.find((x) => x.i == itemId);
  441. if (!item) return keyList;
  442. keyList = [item.k];
  443. if (item.p == 0) return keyList;
  444. while (true) {
  445. item = this.list.find((x) => x.i == item.p);
  446. if (!item) break;
  447. keyList.unshift(item.k);
  448. if (item.p == 0) {
  449. break;
  450. }
  451. }
  452. return keyList;
  453. },
  454. /**
  455. * 长按复制一整个对象
  456. */
  457. faLongpress(e) {
  458. // #ifdef APP-PLUS
  459. if (e && e.stopPropagation) {
  460. e.stopPropagation();
  461. }
  462. // #endif
  463. let that = this;
  464. if (that.canLongpress) {
  465. uni.setClipboardData({
  466. data: JSON.stringify(that.data),
  467. });
  468. } else {
  469. that.$emit("onLongpress");
  470. }
  471. },
  472. /**
  473. * 获取对象单行数据
  474. */
  475. getObjType(obj) {
  476. try {
  477. let title = "unknown";
  478. let type = typeof obj;
  479. let data = obj;
  480. switch (typeof data) {
  481. case "symbol":
  482. title = "[js:symbol]";
  483. try {
  484. if (data.toString) {
  485. title = data.toString();
  486. } else {
  487. title = "[js:symbol]";
  488. }
  489. } catch (error) {
  490. let msg = "";
  491. if (error && error.message) {
  492. msg = error.message;
  493. } else {
  494. msg = "[js:symbol解析失败]";
  495. }
  496. title = msg;
  497. }
  498. break;
  499. case "string":
  500. title = data;
  501. break;
  502. case "object":
  503. if (this.showObjProto) {
  504. try {
  505. let objType = Object.prototype.toString.call(data).slice(8, -1);
  506. title = {};
  507. let keys = Reflect.ownKeys(data);
  508. for (let i = 0; i < keys.length; i++) {
  509. let key = keys[i];
  510. if (key == "__proto__" || key == "__ob__") {
  511. continue;
  512. }
  513. try {
  514. let value = data[key];
  515. let t = typeof value;
  516. if (t == "function") {
  517. continue;
  518. }
  519. if (t == "object") {
  520. let str = "unknown";
  521. if (value === null) {
  522. str = "null";
  523. } else if (Array.isArray(value)) {
  524. str = Object.prototype.toString.call(value).slice(8, -1) + " [...]";
  525. } else {
  526. str = Object.prototype.toString.call(value).slice(8, -1) + " {...}";
  527. }
  528. title[key] = str;
  529. continue;
  530. }
  531. title[key] = data[key];
  532. } catch (error) {
  533. if (error && error.message) {
  534. title[key] = error.message;
  535. } else {
  536. title[key] = "[js对象解析失败]";
  537. }
  538. }
  539. }
  540. for (let i = 0; i < keys.length; i++) {
  541. let key = keys[i];
  542. try {
  543. let value = data[key];
  544. let t = typeof value;
  545. if (t == "function") {
  546. try {
  547. title[key] = value.toString();
  548. } catch (error) {
  549. if (error && error.message) {
  550. title[key] = "[js:function]" + error.message;
  551. } else {
  552. title[key] = "[js:function]";
  553. }
  554. }
  555. }
  556. } catch (error) {
  557. if (error && error.message) {
  558. title[key] = error.message;
  559. }
  560. }
  561. }
  562. if (title.toJSON) {
  563. title.toJSON = "[js:function]";
  564. }
  565. if (objType == "Array") {
  566. title = objType + " " + jsonCompress.safeJsonStringify(title);
  567. } else {
  568. title = objType + " " + jsonCompress.safeJsonStringify(title);
  569. }
  570. if (Array.isArray(data)) {
  571. type = "array";
  572. } else {
  573. type = "object";
  574. }
  575. } catch (error) {
  576. let msg = "unknown";
  577. if (error && error.message) {
  578. msg = error.message;
  579. }
  580. title = "对象解析出错:" + msg;
  581. }
  582. } else {
  583. try {
  584. title = JSON.stringify(data);
  585. if (title.length > 50) {
  586. title = title.slice(0, 50) + "...";
  587. }
  588. } catch (error) {
  589. title = "对象解析失败:" + error;
  590. }
  591. }
  592. break;
  593. case "function":
  594. try {
  595. title = data.toString();
  596. } catch (error) {
  597. title = "[js:function]";
  598. }
  599. break;
  600. default:
  601. title = data;
  602. break;
  603. }
  604. return { title, type };
  605. } catch (error) {
  606. console.log("getObjType error", error);
  607. return {
  608. title: "unknown",
  609. type: "unknown",
  610. };
  611. }
  612. },
  613. },
  614. };
  615. </script>
  616. <style lang="scss" scoped>
  617. .objectAnalysis {
  618. display: flex;
  619. flex-direction: column;
  620. .objectTitle {
  621. display: flex;
  622. flex-direction: row;
  623. align-items: center;
  624. .title {
  625. font-size: 20rpx;
  626. line-height: 20rpx;
  627. color: rgb(89, 74, 154);
  628. lines: 1;
  629. overflow: hidden;
  630. /* #ifdef H5 */
  631. // 限制行数
  632. display: -webkit-box;
  633. -webkit-box-orient: vertical;
  634. -webkit-line-clamp: 1;
  635. /* #endif */
  636. }
  637. }
  638. .objectTitleActive:active {
  639. background-color: rgba(0, 0, 0, 0.08);
  640. }
  641. .objectList {
  642. background-color: rgba(0, 0, 0, 0.02);
  643. border-radius: 8rpx;
  644. /* #ifndef APP-PLUS */
  645. min-height: 50rpx;
  646. /* #endif */
  647. padding: 10rpx;
  648. .listItem:active {
  649. background-color: rgba(0, 0, 0, 0.08);
  650. }
  651. .listItem {
  652. display: flex;
  653. flex-direction: row;
  654. align-items: center;
  655. .emptyFold {
  656. width: 20rpx;
  657. height: 20rpx;
  658. margin-right: 6rpx;
  659. }
  660. .objKey {
  661. font-size: 20rpx;
  662. line-height: 28rpx;
  663. color: rgb(121, 38, 117);
  664. lines: 1;
  665. overflow: hidden;
  666. /* #ifdef H5 */
  667. // 限制行数
  668. display: -webkit-box;
  669. -webkit-box-orient: vertical;
  670. -webkit-line-clamp: 1;
  671. /* #endif */
  672. }
  673. .objValue {
  674. line-height: 28rpx;
  675. margin-left: 5rpx;
  676. color: #333;
  677. font-size: 20rpx;
  678. lines: 1;
  679. overflow: hidden;
  680. /* #ifdef H5 */
  681. // 限制行数
  682. display: -webkit-box;
  683. -webkit-box-orient: vertical;
  684. -webkit-line-clamp: 1;
  685. /* #endif */
  686. &.t-number {
  687. color: rgb(8, 66, 160);
  688. }
  689. &.t-boolean {
  690. color: rgb(133, 2, 255);
  691. }
  692. &.t-string {
  693. color: rgb(227, 54, 46);
  694. }
  695. &.t-array {
  696. color: rgba(0, 0, 0, 0.5);
  697. }
  698. &.t-object {
  699. color: rgba(0, 0, 0, 0.5);
  700. }
  701. &.t-undefined {
  702. color: rgba(0, 0, 0, 0.2);
  703. }
  704. &.t-null {
  705. color: rgba(0, 0, 0, 0.2);
  706. }
  707. }
  708. }
  709. }
  710. }
  711. .foldItem {
  712. width: 20rpx;
  713. height: 20rpx;
  714. background-color: #eee;
  715. border-radius: 4rpx;
  716. margin-right: 6rpx;
  717. }
  718. </style>