|
|
@@ -1,5 +1,5 @@
|
|
|
import React, { useEffect, useState } from 'react'
|
|
|
-import { fetchMethods, fetchQueries, fetchSearchSummary } from '../api.js'
|
|
|
+import { fetchManualAxes, fetchMethods, fetchQueries, fetchSearchSummary } from '../api.js'
|
|
|
import Pager from '../components/Pager.jsx'
|
|
|
|
|
|
const SIZE = 30
|
|
|
@@ -14,6 +14,8 @@ export default function QueriesPage() {
|
|
|
const [page, setPage] = useState(1)
|
|
|
const [data, setData] = useState({ total: 0, page: 1, size: SIZE, items: [] })
|
|
|
const [summary, setSummary] = useState({})
|
|
|
+ const [manualAxes, setManualAxes] = useState({})
|
|
|
+ const [showAxes, setShowAxes] = useState(false)
|
|
|
|
|
|
useEffect(() => {
|
|
|
fetchMethods('queries').then((ms) => {
|
|
|
@@ -23,9 +25,11 @@ export default function QueriesPage() {
|
|
|
})
|
|
|
}, [])
|
|
|
useEffect(() => { fetchSearchSummary().then(setSummary) }, [])
|
|
|
+ useEffect(() => { fetchManualAxes().then(setManualAxes) }, [])
|
|
|
useEffect(() => { if (method) fetchQueries({ method, page, size: SIZE }).then(setData) }, [method, page])
|
|
|
|
|
|
const pick = (m) => { setMethod(m); setPage(1) }
|
|
|
+ const axes = manualAxes[method] // 当前面板的人工定义轴
|
|
|
|
|
|
return (
|
|
|
<div>
|
|
|
@@ -35,9 +39,12 @@ export default function QueriesPage() {
|
|
|
))}
|
|
|
</div>
|
|
|
<div className="filters">
|
|
|
+ {axes && <button className="btn ghost" onClick={() => setShowAxes(true)}>📋 查看人工定义的轴</button>}
|
|
|
<span className="count">共 {data.total} 条 query</span>
|
|
|
</div>
|
|
|
|
|
|
+ {showAxes && axes && <AxesModal method={method} axes={axes} onClose={() => setShowAxes(false)} />}
|
|
|
+
|
|
|
{data.items.length === 0 ? (
|
|
|
<div className="empty">暂无数据。先跑 <code>scripts/build_query_demo.py</code> 再 <code>scripts/import_to_db.py</code></div>
|
|
|
) : (
|
|
|
@@ -48,9 +55,9 @@ export default function QueriesPage() {
|
|
|
<tbody>
|
|
|
{data.items.map((it) => (
|
|
|
<tr key={it.id}>
|
|
|
- <td>{Object.entries(it.axes || {}).map(([k, v]) => (
|
|
|
+ <td><div className="axes">{Object.entries(it.axes || {}).map(([k, v]) => (
|
|
|
<span className="tag k" key={k}>{k}:{v}</span>
|
|
|
- ))}</td>
|
|
|
+ ))}</div></td>
|
|
|
<td className="q">{it.query}</td>
|
|
|
<td><ResultBtn s={summary[it.query]} query={it.query} /></td>
|
|
|
</tr>
|
|
|
@@ -64,6 +71,49 @@ export default function QueriesPage() {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+// 人工定义轴弹窗:列出该方法每个人工轴的全部取值。
|
|
|
+// 取值形状有三种:list(动作/阶段/模态)、{key:[..]}(需求点/载体位置)、{key:str}(知识类型后缀)。
|
|
|
+function AxesModal({ method, axes, onClose }) {
|
|
|
+ return (
|
|
|
+ <div className="modal-mask" onClick={onClose}>
|
|
|
+ <div className="modal" onClick={(e) => e.stopPropagation()}>
|
|
|
+ <div className="modal-hd">
|
|
|
+ <b>人工定义的轴 · {method}</b>
|
|
|
+ <span className="x" onClick={onClose}>✕</span>
|
|
|
+ </div>
|
|
|
+ <div className="modal-bd">
|
|
|
+ <p className="note">实质 / 形式 / 作用来自分类树;下面这些是人工定义的轴及全部取值。</p>
|
|
|
+ {Object.entries(axes).map(([name, vals]) => (
|
|
|
+ <div className="axrow" key={name}>
|
|
|
+ <div className="axname">{name}</div>
|
|
|
+ <div className="axvals"><AxValues vals={vals} /></div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function AxValues({ vals }) {
|
|
|
+ if (Array.isArray(vals)) {
|
|
|
+ return <>{vals.map((v) => <span className="tag k" key={v}>{v}</span>)}</>
|
|
|
+ }
|
|
|
+ // 字典:值是数组 → 「键:a b c」;值是字符串 → 「键 → 值」
|
|
|
+ return (
|
|
|
+ <div className="axsub">
|
|
|
+ {Object.entries(vals).map(([k, v]) => (
|
|
|
+ <div className="axsubrow" key={k}>
|
|
|
+ <span className="axsubkey">{k}</span>
|
|
|
+ {Array.isArray(v)
|
|
|
+ ? v.map((x) => <span className="tag k" key={x}>{x}</span>)
|
|
|
+ : <span className="tag k">{v}</span>}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
function ResultBtn({ s, query }) {
|
|
|
if (s && s.ok > 0) {
|
|
|
return <a className="btn" href={`#/q/${encodeURIComponent(query)}`}>🔎 看 {s.ok} 个结果 ›</a>
|