|
|
|
|
@ -0,0 +1,580 @@
|
|
|
|
|
<!-- 规则列表页 -->
|
|
|
|
|
<template>
|
|
|
|
|
<div class="rule-list-page">
|
|
|
|
|
<!-- 查询条件 -->
|
|
|
|
|
<div class="search-bar">
|
|
|
|
|
<div class="search-item">
|
|
|
|
|
<label class="search-label">规则名称</label>
|
|
|
|
|
<ElInput
|
|
|
|
|
v-model="query.name"
|
|
|
|
|
placeholder="请输入规则名称"
|
|
|
|
|
clearable
|
|
|
|
|
class="search-input"
|
|
|
|
|
@clear="handleSearch"
|
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="search-item">
|
|
|
|
|
<label class="search-label">规则分类</label>
|
|
|
|
|
<ElTreeSelect
|
|
|
|
|
v-model="query.category_id"
|
|
|
|
|
:data="categoryTree"
|
|
|
|
|
:props="treeSelectProps"
|
|
|
|
|
node-key="id"
|
|
|
|
|
check-strictly
|
|
|
|
|
placeholder="请选择规则分类"
|
|
|
|
|
clearable
|
|
|
|
|
filterable
|
|
|
|
|
class="search-input"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="search-item">
|
|
|
|
|
<label class="search-label">状态</label>
|
|
|
|
|
<ElSelect
|
|
|
|
|
v-model="query.status"
|
|
|
|
|
placeholder="请选择状态"
|
|
|
|
|
clearable
|
|
|
|
|
class="search-input"
|
|
|
|
|
>
|
|
|
|
|
<ElOption label="启用" :value="0" />
|
|
|
|
|
<ElOption label="停用" :value="1" />
|
|
|
|
|
</ElSelect>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="search-item date-item">
|
|
|
|
|
<label class="search-label">创建时间</label>
|
|
|
|
|
<ElDatePicker
|
|
|
|
|
v-model="dateRange"
|
|
|
|
|
type="daterange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始日期"
|
|
|
|
|
end-placeholder="结束日期"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
class="date-picker"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="search-actions">
|
|
|
|
|
<ElButton type="primary" :icon="Search" @click="handleSearch">查询</ElButton>
|
|
|
|
|
<ElButton :icon="Refresh" @click="handleReset">重置</ElButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 列表 -->
|
|
|
|
|
<div class="list-card">
|
|
|
|
|
<div class="list-header">
|
|
|
|
|
<div class="list-header-left">
|
|
|
|
|
<span class="list-title">规则列表</span>
|
|
|
|
|
<ElButton type="primary" :icon="Plus" class="create-btn" @click="handleCreate">新建规则</ElButton>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="list-header-right">
|
|
|
|
|
<ElButton
|
|
|
|
|
type="success"
|
|
|
|
|
:disabled="selectedIds.length === 0"
|
|
|
|
|
@click="handleBatchEnable"
|
|
|
|
|
>
|
|
|
|
|
批量启用
|
|
|
|
|
</ElButton>
|
|
|
|
|
<ElButton
|
|
|
|
|
type="warning"
|
|
|
|
|
:disabled="selectedIds.length === 0"
|
|
|
|
|
@click="handleBatchDisable"
|
|
|
|
|
>
|
|
|
|
|
批量停用
|
|
|
|
|
</ElButton>
|
|
|
|
|
<ElButton
|
|
|
|
|
type="danger"
|
|
|
|
|
:disabled="selectedIds.length === 0"
|
|
|
|
|
@click="handleBatchDelete"
|
|
|
|
|
>
|
|
|
|
|
批量删除
|
|
|
|
|
</ElButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ElTable
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
:data="tableData"
|
|
|
|
|
row-key="id"
|
|
|
|
|
stripe
|
|
|
|
|
@selection-change="onSelectionChange"
|
|
|
|
|
>
|
|
|
|
|
<ElTableColumn type="selection" width="50" align="center" />
|
|
|
|
|
<ElTableColumn label="规则名称" prop="name" min-width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="rule-name-link" @click="handleViewDetail(row)">{{ row.name }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="规则分类" prop="category_name" width="160" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="category-tag">{{ row.category_name || getFullLabel(row.category_id) || "-" }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="规则详情" width="100" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<ElButton link type="primary" :icon="View" @click="handleViewDetail(row)">查看</ElButton>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="创建人" width="100" align="center">
|
|
|
|
|
<template #default="{ row }">{{ row.created_by?.name || "-" }}</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="创建时间" prop="created_time" width="170" align="center">
|
|
|
|
|
<template #default="{ row }">{{ formatDateTime(row.created_time) }}</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="最近修改时间" prop="updated_time" width="170" align="center">
|
|
|
|
|
<template #default="{ row }">{{ formatDateTime(row.updated_time) }}</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="状态" width="100" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<ElSwitch
|
|
|
|
|
v-model="row.status"
|
|
|
|
|
:active-value="0"
|
|
|
|
|
:inactive-value="1"
|
|
|
|
|
active-text="启用"
|
|
|
|
|
inactive-text="停用"
|
|
|
|
|
inline-prompt
|
|
|
|
|
@change="(val: string | number | boolean) => handleToggleStatus(row, val as RuleStatus)"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="操作" width="200" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<ElButton link type="primary" @click="handleEdit(row)">编辑</ElButton>
|
|
|
|
|
<ElButton link type="primary" @click="handleTest(row)">测试</ElButton>
|
|
|
|
|
<ElButton link type="primary" @click="handleCopy(row)">复制</ElButton>
|
|
|
|
|
<ElButton link type="danger" @click="handleDelete(row)">删除</ElButton>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<template #empty>
|
|
|
|
|
<ElEmpty description="暂无规则数据" />
|
|
|
|
|
</template>
|
|
|
|
|
</ElTable>
|
|
|
|
|
|
|
|
|
|
<div class="pagination">
|
|
|
|
|
<span class="total-text">共 {{ total }} 条</span>
|
|
|
|
|
<ElPagination
|
|
|
|
|
:current-page="query.page_no"
|
|
|
|
|
:page-size="query.page_size"
|
|
|
|
|
:total="total"
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
layout="sizes, prev, pager, next, jumper"
|
|
|
|
|
@current-change="onPageChange"
|
|
|
|
|
@size-change="onSizeChange"
|
|
|
|
|
/>
|
|
|
|
|
<span class="jump-text">前往</span>
|
|
|
|
|
<ElInputNumber
|
|
|
|
|
v-model="jumpPage"
|
|
|
|
|
:min="1"
|
|
|
|
|
:max="Math.max(1, Math.ceil(total / query.page_size))"
|
|
|
|
|
size="small"
|
|
|
|
|
:controls="false"
|
|
|
|
|
class="jump-input"
|
|
|
|
|
@keyup.enter="goPage"
|
|
|
|
|
/>
|
|
|
|
|
<span class="jump-text">页</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 规则详情弹窗(查看) -->
|
|
|
|
|
<ElDialog
|
|
|
|
|
v-model="detailVisible"
|
|
|
|
|
title="规则详情"
|
|
|
|
|
width="640px"
|
|
|
|
|
destroy-on-close
|
|
|
|
|
>
|
|
|
|
|
<el-descriptions :column="1" border v-if="detailRow">
|
|
|
|
|
<el-descriptions-item label="规则名称">{{ detailRow.name }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="规则分类">
|
|
|
|
|
{{ detailRow.category_name || getFullLabel(detailRow.category_id) }}
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="创建人">{{ detailRow.created_by?.name || "-" }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="创建时间">{{ formatDateTime(detailRow.created_time) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="最近修改时间">{{ formatDateTime(detailRow.updated_time) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="状态">{{ detailRow.status === 0 ? "启用" : "停用" }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="规则描述">{{ detailRow.description || "-" }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="提示词模板">
|
|
|
|
|
<pre class="detail-prompt">{{ detailRow.prompt_template }}</pre>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<ElButton @click="detailVisible = false">关闭</ElButton>
|
|
|
|
|
<ElButton type="primary" @click="() => { detailVisible = false; handleEdit(detailRow!); }">编辑</ElButton>
|
|
|
|
|
</template>
|
|
|
|
|
</ElDialog>
|
|
|
|
|
|
|
|
|
|
<!-- 测试抽屉 -->
|
|
|
|
|
<RuleTestDrawer v-model="testDrawerVisible" :rule="currentTestRule" />
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
defineOptions({ name: "RuleList" });
|
|
|
|
|
|
|
|
|
|
import { ref, reactive, onMounted } from "vue";
|
|
|
|
|
import { useRouter } from "vue-router";
|
|
|
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
|
|
import { Plus, Search, Refresh, View } from "@element-plus/icons-vue";
|
|
|
|
|
import { RuleAPI } from "@/api/module_rule/rule";
|
|
|
|
|
import type { RuleItem, RulePageQuery, RuleStatus, RuleForm } from "@/api/module_rule/rule";
|
|
|
|
|
import { useRuleCategory } from "../useRuleCategory";
|
|
|
|
|
import RuleTestDrawer from "./RuleTestDrawer.vue";
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const { tree: categoryTree, loadTree, getFullLabel } = useRuleCategory();
|
|
|
|
|
|
|
|
|
|
const treeSelectProps = {
|
|
|
|
|
label: "name",
|
|
|
|
|
children: "children",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const tableData = ref<RuleItem[]>([]);
|
|
|
|
|
const total = ref(0);
|
|
|
|
|
const selectedIds = ref<number[]>([]);
|
|
|
|
|
const dateRange = ref<string[] | null>(null);
|
|
|
|
|
const jumpPage = ref(1);
|
|
|
|
|
|
|
|
|
|
const query = reactive<RulePageQuery>({
|
|
|
|
|
page_no: 1,
|
|
|
|
|
page_size: 10,
|
|
|
|
|
name: "",
|
|
|
|
|
category_id: undefined,
|
|
|
|
|
status: undefined,
|
|
|
|
|
created_time: undefined,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 详情弹窗
|
|
|
|
|
const detailVisible = ref(false);
|
|
|
|
|
const detailRow = ref<RuleItem | null>(null);
|
|
|
|
|
|
|
|
|
|
async function loadList() {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const params: RulePageQuery = {
|
|
|
|
|
page_no: query.page_no,
|
|
|
|
|
page_size: query.page_size,
|
|
|
|
|
name: query.name?.trim() || undefined,
|
|
|
|
|
category_id: query.category_id,
|
|
|
|
|
status: query.status,
|
|
|
|
|
};
|
|
|
|
|
if (dateRange.value && dateRange.value.length === 2) {
|
|
|
|
|
params.created_time = dateRange.value;
|
|
|
|
|
}
|
|
|
|
|
const resp = await RuleAPI.page(params);
|
|
|
|
|
const data = resp?.data?.data as PageResult<RuleItem> | undefined;
|
|
|
|
|
tableData.value = data?.items ?? [];
|
|
|
|
|
total.value = data?.total ?? 0;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
tableData.value = [];
|
|
|
|
|
total.value = 0;
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSearch() {
|
|
|
|
|
query.page_no = 1;
|
|
|
|
|
loadList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleReset() {
|
|
|
|
|
query.name = "";
|
|
|
|
|
query.category_id = undefined;
|
|
|
|
|
query.status = undefined;
|
|
|
|
|
dateRange.value = null;
|
|
|
|
|
query.page_no = 1;
|
|
|
|
|
loadList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onPageChange(page: number) {
|
|
|
|
|
query.page_no = page;
|
|
|
|
|
loadList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onSizeChange(size: number) {
|
|
|
|
|
query.page_size = size;
|
|
|
|
|
query.page_no = 1;
|
|
|
|
|
loadList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function goPage() {
|
|
|
|
|
if (jumpPage.value && jumpPage.value >= 1) {
|
|
|
|
|
query.page_no = jumpPage.value;
|
|
|
|
|
loadList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onSelectionChange(rows: RuleItem[]) {
|
|
|
|
|
selectedIds.value = rows.map((r) => r.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleCreate() {
|
|
|
|
|
router.push("/rule/list/edit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleEdit(row: RuleItem) {
|
|
|
|
|
router.push(`/rule/list/edit?id=${row.id}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleViewDetail(row: RuleItem) {
|
|
|
|
|
detailRow.value = row;
|
|
|
|
|
detailVisible.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 测试
|
|
|
|
|
const testDrawerVisible = ref(false);
|
|
|
|
|
const currentTestRule = ref<RuleItem | null>(null);
|
|
|
|
|
|
|
|
|
|
function handleTest(row: RuleItem) {
|
|
|
|
|
currentTestRule.value = row;
|
|
|
|
|
testDrawerVisible.value = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 复制
|
|
|
|
|
async function handleCopy(row: RuleItem) {
|
|
|
|
|
try {
|
|
|
|
|
await RuleAPI.copy(row.id);
|
|
|
|
|
ElMessage.success("复制成功");
|
|
|
|
|
loadList();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// 全局拦截器已处理
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 状态切换(通过 update 接口更新 status 字段)
|
|
|
|
|
async function handleToggleStatus(row: RuleItem, val: RuleStatus) {
|
|
|
|
|
const actionText = val === 0 ? "启用" : "停用";
|
|
|
|
|
try {
|
|
|
|
|
const payload: RuleForm = {
|
|
|
|
|
id: row.id,
|
|
|
|
|
name: row.name,
|
|
|
|
|
category_id: row.category_id ?? null,
|
|
|
|
|
description: row.description ?? "",
|
|
|
|
|
prompt_template: row.prompt_template,
|
|
|
|
|
status: val,
|
|
|
|
|
};
|
|
|
|
|
await RuleAPI.update(row.id, payload);
|
|
|
|
|
ElMessage.success(`${actionText}成功`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// 回滚 UI
|
|
|
|
|
row.status = val === 0 ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 删除
|
|
|
|
|
function handleDelete(row: RuleItem) {
|
|
|
|
|
ElMessageBox.confirm(`确定要删除规则「${row.name}」吗?`, "删除确认", {
|
|
|
|
|
type: "warning",
|
|
|
|
|
confirmButtonText: "删除",
|
|
|
|
|
cancelButtonText: "取消",
|
|
|
|
|
})
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await RuleAPI.remove([row.id]);
|
|
|
|
|
ElMessage.success("删除成功");
|
|
|
|
|
loadList();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// 全局拦截器已处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 批量启用
|
|
|
|
|
async function handleBatchEnable() {
|
|
|
|
|
if (selectedIds.value.length === 0) return;
|
|
|
|
|
try {
|
|
|
|
|
await RuleAPI.batchStatus(selectedIds.value, 0);
|
|
|
|
|
ElMessage.success("批量启用成功");
|
|
|
|
|
selectedIds.value = [];
|
|
|
|
|
loadList();
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 批量停用
|
|
|
|
|
async function handleBatchDisable() {
|
|
|
|
|
if (selectedIds.value.length === 0) return;
|
|
|
|
|
try {
|
|
|
|
|
await RuleAPI.batchStatus(selectedIds.value, 1);
|
|
|
|
|
ElMessage.success("批量停用成功");
|
|
|
|
|
selectedIds.value = [];
|
|
|
|
|
loadList();
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 批量删除
|
|
|
|
|
function handleBatchDelete() {
|
|
|
|
|
if (selectedIds.value.length === 0) return;
|
|
|
|
|
ElMessageBox.confirm(`确定要删除选中的 ${selectedIds.value.length} 条规则吗?`, "批量删除确认", {
|
|
|
|
|
type: "warning",
|
|
|
|
|
confirmButtonText: "删除",
|
|
|
|
|
cancelButtonText: "取消",
|
|
|
|
|
})
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await RuleAPI.remove(selectedIds.value);
|
|
|
|
|
ElMessage.success("批量删除成功");
|
|
|
|
|
selectedIds.value = [];
|
|
|
|
|
loadList();
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatDateTime(value?: string): string {
|
|
|
|
|
if (!value) return "-";
|
|
|
|
|
const d = new Date(value);
|
|
|
|
|
if (Number.isNaN(d.getTime())) return value;
|
|
|
|
|
const pad = (n: number) => String(n).padStart(2, "0");
|
|
|
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(
|
|
|
|
|
d.getHours()
|
|
|
|
|
)}:${pad(d.getMinutes())}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await loadTree();
|
|
|
|
|
await loadList();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.rule-list-page {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 100%;
|
|
|
|
|
// padding: 16px;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 查询条件 */
|
|
|
|
|
.search-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px 16px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-label {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #374151;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
min-width: 56px;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-input {
|
|
|
|
|
width: 200px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.date-picker {
|
|
|
|
|
width: 280px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 列表卡片 */
|
|
|
|
|
.list-card {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.list-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
border-bottom: 1px solid #f3f4f6;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.list-header-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.list-title {
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #111827;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.create-btn {
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.list-header-right {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rule-name-link {
|
|
|
|
|
color: #409eff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
&:hover {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.category-tag {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
padding: 1px 8px;
|
|
|
|
|
border: 1px solid #d0e3ff;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
color: #409eff;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
background: #ecf5ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.total-text,
|
|
|
|
|
.jump-text {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.jump-input {
|
|
|
|
|
width: 60px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-prompt {
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
font-family: "SFMono-Regular", Consolas, monospace;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
max-height: 300px;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
|
|
|
|
</style>
|