Sfoglia il codice sorgente

增加JSONPath查询的功能

zxlie 3 mesi fa
parent
commit
3612788c42

+ 1 - 1
apps/json-format/content-script.css

@@ -376,7 +376,7 @@ html.fh-jf  .x-toolbar .x-sort {
 }
 
 html.fh-jf  .x-toolbar .x-split {
-    margin: 0 15px;
+    margin: 0 5px;
     color: #ccc;
     display: inline;
 }

+ 573 - 192
apps/json-format/index.css

@@ -1,192 +1,573 @@
-@import url("../static/vendor/codemirror/codemirror.css");
-@import url("../static/css/bootstrap.min.css");
-@import url("content-script.css");
-
-body {
-    background-color: #fff;
-}
-html,body {
-    padding: 0;
-    margin: 0;
-    height:100%;
-}
-
-.wp-json {
-    width:auto;
-}
-
-.wp-json .panel-body {
-    padding: 0;
-}
-#jfContent_pre {
-    display: none;
-    padding: 10px;
-}
-
-#errorMsg {
-    color: #f00;
-    margin-left: 10px;
-    float: right;
-}
-.x-placeholder img{
-    width: 400px;
-    opacity: 0.3;
-    margin: 5px 0 0 -8px;
-}
-.x-xdemo,a.x-xdemo {
-    margin-left: 30px;
-    font-size: 12px;
-    color: blue;
-    cursor: pointer;
-    text-decoration: underline;
-}
-.x-xdemo:hover {
-    text-decoration: underline;
-    color: #f00;
-}
-.x-donate-link {
-    line-height: 18px;
-}
-
-#errorTips {
-    border-radius: 4px;
-    box-shadow: 6px 5px 7px rgba(229, 163, 163, 0.4);
-    background-color: #ffecf1;
-    border: 1px solid #dbb2b2;
-    margin-top:10px;
-}
-#errorCode .errorEm {
-    background-color:#ff921b;
-}
-#errorTarget {
-    color:#000;
-    background-color:#fff;
-}
-#errorCode {
-    padding:0 10px 10px;
-    word-break: break-all;
-}
-#errorCode ol {
-    padding:0 0 0 3em;
-}
-#errorCode .x-ph {
-    color: #f00;
-    font-weight: bold;
-}
-#tipsBox {
-    color:#b4465c;
-    padding: 6px 0 4px 2em;
-    background-color: #ffecf1;
-}
-#errorCode ol li {
-    padding:4px 7px 0;
-    list-style-type: decimal;
-    color:#b4465c;
-    background-color:#fdf7f7;
-}
-#errorCode ol li div {
-    color:#000;
-}
-
-.x-error {
-    color:red;
-}
-
-/*layout-up-down:上下布局模式*/
-.wp-json.layout-up-down .mod-json .panel-txt {
-    position: static;
-    width: 100%;
-    height: 250px;
-    margin: 0;
-}
-.wp-json.layout-up-down  .CodeMirror {
-    height: 250px;
-}
-.wp-json.layout-up-down .mod-json .rst-item {
-    margin: 0;
-}
-.wp-json.layout-up-down .mod-json .x-placeholder {
-    padding-top: 0;
-    text-align: left;
-}
-
-/* layout-left-right: 左右布局 */
-.wp-json.layout-left-right .mod-json .panel-txt {
-    width: 500px;
-    height: calc(100% - 10px);
-    float: left;
-}
-.wp-json.layout-left-right .mod-json .rst-item {
-    margin: 0 20px 0 30px;
-    padding-bottom:20px;
-    float: left;
-    width: calc(100% - 520px);
-}
-.wp-json.layout-left-right .mod-json .x-placeholder {
-    padding-top: 50px;
-    text-align: center;
-}
-.wp-json.layout-left-right .panel-body {
-    height:calc(100% - 95px);
-    padding-left: 15px;
-}
-.wp-json.layout-left-right  #jsonSource,
-.wp-json.layout-left-right  .CodeMirror {
-    height: calc(100% - 10px);
-}
-.wp-json.layout-left-right #formattedJson {
-    padding-top: 2px;
-}
-.wp-json.layout-left-right #errorTips {
-    margin-top: 0;
-}
-
-html.fh-jf .x-toolbar {
-    padding-top:5px;
-    padding-bottom: 5px;
-}
-html.fh-jf .x-toolbar.x-inpage {
-    margin:10px 20px;
-}
-.panel-title>a.x-other-tools {
-    top: 0px;
-}
-
-
-.panel-heading {
-    padding:5px 15px;
-}
-
-#layoutBar {margin-left:30px}
-#btnLeftRight,#btnUpDown{
-    -webkit-border-radius:2px;-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.1);
-    -webkit-user-select:none;background:-webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);outline: none;
-    border:1px solid #aaa;color:#444;font-size:12px;margin-bottom:0px;
-    position:relative;z-index:10;display:inline-block;
-    padding: 0 10px;
-    height: 30px;
-    text-shadow:1px 1px rgba(255,255,255,0.3)}
-#btnUpDown{margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;}
-#btnLeftRight{margin-right:0;border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}
-#btnLeftRight:hover,#btnUpDown:hover{-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.2);
-    background:#ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);border-color:#999;color:#222}
-#btnLeftRight.selected, #btnUpDown.selected{-webkit-box-shadow:inset 0px 1px 5px rgba(0,0,0,0.2);
-    background:#ebebeb -webkit-linear-gradient(#e4e4e4, #dfdfdf 40%, #dcdcdc);color:#333}
-
-#fh-img-preview {
-  display: none;
-  position: absolute !important;
-  z-index: 2147483647 !important;
-  pointer-events: none;
-  border-radius: 4px;
-  background: #fff;
-  box-shadow: 0 2px 8px #0002;
-  padding: 4px;
-  max-width: 320px;
-  max-height: 220px;
-}
-#fh-img-preview img {
-  max-width: 300px;
-  max-height: 200px;
-  display: block;
-}
+@import url("../static/vendor/codemirror/codemirror.css");
+@import url("../static/css/bootstrap.min.css");
+@import url("content-script.css");
+
+body {
+    background-color: #fff;
+}
+html,body {
+    padding: 0;
+    margin: 0;
+    height:100%;
+}
+
+.wp-json {
+    width:auto;
+}
+
+.wp-json .panel-body {
+    padding: 0;
+}
+#jfContent_pre {
+    display: none;
+    padding: 10px;
+}
+
+#errorMsg {
+    color: #f00;
+    margin-left: 10px;
+    float: right;
+}
+.x-placeholder img{
+    width: 400px;
+    opacity: 0.3;
+    margin: 5px 0 0 -8px;
+}
+.x-xdemo,a.x-xdemo {
+    margin-left: 30px;
+    font-size: 12px;
+    color: blue;
+    cursor: pointer;
+    text-decoration: underline;
+}
+.x-xdemo:hover {
+    text-decoration: underline;
+    color: #f00;
+}
+.x-donate-link {
+    line-height: 18px;
+}
+
+#errorTips {
+    border-radius: 4px;
+    box-shadow: 6px 5px 7px rgba(229, 163, 163, 0.4);
+    background-color: #ffecf1;
+    border: 1px solid #dbb2b2;
+    margin-top:10px;
+}
+#errorCode .errorEm {
+    background-color:#ff921b;
+}
+#errorTarget {
+    color:#000;
+    background-color:#fff;
+}
+#errorCode {
+    padding:0 10px 10px;
+    word-break: break-all;
+}
+#errorCode ol {
+    padding:0 0 0 3em;
+}
+#errorCode .x-ph {
+    color: #f00;
+    font-weight: bold;
+}
+#tipsBox {
+    color:#b4465c;
+    padding: 6px 0 4px 2em;
+    background-color: #ffecf1;
+}
+#errorCode ol li {
+    padding:4px 7px 0;
+    list-style-type: decimal;
+    color:#b4465c;
+    background-color:#fdf7f7;
+}
+#errorCode ol li div {
+    color:#000;
+}
+
+.x-error {
+    color:red;
+}
+
+/*layout-up-down:上下布局模式*/
+.wp-json.layout-up-down .mod-json .panel-txt {
+    position: static;
+    width: 100%;
+    height: 250px;
+    margin: 0;
+}
+.wp-json.layout-up-down  .CodeMirror {
+    height: 250px;
+}
+.wp-json.layout-up-down .mod-json .rst-item {
+    margin: 0;
+}
+.wp-json.layout-up-down .mod-json .x-placeholder {
+    padding-top: 0;
+    text-align: left;
+}
+
+/* layout-left-right: 左右布局 */
+.wp-json.layout-left-right .mod-json .panel-txt {
+    width: 500px;
+    height: calc(100% - 10px);
+    float: left;
+}
+.wp-json.layout-left-right .mod-json .rst-item {
+    margin: 0 20px 0 30px;
+    padding-bottom:20px;
+    float: left;
+    width: calc(100% - 520px);
+}
+.wp-json.layout-left-right .mod-json .x-placeholder {
+    padding-top: 50px;
+    text-align: center;
+}
+.wp-json.layout-left-right .panel-body {
+    height:calc(100% - 95px);
+    padding-left: 15px;
+}
+.wp-json.layout-left-right  #jsonSource,
+.wp-json.layout-left-right  .CodeMirror {
+    height: calc(100% - 10px);
+}
+.wp-json.layout-left-right #formattedJson {
+    padding-top: 2px;
+}
+.wp-json.layout-left-right #errorTips {
+    margin-top: 0;
+}
+
+html.fh-jf .x-toolbar {
+    padding-top:5px;
+    padding-bottom: 5px;
+}
+html.fh-jf .x-toolbar.x-inpage {
+    margin:10px 20px;
+}
+.panel-title>a.x-other-tools {
+    top: 0px;
+}
+
+
+.panel-heading {
+    padding:5px 15px;
+}
+
+#layoutBar {margin-left:30px}
+#btnLeftRight,#btnUpDown{
+    -webkit-border-radius:2px;-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.1);
+    -webkit-user-select:none;background:-webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);outline: none;
+    border:1px solid #aaa;color:#444;font-size:12px;margin-bottom:0px;
+    position:relative;z-index:10;display:inline-block;
+    padding: 0 10px;
+    height: 30px;
+    text-shadow:1px 1px rgba(255,255,255,0.3)}
+#btnUpDown{margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;}
+#btnLeftRight{margin-right:0;border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}
+#btnLeftRight:hover,#btnUpDown:hover{-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.2);
+    background:#ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);border-color:#999;color:#222}
+#btnLeftRight.selected, #btnUpDown.selected{-webkit-box-shadow:inset 0px 1px 5px rgba(0,0,0,0.2);
+    background:#ebebeb -webkit-linear-gradient(#e4e4e4, #dfdfdf 40%, #dcdcdc);color:#333}
+
+#fh-img-preview {
+  display: none;
+  position: absolute !important;
+  z-index: 2147483647 !important;
+  pointer-events: none;
+  border-radius: 4px;
+  background: #fff;
+  box-shadow: 0 2px 8px #0002;
+  padding: 4px;
+  max-width: 320px;
+  max-height: 220px;
+}
+#fh-img-preview img {
+  max-width: 300px;
+  max-height: 200px;
+  display: block;
+}
+
+/* JSONPath查询样式 */
+.x-jsonpath {
+    display: inline-flex;
+    align-items: center;
+    gap: 5px;
+}
+
+/* JSONPath查询结果区域样式 */
+.jsonpath-results-section {
+    margin-top: 15px;
+}
+
+.xjf-input {
+    padding: 3px 8px;
+    border: 1px solid #ddd;
+    border-radius: 3px;
+    font-size: 12px;
+    width: 250px;
+    background: white;
+    color: #333;
+}
+
+.xjf-input:focus {
+    outline: none;
+    border-color: #007bff;
+    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+}
+
+.xjf-btn-examples {
+    background-color: #6c757d !important;
+    border-color: #6c757d !important;
+}
+
+.xjf-btn-examples:hover {
+    background-color: #5a6268 !important;
+    border-color: #545b62 !important;
+}
+
+/* JSONPath模态框样式 */
+.jsonpath-modal {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 10000;
+}
+
+.jsonpath-modal-content {
+    background: white;
+    border-radius: 8px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+    max-width: 85%;
+    max-height: 80%;
+    min-width: 800px;
+    display: flex;
+    flex-direction: column;
+}
+
+.jsonpath-modal-header {
+    padding: 15px 20px;
+    border-bottom: 1px solid #dee2e6;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    background-color: #f8f9fa;
+    border-radius: 8px 8px 0 0;
+    gap: 15px;
+}
+
+.jsonpath-modal-header h3 {
+    margin: 0;
+    font-size: 18px;
+    color: #333;
+    flex-shrink: 0;
+}
+
+.jsonpath-header-controls {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    flex: 1;
+    max-width: 600px;
+}
+
+.jsonpath-header-input {
+    flex: 1;
+    min-width: 300px;
+    padding: 6px 10px !important;
+    font-size: 13px !important;
+    border: 1px solid #ced4da !important;
+    border-radius: 4px !important;
+    background: white !important;
+    color: #333 !important;
+}
+
+.jsonpath-header-input:focus {
+    outline: none !important;
+    border-color: #007bff !important;
+    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
+}
+
+.jsonpath-header-btn {
+    flex-shrink: 0;
+    padding: 6px 12px !important;
+    font-size: 13px !important;
+    white-space: nowrap;
+}
+
+.jsonpath-modal-close {
+    font-size: 24px;
+    cursor: pointer;
+    color: #6c757d;
+    padding: 0 5px;
+    user-select: none;
+}
+
+.jsonpath-modal-close:hover {
+    color: #dc3545;
+}
+
+.jsonpath-modal-body {
+    padding: 20px;
+    overflow-y: auto;
+    flex: 1;
+}
+
+.jsonpath-query-info {
+    margin-bottom: 15px;
+    padding: 10px;
+    background-color: #f8f9fa;
+    border-radius: 5px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.jsonpath-query-info code {
+    background: #e9ecef;
+    padding: 2px 6px;
+    border-radius: 3px;
+    font-family: 'Courier New', monospace;
+    color: #495057;
+}
+
+.jsonpath-result-count {
+    color: #6c757d;
+    font-size: 14px;
+}
+
+.jsonpath-result-actions {
+    margin-bottom: 15px;
+    display: flex;
+    gap: 10px;
+}
+
+/* 复制按钮状态样式 */
+.xjf-btn-copying {
+    background: #ffc107 !important;
+    border-color: #ffc107 !important;
+    color: #212529 !important;
+}
+
+.xjf-btn-success {
+    background: #28a745 !important;
+    border-color: #28a745 !important;
+    color: #ffffff !important;
+}
+
+.xjf-btn-error {
+    background: #dc3545 !important;
+    border-color: #dc3545 !important;
+    color: #ffffff !important;
+}
+
+.xjf-btn:disabled {
+    opacity: 0.7;
+    cursor: not-allowed;
+}
+
+.jsonpath-results {
+    border: 1px solid #dee2e6;
+    border-radius: 5px;
+    max-height: 400px;
+    overflow-y: auto;
+}
+
+.jsonpath-result-item {
+    border-bottom: 1px solid #dee2e6;
+    padding: 10px;
+}
+
+.jsonpath-result-item:last-child {
+    border-bottom: none;
+}
+
+.jsonpath-result-path {
+    margin-bottom: 5px;
+    font-weight: bold;
+    color: #495057;
+}
+
+.jsonpath-result-path code {
+    background: #e9ecef;
+    padding: 2px 6px;
+    border-radius: 3px;
+    font-family: 'Courier New', monospace;
+    color: #6f42c1;
+}
+
+.jsonpath-result-value {
+    background: #f8f9fa;
+    border: 1px solid #e9ecef;
+    border-radius: 3px;
+    padding: 10px;
+    margin: 0;
+    font-family: 'Courier New', monospace;
+    font-size: 12px;
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    max-height: 200px;
+    overflow-y: auto;
+}
+
+.jsonpath-no-results, .jsonpath-error {
+    text-align: center;
+    padding: 30px;
+    color: #6c757d;
+}
+
+.jsonpath-error .error-msg {
+    color: #dc3545;
+    font-weight: bold;
+}
+
+/* JSONPath示例样式 */
+.jsonpath-examples {
+    display: grid;
+    gap: 10px;
+}
+
+.jsonpath-example-item {
+    padding: 12px;
+    border: 1px solid #dee2e6;
+    border-radius: 5px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+}
+
+.jsonpath-example-item:hover {
+    background-color: #f8f9fa;
+    border-color: #007bff;
+    transform: translateY(-1px);
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.jsonpath-example-path {
+    font-family: 'Courier New', monospace;
+    font-weight: bold;
+    color: #6f42c1;
+    margin-bottom: 5px;
+}
+
+.jsonpath-example-desc {
+    color: #6c757d;
+    font-size: 14px;
+}
+
+/* 深色主题适配 */
+body.theme-dark .xjf-input {
+    background: #3c3c3c;
+    border-color: #555;
+    color: #d4d4d4;
+}
+
+body.theme-dark .xjf-input:focus {
+    border-color: #007bff;
+    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+}
+
+body.theme-dark .jsonpath-modal-content {
+    background: #2d2d30;
+    color: #d4d4d4;
+}
+
+body.theme-dark .jsonpath-modal-header {
+    background-color: #383838;
+    border-color: #444;
+}
+
+body.theme-dark .jsonpath-modal-header h3 {
+    color: #d4d4d4;
+}
+
+body.theme-dark .jsonpath-header-input {
+    background: #3c3c3c !important;
+    border-color: #555 !important;
+    color: #d4d4d4 !important;
+}
+
+body.theme-dark .jsonpath-header-input:focus {
+    border-color: #007bff !important;
+    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
+}
+
+body.theme-dark .jsonpath-query-info {
+    background-color: #383838;
+    border-color: #444;
+}
+
+body.theme-dark .jsonpath-query-info code {
+    background: #444;
+    color: #d4d4d4;
+}
+
+body.theme-dark .jsonpath-results {
+    border-color: #444;
+}
+
+body.theme-dark .jsonpath-result-item {
+    border-color: #444;
+}
+
+body.theme-dark .jsonpath-result-path {
+    color: #d4d4d4;
+}
+
+body.theme-dark .jsonpath-result-path code {
+    background: #444;
+    color: #dcc5ff;
+}
+
+body.theme-dark .jsonpath-result-value {
+    background: #383838;
+    border-color: #444;
+    color: #d4d4d4;
+}
+
+body.theme-dark .jsonpath-example-item {
+    border-color: #444;
+    background: #383838;
+}
+
+body.theme-dark .jsonpath-example-item:hover {
+    background-color: #444;
+    border-color: #007bff;
+}
+
+body.theme-dark .jsonpath-example-path {
+    color: #dcc5ff;
+}
+
+body.theme-dark .jsonpath-example-desc {
+    color: #888;
+}
+
+
+
+/* 深色主题下的复制按钮状态样式 */
+body.theme-dark .xjf-btn-copying {
+    background: #ffc107 !important;
+    border-color: #ffc107 !important;
+    color: #212529 !important;
+}
+
+body.theme-dark .xjf-btn-success {
+    background: #198754 !important;
+    border-color: #198754 !important;
+    color: #ffffff !important;
+}
+
+body.theme-dark .xjf-btn-error {
+    background: #dc3545 !important;
+    border-color: #dc3545 !important;
+    color: #ffffff !important;
+}

+ 163 - 87
apps/json-format/index.html

@@ -1,87 +1,163 @@
-<!DOCTYPE HTML>
-<html lang="zh-CN" class="fh-jf">
-    <head>
-        <title>JSON格式化查看工具</title>
-        <meta charset="UTF-8">
-        <link rel="stylesheet" href="index.css" />
-		<script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
-        <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
-    </head>
-    <body class="theme-default">
-        <div class="wrapper wp-json" id="pageContainer">
-            <div class="panel panel-default" style="margin-bottom: 0px;">
-                <div class="panel-heading">
-                    <h3 class="panel-title">
-                        <a href="https://www.baidufe.com/fehelper/index/index.html" target="_blank" class="x-a-high">
-                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:JSON格式化
-                        <span class="x-xdemo" ref="demoLink1" @click="setDemo">示例1:JSON片段</span>
-                        <a class="x-xdemo" href="http://t.weather.sojson.com/api/weather/city/101030100" target="_blank">示例2:在线JSON</a>
-
-                        <span id="layoutBar">
-                            <button id="btnLeftRight" ref="btnLeftRight" class="selected" @click="changeLayout('left-right')">左右布局</button><button id="btnUpDown" ref="btnUpDown" @click="changeLayout('up-down')">上下布局</button>
-                        </span>
-                        <a class="x-other-tools" v-if="!isInUSAFlag" @click="openOptionsPage($event)"><i class="icon-plus-circle"></i> 探索更多实用工具 <span class="tool-market-badge">工具市场</span></a>
-                        <span class="x-donate-link" v-if="!isInUSAFlag" @click="openDonateModal($event)"><a href="#" id="donateLink"><i class="nav-icon">❤</i>&nbsp;打赏鼓励</a></span>
-                    </h3>
-                </div>
-            </div>
-
-            <div class="x-toolbar x-inpage">
-
-                <button id="btnFormat" class="btn btn-primary btn-xs ui-mr-10" @click="format">格式化</button>
-                <button id="btnCompress" class="btn btn-success btn-xs" @click="compress">压缩</button>
-                <span class="x-split">|</span>
-                <input type="checkbox" v-model="jsonLintSwitch" id="jsonLint" @click="lintOn"><label for="jsonLint">JSONLint</label>
-                <span class="x-split">|</span>
-                <input type="checkbox" v-model="autoDecode" id="endecode" @click="autoDecodeFn"><label for="endecode">自动解码</label>
-                <span class="x-split">|</span>
-                <input type="checkbox" v-model="overrideJson" id="jsonOvrd" @click="setCache"><label for="jsonOvrd">节点编辑</label>
-                <span class="x-split">|</span>
-                <input type="checkbox" v-model="autoUnpackJsonString" id="autoUnpackJsonString" @click="autoUnpackJsonStringFn"><label for="autoUnpackJsonString">支持嵌套解析</label>
-                <span class="x-split">|</span>
-                <span class="x-sort">
-                    <span class="x-stitle">排序:</span>
-                    <label for="sort_null">默认</label>
-                    <input type="radio" name="jsonsort" id="sort_null" value="0" checked @click="format">
-                    <label for="sort_asc">升序</label>
-                    <input type="radio" name="jsonsort" id="sort_asc" value="1" @click="format">
-                    <label for="sort_desc">降序</label>
-                    <input type="radio" name="jsonsort" id="sort_desc" value="-1" @click="format">
-                </span>
-                <span class="x-split">|</span>
-                <span class="x-endecode">
-                    <button class="xjf-btn xjf-btn-left" @click="uniEncode">Uni编码</button><button class="xjf-btn xjf-btn-mid" @click="uniDecode">Uni解码</button><button class="xjf-btn xjf-btn-right" @click="urlDecode">URL解码</button>
-                </span>
-
-                <span id="optionBar"></span>
-            </div>
-
-            <div class="panel-body mod-json">
-                <div class="row panel-txt">
-                    <textarea class="form-control mod-textarea" id="jsonSource" placeholder="在这里粘贴您需要进行格式化的JSON代码" ref="jsonBox"></textarea>
-                </div>
-
-                <div class="row rst-item" id="modJsonResult">
-                    <div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>
-                    <div id="jfCallbackName_start" class="callback-name" v-html="jfCallbackName_start"></div>
-                    <div id="jfContent" v-html="placeHolder"></div>
-                    <pre id="jfContent_pre"></pre>
-                    <div id="jfCallbackName_end" class="callback-name" v-html="jfCallbackName_end"></div>
-                </div>
-            </div>
-        </div>
-        <script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
-        <script src="../static/vendor/codemirror/codemirror.js"></script>
-        <script src="../static/vendor/codemirror/javascript.js"></script>
-        <script src="../static/vendor/codemirror/active-line.js"></script>
-        <script src="../static/vendor/codemirror/matchbrackets.js"></script>
-        <script src="../static/vendor/codemirror/placeholder.js"></script>
-        <script src="json-lint.js"></script>
-        <script src="json-bigint.js"></script>
-        <script src="format-lib.js"></script>
-        <script src="json-abc.js"></script>
-        <script src="json-decode.js"></script>
-        <script src="../static/js/dark-mode.js"></script>
-        <script src="index.js" type="module"></script>
-    </body>
-</html>
+<!DOCTYPE HTML>
+<html lang="zh-CN" class="fh-jf">
+    <head>
+        <title>JSON格式化查看工具</title>
+        <meta charset="UTF-8">
+        <link rel="stylesheet" href="index.css" />
+		<script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
+        <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
+    </head>
+    <body class="theme-default">
+        <div class="wrapper wp-json" id="pageContainer">
+            <div class="panel panel-default" style="margin-bottom: 0px;">
+                <div class="panel-heading">
+                    <h3 class="panel-title">
+                        <a href="https://www.baidufe.com/fehelper/index/index.html" target="_blank" class="x-a-high">
+                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:JSON格式化
+                        <span class="x-xdemo" ref="demoLink1" @click="setDemo">示例1:JSON片段</span>
+                        <a class="x-xdemo" href="http://t.weather.sojson.com/api/weather/city/101030100" target="_blank">示例2:在线JSON</a>
+
+                        <span id="layoutBar">
+                            <button id="btnLeftRight" ref="btnLeftRight" class="selected" @click="changeLayout('left-right')">左右布局</button><button id="btnUpDown" ref="btnUpDown" @click="changeLayout('up-down')">上下布局</button>
+                        </span>
+                        <a class="x-other-tools" v-if="!isInUSAFlag" @click="openOptionsPage($event)"><i class="icon-plus-circle"></i> 探索更多实用工具 <span class="tool-market-badge">工具市场</span></a>
+                        <span class="x-donate-link" v-if="!isInUSAFlag" @click="openDonateModal($event)"><a href="#" id="donateLink"><i class="nav-icon">❤</i>&nbsp;打赏鼓励</a></span>
+                    </h3>
+                </div>
+            </div>
+
+            <div class="x-toolbar x-inpage">
+
+                <button id="btnFormat" class="btn btn-primary btn-xs ui-mr-10" @click="format">格式化</button>
+                <button id="btnCompress" class="btn btn-success btn-xs" @click="compress">压缩</button>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="jsonLintSwitch" id="jsonLint" @click="lintOn"><label for="jsonLint">JSONLint</label>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="autoDecode" id="endecode" @click="autoDecodeFn"><label for="endecode">自动解码</label>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="overrideJson" id="jsonOvrd" @click="setCache"><label for="jsonOvrd">节点编辑</label>
+                <span class="x-split">|</span>
+                <input type="checkbox" v-model="autoUnpackJsonString" id="autoUnpackJsonString" @click="autoUnpackJsonStringFn"><label for="autoUnpackJsonString">支持嵌套解析</label>
+                <span class="x-split">|</span>
+                <span class="x-sort">
+                    <span class="x-stitle">排序:</span>
+                    <label for="sort_null">默认</label>
+                    <input type="radio" name="jsonsort" id="sort_null" value="0" checked @click="format">
+                    <label for="sort_asc">升序</label>
+                    <input type="radio" name="jsonsort" id="sort_asc" value="1" @click="format">
+                    <label for="sort_desc">降序</label>
+                    <input type="radio" name="jsonsort" id="sort_desc" value="-1" @click="format">
+                </span>
+                <span class="x-split">|</span>
+                <span class="x-endecode">
+                    <button class="xjf-btn xjf-btn-left" @click="uniEncode">Uni编码</button><button class="xjf-btn xjf-btn-mid" @click="uniDecode">Uni解码</button><button class="xjf-btn xjf-btn-right" @click="urlDecode">URL解码</button>
+                </span>
+                <span class="x-split">|</span>
+                <button class="xjf-btn" @click="openJsonPathModal">JSONPath查询</button>
+
+                <span id="optionBar"></span>
+            </div>
+
+            <div class="panel-body mod-json">
+                <div class="row panel-txt">
+                    <textarea class="form-control mod-textarea" id="jsonSource" placeholder="在这里粘贴您需要进行格式化的JSON代码" ref="jsonBox"></textarea>
+                </div>
+
+                <div class="row rst-item" id="modJsonResult">
+                    <div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>
+                    <div id="jfCallbackName_start" class="callback-name" v-html="jfCallbackName_start"></div>
+                    <div id="jfContent" v-html="placeHolder"></div>
+                    <pre id="jfContent_pre"></pre>
+                    <div id="jfCallbackName_end" class="callback-name" v-html="jfCallbackName_end"></div>
+                </div>
+            </div>
+
+            <!-- JSONPath查询模态框 -->
+            <div id="jsonPathModal" class="jsonpath-modal" v-show="showJsonPathModal" @click="closeJsonPathModal">
+                <div class="jsonpath-modal-content" @click.stop>
+                    <div class="jsonpath-modal-header">
+                        <h3>JSONPath查询</h3>
+                        <div class="jsonpath-header-controls">
+                            <input type="text" class="xjf-input jsonpath-header-input" v-model="jsonPathQuery" 
+                                   placeholder="JSONPath表达式 (如: $.data.items[*].name)" 
+                                   @keyup.enter="executeJsonPath">
+                            <button class="xjf-btn jsonpath-header-btn" @click="executeJsonPath" :disabled="!jsonPathQuery.trim()">查询</button>
+                            <button class="xjf-btn xjf-btn-examples jsonpath-header-btn" @click="showJsonPathExamples">示例</button>
+                        </div>
+                        <span class="jsonpath-modal-close" @click="closeJsonPathModal">&times;</span>
+                    </div>
+                    <div class="jsonpath-modal-body">
+                        
+                        <!-- 查询结果区域 -->
+                        <div class="jsonpath-results-section" v-if="jsonPathQuery && (jsonPathResults.length > 0 || jsonPathError)">
+                            <div class="jsonpath-query-info">
+                                <strong>查询表达式:</strong> <code>{{jsonPathQuery}}</code>
+                                <span class="jsonpath-result-count" v-if="!jsonPathError">找到 {{jsonPathResults.length}} 个结果</span>
+                            </div>
+                                            <div class="jsonpath-result-actions" v-if="jsonPathResults.length > 0">
+                        <button class="xjf-btn" 
+                                :class="{
+                                    'xjf-btn-copying': copyButtonState === 'copying',
+                                    'xjf-btn-success': copyButtonState === 'success',
+                                    'xjf-btn-error': copyButtonState === 'error'
+                                }"
+                                @click="copyJsonPathResults"
+                                :disabled="copyButtonState === 'copying'">
+                            <span v-if="copyButtonState === 'normal'">复制结果</span>
+                            <span v-if="copyButtonState === 'copying'">复制中...</span>
+                            <span v-if="copyButtonState === 'success'">✓ 复制成功</span>
+                            <span v-if="copyButtonState === 'error'">✗ 复制失败</span>
+                        </button>
+                        <button class="xjf-btn" @click="downloadJsonPathResults">下载结果</button>
+                    </div>
+                        <div class="jsonpath-results" v-if="jsonPathResults.length > 0">
+                            <div class="jsonpath-result-item" v-for="(result, index) in jsonPathResults" :key="index">
+                                <div class="jsonpath-result-path">路径: <code>{{result.path}}</code></div>
+                                <pre class="jsonpath-result-value">{{formatJsonPathResult(result.value)}}</pre>
+                            </div>
+                        </div>
+                        <div class="jsonpath-no-results" v-if="jsonPathResults.length === 0 && !jsonPathError">
+                            <p>未找到匹配的结果</p>
+                        </div>
+                            <div class="jsonpath-error" v-if="jsonPathError">
+                                <p class="error-msg">{{jsonPathError}}</p>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+    
+            <!-- JSONPath示例模态框 -->
+            <div id="jsonPathExamplesModal" class="jsonpath-modal" v-show="showJsonPathExamplesModal" @click="closeJsonPathExamplesModal">
+                <div class="jsonpath-modal-content" @click.stop>
+                    <div class="jsonpath-modal-header">
+                        <h3>JSONPath查询示例</h3>
+                        <span class="jsonpath-modal-close" @click="closeJsonPathExamplesModal">&times;</span>
+                    </div>
+                    <div class="jsonpath-modal-body">
+                        <div class="jsonpath-examples">
+                            <div class="jsonpath-example-item" v-for="example in jsonPathExamples" :key="example.path" @click="useJsonPathExample(example.path)">
+                                <div class="jsonpath-example-path"><code>{{example.path}}</code></div>
+                                <div class="jsonpath-example-desc">{{example.description}}</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
+        <script src="../static/vendor/codemirror/codemirror.js"></script>
+        <script src="../static/vendor/codemirror/javascript.js"></script>
+        <script src="../static/vendor/codemirror/active-line.js"></script>
+        <script src="../static/vendor/codemirror/matchbrackets.js"></script>
+        <script src="../static/vendor/codemirror/placeholder.js"></script>
+        <script src="json-lint.js"></script>
+        <script src="json-bigint.js"></script>
+        <script src="format-lib.js"></script>
+        <script src="json-abc.js"></script>
+        <script src="json-decode.js"></script>
+        <script src="../static/js/dark-mode.js"></script>
+        <script src="index.js" type="module"></script>
+    </body>
+</html>

+ 351 - 1
apps/json-format/index.js

@@ -25,7 +25,26 @@ new Vue({
         fireChange: true,
         overrideJson: false,
         isInUSAFlag: false,
-        autoUnpackJsonString: false
+        autoUnpackJsonString: false,
+        // JSONPath查询相关
+        jsonPathQuery: '',
+        showJsonPathModal: false,
+        showJsonPathExamplesModal: false,
+        jsonPathResults: [],
+        jsonPathError: '',
+        copyButtonState: 'normal', // normal, copying, success, error
+        jsonPathExamples: [
+            { path: '$', description: '根对象' },
+            { path: '$.data', description: '获取data属性' },
+            { path: '$.data.*', description: '获取data下的所有属性' },
+            { path: '$.data[0]', description: '获取data数组的第一个元素' },
+            { path: '$.data[*]', description: '获取data数组的所有元素' },
+            { path: '$.data[?(@.name)]', description: '获取data数组中有name属性的元素' },
+            { path: '$..name', description: '递归查找所有name属性' },
+            { path: '$.data[0:3]', description: '获取data数组的前3个元素' },
+            { path: '$.data[-1]', description: '获取data数组的最后一个元素' },
+            { path: '$.*.price', description: '获取所有子对象的price属性' }
+        ]
     },
     mounted: function () {
         // 自动开关灯控制
@@ -336,6 +355,337 @@ new Vue({
                 localStorage.setItem('jsonformat:auto-unpack-json-string', this.autoUnpackJsonString);
                 this.format();
             });
+        },
+
+        // JSONPath查询功能
+        executeJsonPath: function() {
+            this.jsonPathError = '';
+            this.jsonPathResults = [];
+
+            if (!this.jsonPathQuery.trim()) {
+                this.jsonPathError = '请输入JSONPath查询表达式';
+                return;
+            }
+
+            let source = this.jsonFormattedSource || editor.getValue();
+            if (!source.trim()) {
+                this.jsonPathError = '请先输入JSON数据';
+                return;
+            }
+
+            try {
+                let jsonObj = JSON.parse(source);
+                this.jsonPathResults = this.queryJsonPath(jsonObj, this.jsonPathQuery.trim());
+                this.showJsonPathModal = true;
+            } catch (error) {
+                this.jsonPathError = 'JSON格式错误:' + error.message;
+                this.showJsonPathModal = true;
+            }
+        },
+
+        // JSONPath查询引擎
+        queryJsonPath: function(obj, path) {
+            let results = [];
+            
+            try {
+                // 简化的JSONPath解析器
+                if (path === '$') {
+                    results.push({ path: '$', value: obj });
+                    return results;
+                }
+
+                // 移除开头的$
+                if (path.startsWith('$.')) {
+                    path = path.substring(2);
+                } else if (path.startsWith('$')) {
+                    path = path.substring(1);
+                }
+
+                // 执行查询
+                this.evaluateJsonPath(obj, path, '$', results);
+                
+            } catch (error) {
+                throw new Error('JSONPath表达式错误:' + error.message);
+            }
+
+            return results;
+        },
+
+        // 递归评估JSONPath
+        evaluateJsonPath: function(current, path, currentPath, results) {
+            if (!path) {
+                results.push({ path: currentPath, value: current });
+                return;
+            }
+
+            // 处理递归搜索 ..
+            if (path.startsWith('..')) {
+                let remainPath = path.substring(2);
+                this.recursiveSearch(current, remainPath, currentPath, results);
+                return;
+            }
+
+            // 解析下一个路径片段
+            let match;
+            
+            // 处理数组索引 [index] 或 [*] 或 [start:end]
+            if ((match = path.match(/^\[([^\]]+)\](.*)$/))) {
+                let indexExpr = match[1];
+                let remainPath = match[2];
+                
+                if (!Array.isArray(current)) {
+                    return;
+                }
+
+                if (indexExpr === '*') {
+                    // 通配符:所有元素
+                    current.forEach((item, index) => {
+                        this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results);
+                    });
+                } else if (indexExpr.includes(':')) {
+                    // 数组切片 [start:end]
+                    let [start, end] = indexExpr.split(':').map(s => s.trim() === '' ? undefined : parseInt(s));
+                    let sliced = current.slice(start, end);
+                    sliced.forEach((item, index) => {
+                        let actualIndex = (start || 0) + index;
+                        this.evaluateJsonPath(item, remainPath, currentPath + '[' + actualIndex + ']', results);
+                    });
+                } else if (indexExpr.startsWith('?(')) {
+                    // 过滤表达式 [?(@.prop)]
+                    current.forEach((item, index) => {
+                        if (this.evaluateFilter(item, indexExpr)) {
+                            this.evaluateJsonPath(item, remainPath, currentPath + '[' + index + ']', results);
+                        }
+                    });
+                } else {
+                    // 具体索引
+                    let index = parseInt(indexExpr);
+                    if (index < 0) {
+                        index = current.length + index; // 负索引
+                    }
+                    if (index >= 0 && index < current.length) {
+                        this.evaluateJsonPath(current[index], remainPath, currentPath + '[' + index + ']', results);
+                    }
+                }
+                return;
+            }
+
+            // 处理属性访问 .property 或直接属性名
+            if ((match = path.match(/^\.?([^.\[]+)(.*)$/))) {
+                let prop = match[1];
+                let remainPath = match[2];
+                
+                if (prop === '*') {
+                    // 通配符:所有属性
+                    if (typeof current === 'object' && current !== null) {
+                        Object.keys(current).forEach(key => {
+                            this.evaluateJsonPath(current[key], remainPath, currentPath + '.' + key, results);
+                        });
+                    }
+                } else {
+                    // 具体属性
+                    if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) {
+                        this.evaluateJsonPath(current[prop], remainPath, currentPath + '.' + prop, results);
+                    }
+                }
+                return;
+            }
+
+            // 处理方括号属性访问 ['property']
+            if ((match = path.match(/^\['([^']+)'\](.*)$/))) {
+                let prop = match[1];
+                let remainPath = match[2];
+                
+                if (typeof current === 'object' && current !== null && current.hasOwnProperty(prop)) {
+                    this.evaluateJsonPath(current[prop], remainPath, currentPath + "['" + prop + "']", results);
+                }
+                return;
+            }
+
+            // 如果没有特殊符号,当作属性名处理
+            if (typeof current === 'object' && current !== null && current.hasOwnProperty(path)) {
+                results.push({ path: currentPath + '.' + path, value: current[path] });
+            }
+        },
+
+        // 递归搜索
+        recursiveSearch: function(current, targetProp, currentPath, results) {
+            if (typeof current === 'object' && current !== null) {
+                // 检查当前对象的属性
+                if (current.hasOwnProperty(targetProp)) {
+                    results.push({ path: currentPath + '..' + targetProp, value: current[targetProp] });
+                }
+                
+                // 递归搜索子对象
+                Object.keys(current).forEach(key => {
+                    if (Array.isArray(current[key])) {
+                        current[key].forEach((item, index) => {
+                            this.recursiveSearch(item, targetProp, currentPath + '.' + key + '[' + index + ']', results);
+                        });
+                    } else if (typeof current[key] === 'object' && current[key] !== null) {
+                        this.recursiveSearch(current[key], targetProp, currentPath + '.' + key, results);
+                    }
+                });
+            }
+        },
+
+        // 简单的过滤器评估
+        evaluateFilter: function(item, filterExpr) {
+            // 简化的过滤器实现,只支持基本的属性存在性检查
+            // 如 ?(@.name) 检查是否有name属性
+            let match = filterExpr.match(/^\?\(@\.(\w+)\)$/);
+            if (match) {
+                let prop = match[1];
+                return typeof item === 'object' && item !== null && item.hasOwnProperty(prop);
+            }
+            
+            // 支持简单的比较 ?(@.age > 18)
+            match = filterExpr.match(/^\?\(@\.(\w+)\s*([><=!]+)\s*(.+)\)$/);
+            if (match) {
+                let prop = match[1];
+                let operator = match[2];
+                let value = match[3];
+                
+                if (typeof item === 'object' && item !== null && item.hasOwnProperty(prop)) {
+                    let itemValue = item[prop];
+                    let compareValue = isNaN(value) ? value.replace(/['"]/g, '') : parseFloat(value);
+                    
+                    switch (operator) {
+                        case '>': return itemValue > compareValue;
+                        case '<': return itemValue < compareValue;
+                        case '>=': return itemValue >= compareValue;
+                        case '<=': return itemValue <= compareValue;
+                        case '==': return itemValue == compareValue;
+                        case '!=': return itemValue != compareValue;
+                    }
+                }
+            }
+            
+            return false;
+        },
+
+        // 显示JSONPath示例
+        showJsonPathExamples: function() {
+            this.showJsonPathExamplesModal = true;
+        },
+
+        // 使用JSONPath示例
+        useJsonPathExample: function(path) {
+            this.jsonPathQuery = path;
+            this.closeJsonPathExamplesModal();
+        },
+
+        // 打开JSONPath查询模态框
+        openJsonPathModal: function() {
+            this.showJsonPathModal = true;
+            // 清空之前的查询结果
+            this.jsonPathResults = [];
+            this.jsonPathError = '';
+            this.copyButtonState = 'normal';
+        },
+
+        // 关闭JSONPath结果模态框
+        closeJsonPathModal: function() {
+            this.showJsonPathModal = false;
+            this.copyButtonState = 'normal'; // 重置复制按钮状态
+        },
+
+        // 关闭JSONPath示例模态框
+        closeJsonPathExamplesModal: function() {
+            this.showJsonPathExamplesModal = false;
+        },
+
+        // 格式化JSONPath查询结果
+        formatJsonPathResult: function(value) {
+            if (typeof value === 'object') {
+                return JSON.stringify(value, null, 2);
+            }
+            return String(value);
+        },
+
+        // 复制JSONPath查询结果
+        copyJsonPathResults: function() {
+            let resultText = this.jsonPathResults.map(result => {
+                return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`;
+            }).join('\n\n');
+            
+            // 设置复制状态
+            this.copyButtonState = 'copying';
+            
+            navigator.clipboard.writeText(resultText).then(() => {
+                this.copyButtonState = 'success';
+                setTimeout(() => {
+                    this.copyButtonState = 'normal';
+                }, 2000);
+            }).catch(() => {
+                // 兼容旧浏览器
+                try {
+                    let textArea = document.createElement('textarea');
+                    textArea.value = resultText;
+                    document.body.appendChild(textArea);
+                    textArea.select();
+                    document.execCommand('copy');
+                    document.body.removeChild(textArea);
+                    this.copyButtonState = 'success';
+                    setTimeout(() => {
+                        this.copyButtonState = 'normal';
+                    }, 2000);
+                } catch (error) {
+                    this.copyButtonState = 'error';
+                    setTimeout(() => {
+                        this.copyButtonState = 'normal';
+                    }, 2000);
+                }
+            });
+        },
+
+        // 下载JSONPath查询结果
+        downloadJsonPathResults: function() {
+            let resultText = this.jsonPathResults.map(result => {
+                return `路径: ${result.path}\n值: ${this.formatJsonPathResult(result.value)}`;
+            }).join('\n\n');
+            
+            // 基于JSONPath生成文件名
+            let filename = this.generateFilenameFromPath(this.jsonPathQuery);
+            
+            let blob = new Blob([resultText], { type: 'text/plain;charset=utf-8' });
+            let url = window.URL.createObjectURL(blob);
+            let a = document.createElement('a');
+            a.href = url;
+            a.download = filename + '.txt';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            window.URL.revokeObjectURL(url);
+        },
+
+        // 根据JSONPath生成文件名
+        generateFilenameFromPath: function(path) {
+            if (!path || path === '$') {
+                return 'jsonpath_root';
+            }
+            
+            // 移除开头的$和.
+            let cleanPath = path.replace(/^\$\.?/, '');
+            
+            // 替换特殊字符为下划线,保留数字、字母、点号、中划线
+            let filename = cleanPath
+                .replace(/[\[\]]/g, '_')  // 方括号替换为下划线
+                .replace(/[^\w\u4e00-\u9fa5.-]/g, '_')  // 特殊字符替换为下划线,保留中文
+                .replace(/_{2,}/g, '_')   // 多个连续下划线合并为一个
+                .replace(/^_|_$/g, '');   // 移除开头和结尾的下划线
+            
+            // 如果处理后为空,使用默认名称
+            if (!filename) {
+                return 'jsonpath_query';
+            }
+            
+            // 限制文件名长度
+            if (filename.length > 50) {
+                filename = filename.substring(0, 50) + '_truncated';
+            }
+            
+            return 'jsonpath_' + filename;
         }
     }
 });