This commit is contained in:
+454
-339
@@ -11,12 +11,22 @@ import {
|
||||
Pagination,
|
||||
Select,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
TextArea,
|
||||
Toast,
|
||||
Typography,
|
||||
} from "@douyinfe/semi-ui-19";
|
||||
import {
|
||||
IconFilter,
|
||||
IconSetting,
|
||||
IconSort,
|
||||
IconSearch,
|
||||
IconClose,
|
||||
IconDownload,
|
||||
IconUpload,
|
||||
IconRefresh,
|
||||
} from "@douyinfe/semi-icons";
|
||||
import "./AlphaPage.css";
|
||||
import type { ColumnProps } from "@douyinfe/semi-ui-19/lib/es/table/interface";
|
||||
import {
|
||||
api,
|
||||
@@ -144,6 +154,8 @@ export function AlphaPage({
|
||||
const [facets, setFacets] = useState<Facets>({});
|
||||
const [draft, setDraft] = useState<Record<string, string>>({});
|
||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
||||
const [filterOpen, setFilterOpen] = useState(false);
|
||||
const [selectedViewId, setSelectedViewId] = useState<string | null>(null);
|
||||
const [submission, setSubmission] = useState<Submission>("UNSUBMITTED");
|
||||
const [syncingScope, setSyncingScope] = useState<Submission | null>(null);
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -238,6 +250,8 @@ export function AlphaPage({
|
||||
setDetailId(action.alpha_id);
|
||||
onOverlay();
|
||||
} else {
|
||||
setSelectedViewId(null);
|
||||
setFilterOpen(false);
|
||||
const {
|
||||
sort: nextSort,
|
||||
direction: nextDirection,
|
||||
@@ -296,15 +310,20 @@ export function AlphaPage({
|
||||
}));
|
||||
}
|
||||
function applyFilters() {
|
||||
setFilterOpen(false);
|
||||
setFilters({ ...draft });
|
||||
setPage(1);
|
||||
}
|
||||
function clearFilters() {
|
||||
setFilterOpen(false);
|
||||
setDraft({});
|
||||
setFilters({});
|
||||
setPage(1);
|
||||
}
|
||||
function changeSubmission(value: string) {
|
||||
setSelectedViewId(null);
|
||||
setFilterOpen(false);
|
||||
setDirection("desc");
|
||||
setSubmission(value as Submission);
|
||||
setDraft({});
|
||||
setFilters({});
|
||||
@@ -610,6 +629,233 @@ export function AlphaPage({
|
||||
const connected =
|
||||
account?.connection_status === "connected" ||
|
||||
account?.connection_status === "expired";
|
||||
const filterPanel = (
|
||||
<form
|
||||
className="alpha-filter-panel"
|
||||
aria-label="Alpha 筛选条件"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
applyFilters();
|
||||
}}
|
||||
>
|
||||
<header className="alpha-filter-header">
|
||||
<div>
|
||||
<h2>筛选 Alpha</h2>
|
||||
<p className="muted">设置搜索和筛选条件,查询后应用到当前视图。</p>
|
||||
</div>
|
||||
<Button
|
||||
icon={<IconClose aria-hidden="true" />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
aria-label="关闭筛选"
|
||||
onClick={() => setFilterOpen(false)}
|
||||
/>
|
||||
</header>
|
||||
<div className="alpha-filter-body">
|
||||
<div className="alpha-filter-fields">
|
||||
<label className="alpha-filter-search">
|
||||
搜索
|
||||
<Input
|
||||
prefix={<IconSearch aria-hidden="true" />}
|
||||
aria-label="搜索 Alpha"
|
||||
placeholder="搜索 ID、名称或表达式"
|
||||
value={draft.q ?? ""}
|
||||
onChange={(v) => updateDraft("q", v)}
|
||||
showClear
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
地区
|
||||
<Select
|
||||
aria-label="地区"
|
||||
placeholder="全部地区"
|
||||
showClear
|
||||
value={draft.region || undefined}
|
||||
optionList={options("region")}
|
||||
onChange={(v) => updateDraft("region", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
平台状态
|
||||
<Select
|
||||
aria-label="平台状态"
|
||||
placeholder="全部平台状态"
|
||||
showClear
|
||||
value={draft.status || undefined}
|
||||
optionList={options("status")}
|
||||
onChange={(v) => updateDraft("status", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
研究状态
|
||||
<Select
|
||||
aria-label="研究状态筛选"
|
||||
placeholder="全部研究状态"
|
||||
showClear
|
||||
value={draft.research_state || undefined}
|
||||
optionList={stateOptions}
|
||||
onChange={(v) => updateDraft("research_state", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
检查结果
|
||||
<Select
|
||||
aria-label="检查结果筛选"
|
||||
placeholder="全部检查结果"
|
||||
showClear
|
||||
value={draft.check_type || undefined}
|
||||
optionList={Object.entries(checkLabels).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
}))}
|
||||
onChange={(v) => updateDraft("check_type", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
自定义标签
|
||||
<Select
|
||||
aria-label="自定义标签筛选"
|
||||
showClear
|
||||
filter
|
||||
value={draft.tag || undefined}
|
||||
optionList={options("tags")}
|
||||
placeholder="全部标签"
|
||||
onChange={(v) => updateDraft("tag", v)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<details className="advanced-filters">
|
||||
<summary>更多筛选</summary>
|
||||
<div className="alpha-filter-fields">
|
||||
<label>
|
||||
研究来源
|
||||
<Select
|
||||
aria-label="研究来源筛选"
|
||||
showClear
|
||||
value={draft.source || undefined}
|
||||
optionList={options("source").map((option) => ({
|
||||
...option,
|
||||
label: sourceLabel(option.value),
|
||||
}))}
|
||||
placeholder="全部来源"
|
||||
onChange={(v) => updateDraft("source", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
研究编号
|
||||
<Input
|
||||
aria-label="研究编号筛选"
|
||||
value={draft.research_id || ""}
|
||||
onChange={(v) => updateDraft("research_id", v)}
|
||||
/>
|
||||
</label>
|
||||
{[
|
||||
["universe", "Universe"],
|
||||
["alpha_type", "Alpha 类型"],
|
||||
["language", "语言"],
|
||||
["stage", "IS / OS"],
|
||||
].map(([key, label]) => (
|
||||
<label key={key}>
|
||||
{label}
|
||||
<Select
|
||||
showClear
|
||||
aria-label={label}
|
||||
value={draft[key] || undefined}
|
||||
optionList={options(key)}
|
||||
placeholder="全部"
|
||||
onChange={(v) => updateDraft(key, v)}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
<label>
|
||||
平台隐藏状态
|
||||
<Select
|
||||
showClear
|
||||
value={draft.hidden || undefined}
|
||||
optionList={[
|
||||
{ value: "false", label: "可见" },
|
||||
{ value: "true", label: "隐藏" },
|
||||
]}
|
||||
placeholder="全部"
|
||||
onChange={(v) => updateDraft("hidden", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
中性化
|
||||
<Input
|
||||
aria-label="中性化筛选"
|
||||
value={draft.neutralization || ""}
|
||||
placeholder="例如 INDUSTRY"
|
||||
onChange={(v) => updateDraft("neutralization", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
创建时间起(UTC)
|
||||
<Input
|
||||
type="date"
|
||||
value={draft.created_from?.slice(0, 10) || ""}
|
||||
onChange={(v) =>
|
||||
updateDraft("created_from", v ? `${v}T00:00:00Z` : "")
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
创建时间止(UTC)
|
||||
<Input
|
||||
type="date"
|
||||
value={draft.created_to?.slice(0, 10) || ""}
|
||||
onChange={(v) =>
|
||||
updateDraft("created_to", v ? `${v}T23:59:59.999999Z` : "")
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
{Object.entries(metricLabels).map(([key, label]) => (
|
||||
<label key={key}>
|
||||
{label}
|
||||
<div className="range-inputs">
|
||||
<Input
|
||||
type="number"
|
||||
aria-label={`${label} 最小值`}
|
||||
placeholder="最小值"
|
||||
value={draft[`${key}_min`] || ""}
|
||||
onChange={(v) => updateDraft(`${key}_min`, v)}
|
||||
/>
|
||||
<span>—</span>
|
||||
<Input
|
||||
type="number"
|
||||
aria-label={`${label} 最大值`}
|
||||
placeholder="最大值"
|
||||
value={draft[`${key}_max`] || ""}
|
||||
onChange={(v) => updateDraft(`${key}_max`, v)}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
<Checkbox
|
||||
checked={draft.favorite === "true"}
|
||||
onChange={(e) =>
|
||||
updateDraft("favorite", e.target.checked ? "true" : "")
|
||||
}
|
||||
>
|
||||
只看收藏
|
||||
</Checkbox>
|
||||
</div>
|
||||
<p className="muted">
|
||||
数值筛选使用平台原始值,例如 Turnover 0.15 表示 15%。
|
||||
检查结果仅汇总已同步的平台检查;预检通过不代表可以提交,活动资格可用自定义标签记录。
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
<footer className="alpha-filter-footer">
|
||||
<Button type="tertiary" theme="borderless" onClick={clearFilters}>
|
||||
重置
|
||||
</Button>
|
||||
<Button htmlType="submit" theme="solid">
|
||||
查询
|
||||
</Button>
|
||||
</footer>
|
||||
</form>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{!connected && (
|
||||
@@ -627,223 +873,17 @@ export function AlphaPage({
|
||||
)}
|
||||
{error && <Banner type="danger" description={error} />}
|
||||
<section className="library-panel" ref={tablePanel}>
|
||||
<div className="alpha-tabs-header">
|
||||
<Tabs
|
||||
className="alpha-submission-tabs"
|
||||
activeKey={submission}
|
||||
onChange={changeSubmission}
|
||||
tabList={[
|
||||
{ itemKey: "UNSUBMITTED", tab: "待提交" },
|
||||
{ itemKey: "SUBMITTED", tab: "已提交" },
|
||||
]}
|
||||
/>
|
||||
<span className="alpha-last-sync">
|
||||
最近数据更新{" "}
|
||||
<span>
|
||||
{formatTime(
|
||||
typeof facets.last_sync === "string" ? facets.last_sync : null,
|
||||
account?.timezone,
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<form
|
||||
className="filter-panel"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
applyFilters();
|
||||
}}
|
||||
>
|
||||
<div className="primary-filters">
|
||||
<Input
|
||||
aria-label="搜索 Alpha"
|
||||
placeholder="搜索 ID、名称或表达式"
|
||||
value={draft.q ?? ""}
|
||||
onChange={(v) => updateDraft("q", v)}
|
||||
showClear
|
||||
/>
|
||||
<Select
|
||||
aria-label="地区"
|
||||
placeholder="全部地区"
|
||||
showClear
|
||||
value={draft.region || undefined}
|
||||
optionList={options("region")}
|
||||
onChange={(v) => updateDraft("region", v)}
|
||||
/>
|
||||
<Select
|
||||
aria-label="平台状态"
|
||||
placeholder="全部平台状态"
|
||||
showClear
|
||||
value={draft.status || undefined}
|
||||
optionList={options("status")}
|
||||
onChange={(v) => updateDraft("status", v)}
|
||||
/>
|
||||
<Select
|
||||
aria-label="研究状态筛选"
|
||||
placeholder="全部研究状态"
|
||||
showClear
|
||||
value={draft.research_state || undefined}
|
||||
optionList={stateOptions}
|
||||
onChange={(v) => updateDraft("research_state", v)}
|
||||
/>
|
||||
<Select
|
||||
aria-label="检查结果筛选"
|
||||
placeholder="全部检查结果"
|
||||
showClear
|
||||
value={draft.check_type || undefined}
|
||||
optionList={Object.entries(checkLabels).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
}))}
|
||||
onChange={(v) => updateDraft("check_type", v)}
|
||||
/>
|
||||
<Select
|
||||
aria-label="自定义标签筛选"
|
||||
showClear
|
||||
filter
|
||||
value={draft.tag || undefined}
|
||||
optionList={options("tags")}
|
||||
placeholder="全部标签"
|
||||
onChange={(v) => updateDraft("tag", v)}
|
||||
/>
|
||||
</div>
|
||||
<details className="advanced-filters">
|
||||
<summary>
|
||||
更多筛选{" "}
|
||||
{activeFilterCount ? `· ${activeFilterCount} 项已应用` : ""}
|
||||
</summary>
|
||||
<div className="advanced-grid">
|
||||
<label>
|
||||
研究来源
|
||||
<Select
|
||||
aria-label="研究来源筛选"
|
||||
showClear
|
||||
value={draft.source || undefined}
|
||||
optionList={options("source").map((option) => ({
|
||||
...option,
|
||||
label: sourceLabel(option.value),
|
||||
}))}
|
||||
placeholder="全部来源"
|
||||
onChange={(v) => updateDraft("source", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
研究编号
|
||||
<Input
|
||||
aria-label="研究编号筛选"
|
||||
value={draft.research_id || ""}
|
||||
onChange={(v) => updateDraft("research_id", v)}
|
||||
/>
|
||||
</label>
|
||||
{[
|
||||
["universe", "Universe"],
|
||||
["alpha_type", "Alpha 类型"],
|
||||
["language", "语言"],
|
||||
["stage", "IS / OS"],
|
||||
].map(([key, label]) => (
|
||||
<label key={key}>
|
||||
{label}
|
||||
<Select
|
||||
showClear
|
||||
aria-label={label}
|
||||
value={draft[key] || undefined}
|
||||
optionList={options(key)}
|
||||
placeholder="全部"
|
||||
onChange={(v) => updateDraft(key, v)}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
<label>
|
||||
平台隐藏状态
|
||||
<Select
|
||||
showClear
|
||||
value={draft.hidden || undefined}
|
||||
optionList={[
|
||||
{ value: "false", label: "可见" },
|
||||
{ value: "true", label: "隐藏" },
|
||||
]}
|
||||
placeholder="全部"
|
||||
onChange={(v) => updateDraft("hidden", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
中性化
|
||||
<Input
|
||||
aria-label="中性化筛选"
|
||||
value={draft.neutralization || ""}
|
||||
placeholder="例如 INDUSTRY"
|
||||
onChange={(v) => updateDraft("neutralization", v)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
创建时间起(UTC)
|
||||
<Input
|
||||
type="date"
|
||||
value={draft.created_from?.slice(0, 10) || ""}
|
||||
onChange={(v) =>
|
||||
updateDraft("created_from", v ? `${v}T00:00:00Z` : "")
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
创建时间止(UTC)
|
||||
<Input
|
||||
type="date"
|
||||
value={draft.created_to?.slice(0, 10) || ""}
|
||||
onChange={(v) =>
|
||||
updateDraft("created_to", v ? `${v}T23:59:59.999999Z` : "")
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
{Object.entries(metricLabels).map(([key, label]) => (
|
||||
<label key={key}>
|
||||
{label}
|
||||
<div className="range-inputs">
|
||||
<Input
|
||||
type="number"
|
||||
aria-label={`${label} 最小值`}
|
||||
placeholder="最小值"
|
||||
value={draft[`${key}_min`] || ""}
|
||||
onChange={(v) => updateDraft(`${key}_min`, v)}
|
||||
/>
|
||||
<span>—</span>
|
||||
<Input
|
||||
type="number"
|
||||
aria-label={`${label} 最大值`}
|
||||
placeholder="最大值"
|
||||
value={draft[`${key}_max`] || ""}
|
||||
onChange={(v) => updateDraft(`${key}_max`, v)}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
<Checkbox
|
||||
checked={draft.favorite === "true"}
|
||||
onChange={(e) =>
|
||||
updateDraft("favorite", e.target.checked ? "true" : "")
|
||||
}
|
||||
>
|
||||
只看收藏
|
||||
</Checkbox>
|
||||
</div>
|
||||
<p className="muted">
|
||||
数值筛选使用平台原始值,例如 Turnover 0.15 表示 15%。
|
||||
检查结果仅汇总已同步的平台检查;预检通过不代表可以提交,活动资格可用自定义标签记录。
|
||||
</p>
|
||||
</details>
|
||||
<div className="inline-actions filter-actions">
|
||||
<Button htmlType="submit" theme="solid">
|
||||
查询
|
||||
</Button>
|
||||
<Button type="tertiary" theme="borderless" onClick={clearFilters}>
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<SavedViews
|
||||
submission={submission}
|
||||
selectedId={selectedViewId}
|
||||
onSelect={setSelectedViewId}
|
||||
onSubmissionChange={changeSubmission}
|
||||
suspended={overlaySuspended || !active}
|
||||
onOverlay={onOverlay}
|
||||
filters={{ ...filters, submission, sort, direction, limit: pageSize }}
|
||||
columns={visibleColumns}
|
||||
onRestore={(view) => {
|
||||
setFilterOpen(false);
|
||||
const {
|
||||
submission: savedSubmission,
|
||||
sort: savedSort,
|
||||
@@ -861,11 +901,9 @@ export function AlphaPage({
|
||||
setDraft(values);
|
||||
setPage(1);
|
||||
setSelected([]);
|
||||
if (
|
||||
savedSubmission === "SUBMITTED" ||
|
||||
savedSubmission === "UNSUBMITTED"
|
||||
)
|
||||
setSubmission(savedSubmission);
|
||||
setSubmission(
|
||||
savedSubmission === "SUBMITTED" ? "SUBMITTED" : "UNSUBMITTED",
|
||||
);
|
||||
setSort(String(savedSort || "date_created"));
|
||||
setDirection(String(savedDirection || "desc"));
|
||||
if (typeof limit === "number") setPageSize(limit);
|
||||
@@ -879,104 +917,148 @@ export function AlphaPage({
|
||||
localStorage.setItem("alpha-columns", JSON.stringify(next));
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="table-toolbar">
|
||||
<div>
|
||||
<strong>
|
||||
{submission === "UNSUBMITTED" ? "待提交" : "已提交"} Alpha
|
||||
</strong>
|
||||
<span className="count-pill">{data.total.toLocaleString()}</span>
|
||||
{selected.length > 0 && (
|
||||
<span className="selection-text">
|
||||
已选择 {selected.length} 条
|
||||
</span>
|
||||
)}
|
||||
<div
|
||||
className="table-toolbar alpha-toolbar"
|
||||
aria-label="Alpha 表格工具栏"
|
||||
>
|
||||
<div className="inline-actions alpha-view-tools">
|
||||
<Popover
|
||||
trigger="custom"
|
||||
position="bottomLeft"
|
||||
visible={filterOpen && active && !overlaySuspended}
|
||||
onClickOutSide={() => setFilterOpen(false)}
|
||||
onEscKeyDown={() => setFilterOpen(false)}
|
||||
content={filterPanel}
|
||||
>
|
||||
<Button
|
||||
icon={<IconFilter aria-hidden="true" />}
|
||||
type={filterOpen || activeFilterCount ? "primary" : "tertiary"}
|
||||
theme={filterOpen || activeFilterCount ? "light" : "borderless"}
|
||||
aria-label="筛选 Alpha"
|
||||
onClick={() => {
|
||||
setFilterOpen((open) => !open);
|
||||
onOverlay();
|
||||
}}
|
||||
>
|
||||
筛选
|
||||
{activeFilterCount > 0 && (
|
||||
<span className="alpha-filter-count">
|
||||
{activeFilterCount}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</Popover>
|
||||
<Popover
|
||||
trigger="click"
|
||||
position="bottomRight"
|
||||
content={
|
||||
<div className="columns-picker">
|
||||
<strong>显示列</strong>
|
||||
<CheckboxGroup
|
||||
direction="vertical"
|
||||
value={visibleColumns}
|
||||
options={Object.entries(columnLabels).map(
|
||||
([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
disabled: value === "name",
|
||||
}),
|
||||
)}
|
||||
onChange={(values) => {
|
||||
const next = values.map(String);
|
||||
setVisibleColumns(next);
|
||||
localStorage.setItem(
|
||||
"alpha-columns",
|
||||
JSON.stringify(next),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
icon={<IconSetting aria-hidden="true" />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
aria-label="显示列设置"
|
||||
>
|
||||
列设置
|
||||
</Button>
|
||||
</Popover>
|
||||
<Popover
|
||||
trigger="click"
|
||||
position="bottomLeft"
|
||||
content={
|
||||
<div className="alpha-sort-panel">
|
||||
<h2>排序</h2>
|
||||
<Select
|
||||
aria-label="排序字段"
|
||||
size="small"
|
||||
value={sort}
|
||||
optionList={[
|
||||
{ value: "date_created", label: "创建时间" },
|
||||
{ value: "date_submitted", label: "提交时间" },
|
||||
{ value: "synced_at", label: "同步时间" },
|
||||
...Object.entries(metricLabels).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
})),
|
||||
{ value: "name", label: "名称" },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setSort(String(v));
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
aria-label="排序方向"
|
||||
size="small"
|
||||
value={direction}
|
||||
optionList={[
|
||||
{ value: "desc", label: "降序" },
|
||||
{ value: "asc", label: "升序" },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setDirection(String(v));
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
icon={<IconSort aria-hidden="true" />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
aria-label="排序设置"
|
||||
>
|
||||
排序
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="inline-actions">
|
||||
{selected.length > 0 && (
|
||||
<>
|
||||
<Button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
loading={busy === "self_correlation"}
|
||||
onClick={() => void newTask("self_correlation", selected)}
|
||||
>
|
||||
检测自相关
|
||||
</Button>
|
||||
<Button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setBulkVersions(
|
||||
Object.fromEntries(
|
||||
data.items
|
||||
.filter((row) => selected.includes(row.id))
|
||||
.map((row) => [row.id, row.research.version]),
|
||||
),
|
||||
);
|
||||
setBulkOpen(true);
|
||||
onOverlay();
|
||||
}}
|
||||
>
|
||||
批量编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
disabled={!connected}
|
||||
loading={busy === "alpha_refresh"}
|
||||
onClick={() => void newTask("alpha_refresh", selected)}
|
||||
>
|
||||
刷新选中
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Select
|
||||
aria-label="排序字段"
|
||||
size="small"
|
||||
value={sort}
|
||||
optionList={[
|
||||
{ value: "date_created", label: "创建时间" },
|
||||
{ value: "date_submitted", label: "提交时间" },
|
||||
{ value: "synced_at", label: "同步时间" },
|
||||
...Object.entries(metricLabels).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
})),
|
||||
{ value: "name", label: "名称" },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setSort(String(v));
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
aria-label="排序方向"
|
||||
size="small"
|
||||
value={direction}
|
||||
optionList={[
|
||||
{ value: "desc", label: "降序" },
|
||||
{ value: "asc", label: "升序" },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setDirection(String(v));
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
<div className="inline-actions alpha-table-actions">
|
||||
<a href={`/api/v1/alphas/export?${query}`}>
|
||||
<Button type="tertiary" size="small">
|
||||
<Button
|
||||
icon={<IconDownload aria-hidden="true" />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
</a>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<IconUpload aria-hidden="true" />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
onClick={() => setImporting(true)}
|
||||
onClick={() => {
|
||||
setImporting(true);
|
||||
onOverlay();
|
||||
}}
|
||||
>
|
||||
导入 Alpha ID
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<IconRefresh aria-hidden="true" />}
|
||||
theme="solid"
|
||||
disabled={!connected}
|
||||
onClick={() => {
|
||||
@@ -1007,40 +1089,56 @@ export function AlphaPage({
|
||||
全量同步已提交
|
||||
</Button>
|
||||
)}
|
||||
<Popover
|
||||
trigger="click"
|
||||
position="bottomRight"
|
||||
content={
|
||||
<div className="columns-picker">
|
||||
<strong>显示列</strong>
|
||||
<CheckboxGroup
|
||||
direction="vertical"
|
||||
value={visibleColumns}
|
||||
options={Object.entries(columnLabels).map(
|
||||
([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
disabled: value === "name",
|
||||
}),
|
||||
)}
|
||||
onChange={(values) => {
|
||||
const next = values.map(String);
|
||||
setVisibleColumns(next);
|
||||
localStorage.setItem(
|
||||
"alpha-columns",
|
||||
JSON.stringify(next),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button type="tertiary" aria-label="显示列设置" size="small">
|
||||
列设置
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
{selected.length > 0 && (
|
||||
<div className="alpha-selection-actions inline-actions">
|
||||
<span className="selection-text">已选择 {selected.length} 条</span>
|
||||
<Button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
loading={busy === "self_correlation"}
|
||||
onClick={() => void newTask("self_correlation", selected)}
|
||||
>
|
||||
检测自相关
|
||||
</Button>
|
||||
<Button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setBulkVersions(
|
||||
Object.fromEntries(
|
||||
data.items
|
||||
.filter((row) => selected.includes(row.id))
|
||||
.map((row) => [row.id, row.research.version]),
|
||||
),
|
||||
);
|
||||
setBulkOpen(true);
|
||||
onOverlay();
|
||||
}}
|
||||
>
|
||||
批量编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="tertiary"
|
||||
size="small"
|
||||
disabled={!connected}
|
||||
loading={busy === "alpha_refresh"}
|
||||
onClick={() => void newTask("alpha_refresh", selected)}
|
||||
>
|
||||
刷新选中
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
onClick={() => setSelected([])}
|
||||
>
|
||||
取消选择
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Table<Alpha>
|
||||
columns={columns}
|
||||
dataSource={data.items}
|
||||
@@ -1075,7 +1173,24 @@ export function AlphaPage({
|
||||
}
|
||||
/>
|
||||
<footer className="table-pagination" aria-label="Alpha 分页">
|
||||
<span className="muted">共 {data.total.toLocaleString()} 条</span>
|
||||
<div className="alpha-table-meta">
|
||||
<span className="muted">
|
||||
{submission === "UNSUBMITTED" ? "待提交" : "已提交"} Alpha{" "}
|
||||
<span className="count-pill">{data.total.toLocaleString()}</span>{" "}
|
||||
条
|
||||
</span>
|
||||
<span className="alpha-last-sync">
|
||||
最近数据更新{" "}
|
||||
<span>
|
||||
{formatTime(
|
||||
typeof facets.last_sync === "string"
|
||||
? facets.last_sync
|
||||
: null,
|
||||
account?.timezone,
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<Pagination
|
||||
size={compact ? "small" : "default"}
|
||||
currentPage={page}
|
||||
|
||||
Reference in New Issue
Block a user