Commit 6f4521a3 authored by David Yang's avatar David Yang

update: ui

parent 90f936e8
......@@ -43,7 +43,7 @@ export const AvatarButton = forwardRef(function AvatarButton(
let classes = clsx(
className,
square ? 'rounded-[20%]' : 'rounded-full',
'relative inline-grid focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500'
'relative inline-grid focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-[var(--pm-brand)]'
)
return typeof props.href === 'string' ? (
......
......@@ -20,7 +20,7 @@ const colors = {
teal: 'bg-teal-500/15 text-teal-700 group-data-hover:bg-teal-500/25',
cyan: 'bg-cyan-400/20 text-cyan-700 group-data-hover:bg-cyan-400/30',
sky: 'bg-sky-500/15 text-sky-700 group-data-hover:bg-sky-500/25',
blue: 'bg-blue-500/15 text-blue-700 group-data-hover:bg-blue-500/25',
blue: 'bg-[var(--pm-brand)]/15 text-[var(--pm-brand-strong)] group-data-hover:bg-[var(--pm-brand)]/25',
indigo:
'bg-indigo-500/15 text-indigo-700 group-data-hover:bg-indigo-500/25',
violet:
......@@ -54,7 +54,7 @@ export const BadgeButton = forwardRef(function BadgeButton(
) {
let classes = clsx(
className,
'group relative inline-flex rounded-md focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500'
'group relative inline-flex rounded-md focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-[var(--pm-brand)]'
)
return typeof props.href === 'string' ? (
......
......@@ -57,7 +57,7 @@ const base = [
'after:absolute after:inset-0 after:rounded-[calc(0.3125rem-1px)] after:shadow-[inset_0_1px_--theme(--color-white/15%)]',
,
// Focus ring
'group-data-focus:outline-2 group-data-focus:outline-offset-2 group-data-focus:outline-blue-500',
'group-data-focus:outline-2 group-data-focus:outline-offset-2 group-data-focus:outline-[var(--pm-brand)]',
// Disabled state
'group-data-disabled:opacity-50',
'group-data-disabled:border-zinc-950/25 group-data-disabled:bg-zinc-950/5 group-data-disabled:[--checkbox-check:var(--color-zinc-950)]/50 group-data-disabled:before:bg-transparent',
......@@ -95,7 +95,7 @@ const colors = {
teal: '[--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--color-teal-600)] [--checkbox-checked-border:var(--color-teal-700)]/90',
cyan: '[--checkbox-check:var(--color-cyan-950)] [--checkbox-checked-bg:var(--color-cyan-300)] [--checkbox-checked-border:var(--color-cyan-400)]/80',
sky: '[--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--color-sky-500)] [--checkbox-checked-border:var(--color-sky-600)]/80',
blue: '[--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--color-blue-600)] [--checkbox-checked-border:var(--color-blue-700)]/90',
blue: '[--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--pm-brand)] [--checkbox-checked-border:var(--pm-brand-strong)]',
indigo:
'[--checkbox-check:var(--color-white)] [--checkbox-checked-bg:var(--color-indigo-500)] [--checkbox-checked-border:var(--color-indigo-600)]/90',
violet:
......
......@@ -38,7 +38,7 @@ export function Combobox({
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-blue-500',
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-[var(--pm-brand)]',
// Disabled state
'has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none',
// Invalid state
......@@ -133,7 +133,7 @@ export function ComboboxOption({ children, className, ...props }) {
// Typography
'text-base/6 text-zinc-950 sm:text-sm/6 forced-colors:text-[CanvasText]',
// Focus
'outline-hidden data-focus:bg-blue-500 data-focus:text-white',
'outline-hidden data-focus:bg-[var(--pm-brand)] data-focus:text-white',
// Forced colors mode
'forced-color-adjust-none forced-colors:data-focus:bg-[Highlight] forced-colors:data-focus:text-[HighlightText]',
// Disabled
......
......@@ -51,7 +51,7 @@ export function DropdownItem({ className, ...props }) {
// Text styles
'text-left text-base/6 text-zinc-950 sm:text-sm/6 forced-colors:text-[CanvasText]',
// Focus
'data-focus:bg-blue-500 data-focus:text-white',
'data-focus:bg-[var(--pm-brand)] data-focus:text-white',
// Disabled state
'data-disabled:opacity-50',
// Forced colors mode
......
......@@ -38,7 +38,7 @@ export const Input = forwardRef(function Input(
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-blue-500',
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-[var(--pm-brand)]',
// Disabled state
'has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none',
])}
......
......@@ -22,7 +22,7 @@ export function Listbox({ className, placeholder, autoFocus, 'aria-label': ariaL
// Hide default focus styles
'focus:outline-hidden',
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset data-focus:after:ring-2 data-focus:after:ring-blue-500',
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset data-focus:after:ring-2 data-focus:after:ring-[var(--pm-brand)]',
// Disabled state
'data-disabled:opacity-50 data-disabled:before:bg-zinc-950/5 data-disabled:before:shadow-none',
])}
......@@ -115,7 +115,7 @@ export function ListboxOption({ children, className, ...props }) {
// Typography
'text-base/6 text-zinc-950 sm:text-sm/6 forced-colors:text-[CanvasText]',
// Focus
'outline-hidden data-focus:bg-blue-500 data-focus:text-white',
'outline-hidden data-focus:bg-[var(--pm-brand)] data-focus:text-white',
// Forced colors mode
'forced-color-adjust-none forced-colors:data-focus:bg-[Highlight] forced-colors:data-focus:text-[HighlightText]',
// Disabled
......
......@@ -61,7 +61,7 @@ const base = [
// Indicator color (dark mode)
,
// Focus ring
'group-data-focus:outline group-data-focus:outline-2 group-data-focus:outline-offset-2 group-data-focus:outline-blue-500',
'group-data-focus:outline group-data-focus:outline-2 group-data-focus:outline-offset-2 group-data-focus:outline-[var(--pm-brand)]',
// Disabled state
'group-data-disabled:opacity-50',
'group-data-disabled:border-zinc-950/25 group-data-disabled:bg-zinc-950/5 group-data-disabled:[--radio-checked-indicator:var(--color-zinc-950)]/50 group-data-disabled:before:bg-transparent',
......@@ -96,7 +96,7 @@ const colors = {
teal: '[--radio-checked-indicator:var(--color-white)] [--radio-checked-bg:var(--color-teal-600)] [--radio-checked-border:var(--color-teal-700)]/90',
cyan: '[--radio-checked-bg:var(--color-cyan-300)] [--radio-checked-border:var(--color-cyan-400)]/80 [--radio-checked-indicator:var(--color-cyan-950)]',
sky: '[--radio-checked-indicator:var(--color-white)] [--radio-checked-bg:var(--color-sky-500)] [--radio-checked-border:var(--color-sky-600)]/80',
blue: '[--radio-checked-indicator:var(--color-white)] [--radio-checked-bg:var(--color-blue-600)] [--radio-checked-border:var(--color-blue-700)]/90',
blue: '[--radio-checked-indicator:var(--color-white)] [--radio-checked-bg:var(--pm-brand)] [--radio-checked-border:var(--pm-brand-strong)]',
indigo:
'[--radio-checked-indicator:var(--color-white)] [--radio-checked-bg:var(--color-indigo-500)] [--radio-checked-border:var(--color-indigo-600)]/90',
violet:
......
'use client';
import clsx from 'clsx';
export function Segment({ className, children }) {
return (
<div
className={clsx(
'inline-flex items-center gap-1 rounded-full border border-zinc-200 bg-zinc-100/80 p-1',
className,
)}
>
{children}
</div>
);
}
export function SegmentOption({
active = false,
className,
children,
...props
}) {
return (
<button
type="button"
{...props}
className={clsx(
'inline-flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition-all',
active
? 'bg-white text-sky-700 shadow-sm ring-1 ring-sky-200'
: 'text-zinc-600 hover:bg-white/80 hover:text-zinc-900',
className,
)}
>
{children}
</button>
);
}
......@@ -15,7 +15,7 @@ export const Select = forwardRef(function Select({ className, multiple, ...props
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset has-data-focus:after:ring-2 has-data-focus:after:ring-blue-500',
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset has-data-focus:after:ring-2 has-data-focus:after:ring-[var(--pm-brand)]',
// Disabled state
'has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none',
])}
......
......@@ -101,8 +101,8 @@ const colors = {
'[--switch:white] [--switch-ring:var(--color-sky-600)]/80 [--switch-shadow:var(--color-sky-900)]/20',
],
blue: [
'[--switch-bg-ring:var(--color-blue-700)]/90 [--switch-bg:var(--color-blue-600)]',
'[--switch:white] [--switch-ring:var(--color-blue-700)]/90 [--switch-shadow:var(--color-blue-900)]/20',
'[--switch-bg-ring:var(--pm-brand-strong)] [--switch-bg:var(--pm-brand)]',
'[--switch:white] [--switch-ring:var(--pm-brand-strong)] [--switch-shadow:var(--pm-brand-strong)]/20',
],
indigo: [
'[--switch-bg-ring:var(--color-indigo-600)]/90 [--switch-bg:var(--color-indigo-500)]',
......@@ -148,7 +148,7 @@ export function Switch({ color = 'dark/zinc', className, ...props }) {
// Checked
'data-checked:bg-(--switch-bg) data-checked:ring-(--switch-bg-ring)',
// Focus
'focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-blue-500',
'focus:not-data-focus:outline-hidden data-focus:outline-2 data-focus:outline-offset-2 data-focus:outline-[var(--pm-brand)]',
// Hover
'data-hover:ring-black/15 data-hover:data-checked:ring-(--switch-bg-ring)',
,
......
......@@ -70,7 +70,7 @@ export function TableRow({ href, target, title, className, ...props }) {
className={clsx(
className,
href &&
'has-[[data-row-link][data-focus]]:outline-2 has-[[data-row-link][data-focus]]:-outline-offset-2 has-[[data-row-link][data-focus]]:outline-blue-500',
'has-[[data-row-link][data-focus]]:outline-2 has-[[data-row-link][data-focus]]:-outline-offset-2 has-[[data-row-link][data-focus]]:outline-[var(--pm-brand)]',
striped && 'even:bg-zinc-950/2.5',
href && striped && 'hover:bg-zinc-950/5',
href && !striped && 'hover:bg-zinc-950/2.5',
......
......@@ -15,7 +15,7 @@ export const Textarea = forwardRef(function Textarea({ className, resizable = tr
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
,
// Focus ring
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-blue-500',
'after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset sm:focus-within:after:ring-2 sm:focus-within:after:ring-[var(--pm-brand)]',
// Disabled state
'has-data-disabled:opacity-50 has-data-disabled:before:bg-zinc-950/5 has-data-disabled:before:shadow-none',
])}
......
:root {
--pm-brand: #009cd9;
--pm-brand-strong: #007bb8;
--pm-brand: #b42318;
--pm-brand-strong: #991b1b;
--pm-surface: #ffffff;
--pm-bg: #eef5f9;
--pm-bg: #f8fafc;
--pm-sidebar: #0f172a;
--pm-sidebar-soft: #1e293b;
--pm-text: #0f172a;
......@@ -17,7 +17,10 @@ body,
body {
margin: 0;
background: linear-gradient(180deg, #f8fbfd 0%, #eef5f9 100%);
background:
radial-gradient(circle at top left, rgba(251, 191, 188, 0.22), transparent 26%),
radial-gradient(circle at bottom right, rgba(251, 207, 232, 0.2), transparent 30%),
#f8fafc;
color: var(--pm-text);
font-family:
"PingFang SC",
......
import xyLogo from '@/assets/xy-logo.png';
import xyLogoSmall from '@/assets/xy-logo_small.png';
import { UserContext } from '@/wrapper/Auth';
import {
......@@ -220,7 +219,7 @@ export default function ProjectManagerLayout() {
}, [location.pathname]);
return (
<div className="flex h-screen overflow-hidden bg-zinc-50">
<div className="flex h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,_rgba(251,191,188,0.22),_transparent_26%),radial-gradient(circle_at_bottom_right,_rgba(251,207,232,0.2),_transparent_30%),#f8fafc]">
<aside
className={clsx(
'relative z-30 hidden shrink-0 flex-col border-r border-zinc-800 bg-zinc-900 p-5 lg:flex',
......@@ -230,19 +229,16 @@ export default function ProjectManagerLayout() {
<div
className={clsx(
'mb-7 flex items-center',
sidebarCollapsed ? 'justify-center' : 'gap-3 px-1',
sidebarCollapsed ? 'justify-center' : 'px-1',
)}
>
<img
src={sidebarCollapsed ? xyLogoSmall : xyLogo}
alt="XY Logo"
className={clsx(
'shrink-0 object-contain',
sidebarCollapsed
? 'h-9 w-auto max-w-[52px]'
: 'h-5 w-auto max-w-[128px]',
)}
/>
{sidebarCollapsed ? (
<img
src={xyLogoSmall}
alt="XY Logo"
className="h-9 w-auto max-w-[52px] shrink-0 object-contain"
/>
) : null}
{!sidebarCollapsed ? (
<h1 className="truncate text-sm font-semibold tracking-[0.02em] text-white/95">
{APP_NAME}
......@@ -300,12 +296,7 @@ export default function ProjectManagerLayout() {
close={() => setIsMobileSidebarOpen(false)}
>
<div className="px-5 pt-6">
<div className="flex items-center gap-2">
<img
src={xyLogo}
alt="XY Logo"
className="h-5 w-auto max-w-[128px] shrink-0 object-contain"
/>
<div className="flex items-center">
<h1 className="text-base font-semibold tracking-[0.02em] text-white">
{APP_NAME}
</h1>
......
......@@ -49,11 +49,12 @@
关键字段:
- `label`:案例标题
- `category`:分类与案例性质主维度
- `tag`:标签
- `case_type`:案例类型,列表与导入模板的主展示字段
- `case_attribute`:案例属性
- `category`:案例性质
- `case_details`:案例详情正文
- `case_warning`:案件警示
- `case_warning`:案例警示
- `keyword`:关键字,用于检索和热度统计
- `case_source`:案例来源
- `case_level`:案例等级
......@@ -67,7 +68,7 @@
1. 用户进入 `案例管理`
2. 点击 `手动新建`
3. 填写案例类型、案例属性、案例性质、案例详情、案件警示、关键字、案例来源、案例等级、案例年份
3. 填写标题、分类、标签、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级、案例年份
4. 前端调用 `POST /record`
5. 成功后生成一条 `risk_case_library` 记录
6. 页面顶部提示创建成功,并刷新当前列表
......@@ -77,19 +78,21 @@
1. 用户进入 `案例管理`
2. 点击 `Excel 导入`
3. 上传 `.xlsx`、`.xls` 或 `.csv`
3. 在导入弹框中可先点击 `导出模板` 下载标准模板,再上传 `.xlsx`、`.xls` 或 `.csv`
4. 前端读取首个工作表并解析表头
5. Excel 表头按以下模板解析:
- `案例类型` → `metadata.case_type`,同时作为 `title` 与 `metadata.label`
- `标题` → `metadata.label`,同时作为 `title`
- `分类` → `metadata.category`
- `标签` → `metadata.tag`
- `案例类型` → `metadata.case_type`;若 `标题` 为空,可退化为 `title`
- `案例属性` → `metadata.case_attribute`
- `案例性质` → `metadata.category`
- `案例详情` → `content`、`metadata.case_details`
- `案件警示` → `metadata.case_warning`
- `案例警示` → `metadata.case_warning`
- `关键字` → `metadata.keyword`,同时回填 `metadata.tag` 以兼容现有检索
- `案例来源` → `metadata.case_source`
- `案例等级` → `metadata.case_level`
- `案例年份` → `metadata.case_year`
6. 前端兼容旧模板字段名,但新导入导出统一使用上述 9 列模板
6. 前端兼容旧模板字段名,但新导入导出统一使用上述 11 列模板
7. 前端逐条调用 `POST /record` 批量落库
8. 导入完成后反馈成功条数和失败条数
......@@ -98,12 +101,14 @@
1. 用户进入 `案例管理`
2. 点击 `Excel 批量导出`
3. 前端按当前搜索词、案例性质与关键字筛选条件查询全部案例
4. 导出文件统一输出以下 9 列:
4. 导出文件统一输出以下 11 列:
- `标题`
- `分类`
- `标签`
- `案例类型`
- `案例属性`
- `案例性质`
- `案例详情`
- `案件警示`
- `案例警示`
- `关键字`
- `案例来源`
- `案例等级`
......@@ -113,20 +118,32 @@
### 4.4 浏览与检索案例
1. 用户进入 `案例管理`
2. 通过搜索框检索案例类型、案例属性、案例详情、案件警示、关键字、案例来源、案例等级
2. 通过搜索框检索标题、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级
3. 通过案例性质标签快速切换当前视图
4. 通过关键字输入框进一步缩小范围,前端同时兼容历史 `metadata.tag`
5. 页面以卡片方式展示案例摘要
6. 点击卡片后,以弹框方式查看并编辑详情
5. 页面顶部提供 `Segment` 视图切换,可在“卡片视图 / 表格视图”之间切换
6. 卡片视图用于浏览案例摘要,点击卡片后以弹框方式查看并编辑详情
7. 表格视图展示标题、分类、标签、案例类型、关键字、来源、等级、年份、更新时间等字段
8. 表格视图支持单选、全选当前已加载案例,并显示当前勾选数量
### 4.5 编辑案例
1. 用户在列表中打开某个案例详情
2. 修改案例类型、案例属性、案例性质、案例详情、案件警示、关键字、案例来源、案例等级、案例年份
2. 修改标题、分类、标签、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级、案例年份
3. 前端调用 `PUT /record/:name`
4. 保存成功后刷新列表与统计数据
### 4.6 删除案例
### 4.6 批量修改案例
1. 用户切换到表格视图
2. 勾选一个或多个案例
3. 点击 `批量修改`
4. 在弹框中填写需要统一覆盖的字段
5. 未填写字段保持原值不变
6. 前端逐条调用 `PUT /record/:name`
7. 保存成功后刷新列表;若部分失败,反馈失败条目
### 4.7 删除案例
1. 用户在案例详情内点击删除
2. 页面弹出二次确认
......@@ -134,6 +151,15 @@
4. 删除后从当前列表中移除
5. 默认走软删除,不直接做物理删除
### 4.8 表格批量删除与勾选导出
1. 用户切换到表格视图
2. 勾选一个或多个案例
3. 可点击 `导出勾选`,前端直接将当前勾选记录导出为 `.xlsx`
4. 可点击 `批量删除`,页面弹出二次确认
5. 前端逐条调用 `DELETE /record/:name`
6. 删除后刷新列表,并保留失败项提示
---
## 5. 联动规则
......@@ -143,15 +169,16 @@
- 案例库统一读取 `risk_case_library`
- 所有业务字段均放在 `metadata` 中
- `title` 与 `metadata.label` 保持同步,便于复用统一查询能力
- `title` 为空时可回退使用 `metadata.case_type`,再退化为 `metadata.case_details` 摘要
### 5.2 Excel 导入字段规则
- 未识别到的列忽略,不中断导入
- 空行直接跳过
- `案例类型` 为空时,使用 `标题` 或 `案例详情` 摘要自动生成 `title`
- `标题` 为空时,优先使用 `案例类型`,再回退为 `案例详情` 摘要自动生成 `title`
- 所有文本字段统一按字符串保存
- `案例年份` 优先按数字写入,不可解析时保留原字符串
- 导出模板固定为 9 列标准表头,便于线下统一维护
- 导出模板固定为 11 列标准表头,便于线下统一维护
### 5.3 筛选规则
......@@ -191,10 +218,17 @@
- 搜索框
- 案例性质切换
- 关键字筛选
- `Segment` 视图切换(卡片 / 表格)
- 手动新建按钮
- Excel 导入按钮
- 导入弹框中的导出模板按钮
- Excel 导出按钮
- 案例卡片列表
- 案例表格列表
- 表格勾选与全选
- 批量修改按钮
- 勾选导出按钮
- 批量删除按钮
- 详情编辑弹框
- 删除确认弹框
......@@ -203,11 +237,13 @@
建议展示:
- 标题
- 分类 / 标签
- 案例类型
- 案例属性
- 案例详情全文
- 案例性质
- 关键字
- 案件警示
- 案例警示
- 案例来源 / 案例等级 / 案例年份
- 案例详情字数
- 创建时间 / 更新时间
......
......@@ -34,7 +34,7 @@
建议说明:
- 一条记录代表一个案例
- 主要承载案例类型、案例属性、案例性质、案例详情、案件警示、关键字、案例来源、案例等级、案例年份
- 主要承载标题、分类、标签、案例类型、案例属性、案例详情、案例警示、关键字、案例来源、案例等级、案例年份
## 3.2 字段清单
......
......@@ -68,7 +68,8 @@ type Record = {
### 2.2 `Record.title`
- 建议优先取 `metadata.label`
- 若 `label` 为空,可退化为 `metadata.case_details` 前 24~32 个字符摘要
- 若 `label` 为空,可回退为 `metadata.case_type`
- 若仍为空,可退化为 `metadata.case_details` 前 24~32 个字符摘要
- 再兜底为 `未命名案例`
### 2.3 搜索与展示建议
......@@ -76,6 +77,7 @@ type Record = {
- 列表主标题使用 `label`
- 卡片或详情摘要使用 `case_details`
- 案例性质筛选读取 `category`
- 标签筛选读取 `tag`
- 关键字筛选读取 `keyword`,历史数据可兼容读取 `tag`
- 关键词检索优先覆盖 `label`、`keyword`、`case_details`、`case_warning`
......@@ -93,25 +95,49 @@ type Record = {
- `label` 用作主展示标题
- `category` 用于主题归类
- `tag` 用于更灵活的检索和二次分组
- `tag` 用于标签化管理和快速筛选
- `case_type` 用于案例主分类或类型归纳
- `case_attribute` 用于补充案例属性、归属维度或风险特征
- `case_details` 为案例详情正文,通常为几百到上千字
- `case_warning` 用于提炼关键警示信息
- `keyword` 用于检索和热度统计,可与历史 `tag` 并存
- `case_year` 建议存储为数字类型年份,便于筛选和排序
| 字段标签 | 字段别名 | 字段类型 | 是否主键 | 是否多值 | 是否必填 | 说明 |
| --- | --- | --- | --- | --- | --- | --- |
| 标题 | `label` | text | 否 | 否 | 否 | 案例展示主标题 |
| 分类 | `category` | text | 否 | 否 | 否 | 案例分类,如舞弊风险、合规风险 |
| 标签 | `tag` | text | 否 | 否 | 否 | 标签或关键词,如资金管理、采购审批 |
| 案例类型 | `case_type` | text | 否 | 否 | 否 | 案例类型,如违规操作、警示通报 |
| 案例属性 | `case_attribute` | text | 否 | 否 | 否 | 案例属性或归属维度 |
| 案例详情 | `case_details` | long_text | 否 | 否 | 否 | 案例详细内容正文 |
| 案例警示 | `case_warning` | text | 否 | 否 | 否 | 案例提炼出的警示或风险提示 |
| 关键字 | `keyword` | text | 否 | 否 | 否 | 检索关键字,支持人工维护 |
| 案例来源 | `case_source` | text | 否 | 否 | 否 | 案例来源,如监管通报、内部复盘 |
| 案例等级 | `case_level` | text | 否 | 否 | 否 | 案例风险等级或分级 |
| 案例年份 | `case_year` | number | 否 | 否 | 否 | 案例发生或归档年份 |
### 3.1.1 metadata 示例
| 字段标签 | 字段别名 | 字段类型 | 默认值 | 是否主键 | 是否多值 | 是否必填 | 搜索 | 聚合 | 说明 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 标题 | `label` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例展示主标题 |
| 分类 | `category` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例分类,如舞弊风险、合规风险 |
| 标签 | `tag` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 标签字段,用于快速筛选或兼容旧数据 |
| 案例类型 | `case_type` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例类型,如审批绕行、权限滥用 |
| 案例属性 | `case_attribute` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例属性或归属维度 |
| 案例详情 | `case_details` | `long_text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例详细内容正文 |
| 案例警示 | `case_warning` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例提炼出的警示或风险提示 |
| 关键字 | `keyword` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 检索关键字,支持人工维护 |
| 案例来源 | `case_source` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例来源,如监管通报、内部复盘 |
| 案例等级 | `case_level` | `text` | `null` | 否 | 否 | 否 | 否 | 否 | 案例风险等级或分级 |
| 案例年份 | `case_year` | `number` | `null` | 否 | 否 | 否 | 否 | 否 | 案例发生或归档年份 |
### 3.1.1 原始字段结构
以下结构与当前前端表单、保存 payload 和动态字段定义保持一致:
```json
[
{ "name": "label", "label": "标题", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "category", "label": "分类", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "tag", "label": "标签", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_type", "label": "案例类型", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_attribute", "label": "案例属性", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_details", "label": "案例详情", "type": "long_text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_warning", "label": "案例警示", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "keyword", "label": "关键字", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_source", "label": "案例来源", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_level", "label": "案例等级", "type": "text", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} },
{ "name": "case_year", "label": "案例年份", "type": "number", "default": null, "multiple": false, "required": false, "primary": false, "search": { "enabled": false }, "aggregation": { "enabled": false }, "settings": {} }
]
```
### 3.1.2 metadata 示例
```json
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment