样式修改
This commit is contained in:
1 parent
cd3a71baab
commit
30d4f9667b
1 file changed
+343
-121
@@ -1,12 +1,23 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
AlertCircle,
|
||||
AlignLeft,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Copy,
|
||||
ExternalLink,
|
||||
FolderTree,
|
||||
Globe,
|
||||
HelpCircle,
|
||||
Info,
|
||||
Layers,
|
||||
Link2,
|
||||
Monitor,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
Sliders,
|
||||
Tag,
|
||||
Trash2,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
@@ -98,6 +109,7 @@ export default function LightAppManagementPage() {
|
||||
const [editing, setEditing] = useState<LightApp | null>(null);
|
||||
const [form, setForm] = useState<FormState>(emptyForm);
|
||||
const [activeParamPopover, setActiveParamPopover] = useState<string | null>(null);
|
||||
const [viewMode, setViewMode] = useState<"table" | "cards">("table");
|
||||
|
||||
const saving = actions.create.isPending || actions.update.isPending;
|
||||
|
||||
@@ -180,109 +192,164 @@ export default function LightAppManagementPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyUrl = (url: string) => {
|
||||
navigator.clipboard?.writeText(url);
|
||||
toast.success("已复制地址");
|
||||
};
|
||||
|
||||
const tableApps = useMemo(() => apps, [apps]);
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto bg-[#FAFAFA]">
|
||||
<div className="mx-auto flex min-h-full max-w-[1280px] flex-col space-y-6 px-6 py-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-[20px] font-bold tracking-tight text-[#222222]">应用管理</h1>
|
||||
<p className="max-w-4xl text-[13px] leading-relaxed text-[#575757]">
|
||||
注册轻应用。window.open 类型在「应用列表」以卡片展示;iframe 类型挂载到所选菜单下并注入侧栏。
|
||||
<div className="h-full overflow-y-auto bg-neutral-50">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-7xl flex-col space-y-7 px-8 py-8">
|
||||
<div className="flex flex-col justify-between gap-6 md:flex-row md:items-center">
|
||||
<div className="space-y-1.5">
|
||||
<h1 className="flex items-center text-xl font-black tracking-tight text-neutral-900">
|
||||
应用管理
|
||||
</h1>
|
||||
<p className="max-w-2xl text-xs leading-relaxed text-neutral-500">
|
||||
注册轻应用。
|
||||
<code className="mx-1 rounded bg-neutral-100 px-1.5 py-0.5 font-mono text-[11px] text-neutral-600">
|
||||
window.open
|
||||
</code>
|
||||
类型在「应用列表」以卡片展示;
|
||||
<code className="mx-1 rounded bg-neutral-100 px-1.5 py-0.5 font-mono text-[11px] text-neutral-600">
|
||||
iframe
|
||||
</code>
|
||||
类型挂载到所选菜单下并注入侧栏。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={openCreate}
|
||||
className="flex h-9 cursor-pointer items-center space-x-1.5 rounded-[2px] bg-[#A8071A] px-4 text-[14px] font-bold text-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] transition-all hover:bg-[#BC0D1E] active:bg-[#820014]"
|
||||
className="flex cursor-pointer items-center justify-center space-x-1.5 rounded-lg bg-[#A8071A] px-5 py-2.5 text-xs font-semibold text-white shadow-md shadow-rose-950/10 transition-all hover:bg-[#BC0D1E] hover:shadow-lg hover:shadow-rose-900/15 active:bg-[#820014]"
|
||||
>
|
||||
<span>+ 注册应用</span>
|
||||
<Plus className="h-4 w-4" />
|
||||
<span>注册应用</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-sm">
|
||||
<Search className="absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-[#C1C1C1]" />
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="搜索应用名称 / 描述"
|
||||
className="h-9 w-full rounded-[2px] border border-[#E8E8E8] bg-white pl-10 pr-4 text-[14px] text-[#222222] placeholder-[#C1C1C1] shadow-sm transition-all focus:border-[#F5222D] focus:outline-none focus:ring-1 focus:ring-[#F5222D]"
|
||||
/>
|
||||
{search && (
|
||||
<div className="flex flex-col items-center gap-3.5 rounded-xl border border-neutral-100 bg-white p-3 shadow-[0_1px_3px_rgba(0,0,0,0.01)] md:flex-row">
|
||||
<div className="relative w-full flex-1">
|
||||
<Search className="absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-neutral-400" />
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="搜索应用名称 / 描述..."
|
||||
className="w-full rounded-lg border border-neutral-100 bg-neutral-50/60 py-2 pl-10 pr-10 text-xs text-neutral-800 placeholder-neutral-400 transition-all hover:bg-white focus:border-[#F5222D]/40 focus:outline-none focus:ring-4 focus:ring-[#F5222D]/10"
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
onClick={() => setSearch("")}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer text-neutral-400 hover:text-neutral-700"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center space-x-0.5 self-end rounded-lg border border-neutral-200/50 bg-neutral-100/60 p-0.5 md:self-auto">
|
||||
<button
|
||||
onClick={() => setSearch("")}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer text-[12px] text-[#A6A6A6] hover:text-[#F5222D]"
|
||||
onClick={() => setViewMode("table")}
|
||||
className={`cursor-pointer rounded-md px-4 py-1.5 text-xs font-bold transition-all ${
|
||||
viewMode === "table"
|
||||
? "bg-white text-[#F5222D] shadow-sm"
|
||||
: "text-neutral-500 hover:text-neutral-700"
|
||||
}`}
|
||||
>
|
||||
清除
|
||||
表格视图
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setViewMode("cards")}
|
||||
className={`cursor-pointer rounded-md px-4 py-1.5 text-xs font-bold transition-all ${
|
||||
viewMode === "cards"
|
||||
? "bg-white text-[#F5222D] shadow-sm"
|
||||
: "text-neutral-500 hover:text-neutral-700"
|
||||
}`}
|
||||
>
|
||||
卡片视图
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-visible rounded-[4px] border border-[#E8E8E8] bg-white shadow-[0_2px_8px_rgba(34,34,34,0.08)]">
|
||||
<table className="w-full table-fixed border-collapse text-left">
|
||||
<thead>
|
||||
<tr className="border-b border-[#E8E8E8] bg-[#FAFAFA] text-[12px] font-bold uppercase tracking-wider text-[#A6A6A6]">
|
||||
<th className="w-[18%] px-6 py-3.5">名称</th>
|
||||
<th className="w-[12%] px-4 py-3.5">类型</th>
|
||||
<th className="w-[34%] px-4 py-3.5">地址 / 挂载</th>
|
||||
<th className="w-[12%] px-4 py-3.5">参数</th>
|
||||
<th className="w-[12%] px-4 py-3.5">启用</th>
|
||||
<th className="w-[12%] px-4 py-3.5 pr-6 text-right">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[#E8E8E8]">
|
||||
<div className="space-y-4">
|
||||
<div className="text-xs font-medium text-neutral-500">
|
||||
搜寻到 <span className="font-bold text-[#F5222D]">{tableApps.length}</span>{" "}
|
||||
个注册完成的子模块轻应用
|
||||
</div>
|
||||
|
||||
{viewMode === "table" ? (
|
||||
<div className="overflow-visible rounded-xl border border-neutral-100 bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)]">
|
||||
<table className="w-full table-fixed border-collapse text-left">
|
||||
<thead>
|
||||
<tr className="select-none border-b border-neutral-100/80 bg-neutral-50/40 text-[12.5px] font-semibold text-neutral-500">
|
||||
<th className="w-[15%] px-6 py-4">名称</th>
|
||||
<th className="w-[14%] px-4 py-4">类型</th>
|
||||
<th className="w-[27%] px-4 py-4">地址</th>
|
||||
<th className="w-[14%] px-4 py-4">挂载</th>
|
||||
<th className="w-[9%] px-4 py-4">参数</th>
|
||||
<th className="w-[9%] px-4 py-4">启用</th>
|
||||
<th className="w-[12%] px-4 py-4 pr-6 text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-neutral-100">
|
||||
{tableApps.map((app) => (
|
||||
<tr key={app.appId} className="group align-top transition-colors hover:bg-[#FEFAFA]">
|
||||
<tr key={app.appId} className="group align-middle transition-colors hover:bg-neutral-50/40">
|
||||
<td className="px-6 py-[18px]">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span className="text-[14px] font-bold text-[#222222] transition-colors group-hover:text-[#F5222D]">
|
||||
{app.name}
|
||||
</span>
|
||||
{app.description ? (
|
||||
<span className="line-clamp-2 text-[12px] text-[#575757]">{app.description}</span>
|
||||
) : (
|
||||
<span className="text-[11px] italic text-[#C1C1C1]">无描述说明</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[14px] font-bold text-neutral-800 transition-colors group-hover:text-[#F5222D]">
|
||||
{app.name}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td className="px-4 py-[18px]">
|
||||
{app.appType === "window_open" ? (
|
||||
<span className="inline-flex select-none items-center space-x-1 rounded-[2px] border border-[#FDDADC] bg-[#FEEDED] px-2 py-0.5 text-[11px] font-medium text-[#F5222D] shadow-sm">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
<span className="inline-flex select-none items-center space-x-1.5 rounded-lg border border-rose-100 bg-rose-50 px-3 py-1 text-[12px] font-semibold text-rose-800">
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
<span>新页跳转</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex select-none items-center space-x-1 rounded-[2px] border border-[#D4E9FF] bg-[#EAF4FF] px-2 py-0.5 text-[11px] font-medium text-[#0079FE] shadow-sm">
|
||||
<Layers className="h-3 w-3" />
|
||||
<span className="inline-flex select-none items-center space-x-1.5 rounded-lg border border-sky-100 bg-sky-50 px-3 py-1 text-[12px] font-semibold text-sky-800">
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
<span>iframe 嵌套</span>
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
<td className="overflow-hidden px-4 py-[18px]">
|
||||
<div className="flex max-w-full flex-col space-y-1">
|
||||
<div className="flex max-w-full items-center space-x-1.5 font-mono text-[12px] text-neutral-600 transition-colors group-hover:text-neutral-800">
|
||||
<a
|
||||
href={app.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center space-x-1.5 truncate font-mono text-[13px] text-[#222222] hover:text-[#0079FE] hover:underline"
|
||||
className="truncate hover:underline"
|
||||
title={app.url}
|
||||
>
|
||||
<span className="truncate">{app.url}</span>
|
||||
{app.url}
|
||||
</a>
|
||||
{app.appType === "iframe" && app.mountLocation && (
|
||||
<div className="flex items-center space-x-1 text-[11px] text-[#A6A6A6]">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-[#0079FE]" />
|
||||
<span>挂载: {mountLabel(app.mountLocation)}</span>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleCopyUrl(app.url)}
|
||||
className="shrink-0 rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
|
||||
title="复制网址"
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-4 py-[18px]">
|
||||
{app.appType === "iframe" && app.mountLocation ? (
|
||||
<span className="inline-flex max-w-full items-center space-x-1.5 rounded bg-sky-50 px-2 py-0.5 text-[11px] font-medium text-sky-700">
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-sky-500" />
|
||||
<span className="truncate">{mountLabel(app.mountLocation)}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[12px] text-neutral-300">—</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
<td className="relative px-4 py-[18px]">
|
||||
<div className="flex items-center select-none">
|
||||
<div className="flex select-none items-center">
|
||||
{app.routeParams.length > 0 ? (
|
||||
<button
|
||||
onClick={() =>
|
||||
@@ -290,15 +357,17 @@ export default function LightAppManagementPage() {
|
||||
activeParamPopover === app.appId ? null : app.appId
|
||||
)
|
||||
}
|
||||
className="group/param inline-flex cursor-pointer items-center space-x-1 text-[14px] text-[#222222] transition-colors hover:text-[#0079FE]"
|
||||
className="inline-flex cursor-pointer items-center space-x-1.5 rounded border border-neutral-200/60 bg-neutral-50/60 px-2.5 py-1 text-xs font-medium text-neutral-500 transition-colors hover:bg-neutral-100"
|
||||
>
|
||||
<span className="font-medium">{app.routeParams.length}个</span>
|
||||
<span className="text-[10px] text-[#A6A6A6] transition-colors group-hover/param:text-[#0079FE]">
|
||||
▼
|
||||
</span>
|
||||
<span>{app.routeParams.length}个</span>
|
||||
{activeParamPopover === app.appId ? (
|
||||
<ChevronUp className="h-3.5 w-3.5 text-neutral-400" />
|
||||
) : (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-neutral-400" />
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span className="text-[14px] text-[#A6A6A6]">0个</span>
|
||||
<span className="text-xs text-neutral-400">0个</span>
|
||||
)}
|
||||
|
||||
{activeParamPopover === app.appId && app.routeParams.length > 0 && (
|
||||
@@ -307,23 +376,30 @@ export default function LightAppManagementPage() {
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setActiveParamPopover(null)}
|
||||
/>
|
||||
<div className="absolute left-4 top-11 z-50 mt-1 w-[280px] animate-in fade-in rounded-[4px] border border-[#E8E8E8] bg-white p-3 text-[12px] shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="mb-2 flex items-center justify-between border-b border-[#E8E8E8] pb-2 font-semibold text-[#3D3D3D]">
|
||||
<span>路由参数明细</span>
|
||||
<span className="rounded-sm bg-[#EAF4FF] px-1.5 py-0.5 text-[10px] text-[#0079FE]">
|
||||
URL 拼接
|
||||
<div className="absolute left-4 top-12 z-50 w-[280px] animate-in fade-in rounded-xl border border-neutral-100 bg-white p-3.5 text-[12px] shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="mb-2.5 flex items-center justify-between border-b border-neutral-100 pb-2">
|
||||
<span className="text-[11px] font-bold uppercase tracking-wider text-neutral-400">
|
||||
注入的路由参数
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setActiveParamPopover(null)}
|
||||
className="text-[10px] font-semibold text-rose-700 hover:underline"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
<div className="max-h-[160px] space-y-1.5 overflow-y-auto">
|
||||
<div className="max-h-40 space-y-1.5 overflow-y-auto">
|
||||
{app.routeParams.map((p, idx) => (
|
||||
<div
|
||||
key={`${app.appId}-${idx}`}
|
||||
className="flex items-center justify-between rounded-sm bg-[#FAFAFA] p-1.5 font-mono hover:bg-[#FEF6F6]"
|
||||
className="rounded-lg border border-neutral-100 bg-neutral-50/60 p-2 text-[11px] leading-relaxed"
|
||||
>
|
||||
<span className="font-bold text-[#F5222D]">{p.key}</span>
|
||||
<span className="text-[11px] text-[#A6A6A6]">
|
||||
({getParamSourceLabel(p)})
|
||||
</span>
|
||||
<div className="flex items-center justify-between gap-1 font-mono">
|
||||
<span className="truncate font-bold text-rose-700">{p.key}</span>
|
||||
<span className="truncate text-[10px] text-neutral-500">
|
||||
{getParamSourceLabel(p)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -337,36 +413,36 @@ export default function LightAppManagementPage() {
|
||||
<div className="flex select-none items-center space-x-2">
|
||||
{app.enabled ? (
|
||||
<>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#2FD69A] opacity-75" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-[#2FD69A]" />
|
||||
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
|
||||
</span>
|
||||
<span className="text-[13px] font-semibold text-[#189D72]">已启用</span>
|
||||
<span className="text-[12.5px] font-bold text-emerald-700">已启用</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="h-2 w-2 rounded-full bg-[#A6A6A6]" />
|
||||
<span className="text-[13px] text-[#A6A6A6]">已禁用</span>
|
||||
<span className="h-2.5 w-2.5 shrink-0 rounded-full bg-neutral-300" />
|
||||
<span className="text-[12.5px] font-medium text-neutral-400">已停用</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-4 py-[18px] pr-6 text-right">
|
||||
<div className="inline-flex items-center justify-end space-x-2.5">
|
||||
<td className="px-4 py-[18px] pr-6 text-center">
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
<button
|
||||
onClick={() => openEdit(app)}
|
||||
className="group/btn flex h-[34px] w-[34px] cursor-pointer items-center justify-center rounded-[4px] border border-[#D4E9FF] bg-[#EAF4FF] text-[#0079FE] transition-all duration-200 hover:scale-105 hover:border-[#0079FE] hover:bg-[#0079FE] hover:text-white active:scale-95"
|
||||
className="cursor-pointer rounded-lg p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
|
||||
title="编辑配置"
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5 transition-transform duration-200 group-hover/btn:rotate-12" />
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(app)}
|
||||
className="group/btn flex h-[34px] w-[34px] cursor-pointer items-center justify-center rounded-[4px] border border-[#FDDADC] bg-[#FEEDED] text-[#F5222D] transition-all duration-200 hover:scale-105 hover:border-[#F5222D] hover:bg-[#F5222D] hover:text-white active:scale-95"
|
||||
className="cursor-pointer rounded-lg p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
title="删除配置"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 transition-transform duration-200 group-hover/btn:scale-110" />
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -375,17 +451,17 @@ export default function LightAppManagementPage() {
|
||||
|
||||
{tableApps.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={6} className="py-12 text-center">
|
||||
<td colSpan={7} className="py-12 text-center">
|
||||
<div className="flex flex-col items-center justify-center space-y-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[#F5F5F5] text-[#C1C1C1]">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
|
||||
<Info className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-[14px] font-semibold text-[#A6A6A6]">
|
||||
<p className="text-[14px] font-semibold text-neutral-400">
|
||||
{isLoading ? "加载中..." : "暂无匹配的组件应用"}
|
||||
</p>
|
||||
{!isLoading && (
|
||||
<p className="text-[12px] text-[#C1C1C1]">
|
||||
<p className="text-[12px] text-neutral-300">
|
||||
{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}
|
||||
</p>
|
||||
)}
|
||||
@@ -396,25 +472,152 @@ export default function LightAppManagementPage() {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : tableApps.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-neutral-200 bg-white py-20 text-center">
|
||||
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
|
||||
<Info className="h-6 w-6" />
|
||||
</div>
|
||||
<p className="text-[14px] font-semibold text-neutral-400">
|
||||
{isLoading ? "加载中..." : "暂无匹配的组件应用"}
|
||||
</p>
|
||||
{!isLoading && (
|
||||
<p className="mt-1 text-[12px] text-neutral-300">
|
||||
{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3">
|
||||
{tableApps.map((app) => (
|
||||
<div
|
||||
key={app.appId}
|
||||
className="group flex flex-col justify-between overflow-hidden rounded-xl border border-neutral-100 bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)] transition-all hover:border-[#F5222D]/20 hover:shadow-md"
|
||||
>
|
||||
<div
|
||||
className={`h-1.5 w-full bg-gradient-to-r ${
|
||||
app.appType === "window_open"
|
||||
? "from-rose-600 to-amber-500"
|
||||
: "from-indigo-600 to-sky-500"
|
||||
}`}
|
||||
/>
|
||||
<div className="flex-1 space-y-3.5 p-5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1 space-y-1.5">
|
||||
{app.appType === "window_open" ? (
|
||||
<span className="inline-flex items-center space-x-1 rounded border border-rose-100 bg-rose-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-rose-700">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
<span>新页跳转</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center space-x-1 rounded border border-sky-100 bg-sky-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-sky-700">
|
||||
<Monitor className="h-3 w-3" />
|
||||
<span>iframe 嵌套</span>
|
||||
</span>
|
||||
)}
|
||||
<h4 className="line-clamp-1 text-sm font-bold leading-snug text-neutral-800 transition-colors group-hover:text-[#F5222D]">
|
||||
{app.name}
|
||||
</h4>
|
||||
</div>
|
||||
{app.enabled ? (
|
||||
<span className="inline-flex shrink-0 items-center space-x-1 text-[11px] font-bold text-emerald-700">
|
||||
<span className="h-2 w-2 rounded-full bg-emerald-500" />
|
||||
<span>已启用</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex shrink-0 items-center space-x-1 text-[11px] font-medium text-neutral-400">
|
||||
<span className="h-2 w-2 rounded-full bg-neutral-300" />
|
||||
<span>已停用</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="line-clamp-2 min-h-[2.25rem] text-xs leading-relaxed text-neutral-500">
|
||||
{app.description || "暂无应用详细描述说明。"}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-1.5 rounded-lg border border-neutral-200/40 bg-neutral-50/60 p-2 font-mono text-[11px] text-neutral-600">
|
||||
<Globe className="h-3.5 w-3.5 shrink-0 text-neutral-400" />
|
||||
<span className="flex-1 truncate" title={app.url}>
|
||||
{app.url}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleCopyUrl(app.url)}
|
||||
className="shrink-0 rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-200 hover:text-neutral-600"
|
||||
title="复制网址"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{app.routeParams.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{app.routeParams.map((p, idx) => (
|
||||
<span
|
||||
key={`${app.appId}-card-${idx}`}
|
||||
className="rounded border border-neutral-200/40 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] text-neutral-500"
|
||||
>
|
||||
{p.key}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{app.appType === "iframe" && app.mountLocation && (
|
||||
<div className="inline-flex items-center gap-1.5 rounded-lg border border-sky-100/60 bg-sky-50/60 p-2 text-[11px] font-medium leading-none text-sky-700">
|
||||
<span>挂载分类:</span>
|
||||
<code className="font-semibold">{mountLabel(app.mountLocation)}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end space-x-1 border-t border-neutral-100/60 bg-neutral-50/30 px-5 py-3">
|
||||
<button
|
||||
onClick={() => openEdit(app)}
|
||||
className="cursor-pointer rounded-lg p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
|
||||
title="编辑配置"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(app)}
|
||||
className="cursor-pointer rounded-lg p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
title="删除配置"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex max-w-full select-none items-start space-x-3.5 rounded-[4px] border border-[#FFBB55] bg-[#FFF6EA] p-4 text-[13px] text-[#575757]">
|
||||
<Info className="mt-0.5 h-5 w-5 flex-shrink-0 text-[#FF9900]" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-bold text-[#222222]">📌 智能多维挂载规则:</p>
|
||||
<ul className="list-inside list-disc space-y-1 pl-1">
|
||||
<li>
|
||||
当您注册 <strong>iframe 嵌套</strong>{" "}
|
||||
应用时,您需要在菜单一栏指定分类(如“资源管理”),组件将直接作为新导航菜单项加入该分类。
|
||||
{viewMode === "table" && (
|
||||
<div className="select-none rounded-xl border border-amber-200/40 bg-amber-50/40 p-5 shadow-[0_1px_2px_rgba(0,0,0,0.005)]">
|
||||
<div className="flex items-center space-x-2 border-b border-amber-100/50 pb-3 text-amber-800">
|
||||
<AlertCircle className="h-4 w-4 shrink-0 text-amber-600" />
|
||||
<h4 className="text-[13px] font-bold tracking-tight">智能多维挂载规则:</h4>
|
||||
</div>
|
||||
<ul className="mt-3.5 space-y-3 pl-1 text-[12px] leading-relaxed text-neutral-600">
|
||||
<li className="flex items-start space-x-2">
|
||||
<span className="mt-1 shrink-0 text-amber-600">•</span>
|
||||
<span>
|
||||
当您注册 <strong className="font-bold text-neutral-800">iframe 嵌套</strong>{" "}
|
||||
应用时,您需要在菜单一栏指定分类(如“资源管理”),组件将直接作为新导航菜单项加入该分类。
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
当路由挂载成功,点击左侧注入的子栏名称可在主区域调起{" "}
|
||||
<strong>真实 Iframe 预览器</strong>
|
||||
,并自动将参数以加密/标准的QueryString传递过去。
|
||||
<li className="flex items-start space-x-2">
|
||||
<span className="mt-1 shrink-0 text-amber-600">•</span>
|
||||
<span>
|
||||
当路由挂载成功,点击左侧注入的子栏名称可在主区域调起{" "}
|
||||
<strong className="font-bold text-neutral-800">真实 Iframe 预览器</strong>
|
||||
,并自动将参数以加密/标准的QueryString传递过去。
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{dialogOpen && (
|
||||
@@ -426,9 +629,14 @@ export default function LightAppManagementPage() {
|
||||
|
||||
<div className="relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[580px] flex-col rounded-[4px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
|
||||
<h2 className="text-[17px] font-bold text-[#222222]">
|
||||
{editing ? "修改轻应用" : "注册应用"}
|
||||
</h2>
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<span className="rounded-lg bg-rose-50 p-1.5 text-[#A8071A]">
|
||||
<Layers className="h-5 w-5" />
|
||||
</span>
|
||||
<h2 className="text-[17px] font-bold text-[#222222]">
|
||||
{editing ? "修改轻应用" : "注册应用"}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setDialogOpen(false)}
|
||||
className="cursor-pointer rounded-sm p-1 text-[#A6A6A6] transition-colors hover:bg-[#FAFAFA] hover:text-[#F5222D]"
|
||||
@@ -439,9 +647,10 @@ export default function LightAppManagementPage() {
|
||||
|
||||
<div className="flex-1 space-y-4 overflow-y-auto py-4 pr-1 text-[#222222]">
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center text-[14px] font-medium">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<Layers className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用类型</span>
|
||||
<span className="ml-1 text-[#F5222D]">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={form.appType}
|
||||
@@ -451,12 +660,18 @@ export default function LightAppManagementPage() {
|
||||
<option value="iframe">iframe 嵌套 (挂载到菜单)</option>
|
||||
<option value="window_open">window.open (新页跳转)</option>
|
||||
</select>
|
||||
<p className="text-[11.5px] leading-relaxed text-[#A6A6A6]">
|
||||
{form.appType === "window_open"
|
||||
? "在新标签页或卡片式路由容器打开。多用于外部高度复杂的系统跳转。"
|
||||
: "通过标准 iframe 嵌入页面,可直接挂载到左侧导航树相应分类内。"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center text-[14px] font-medium">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<Tag className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用名称</span>
|
||||
<span className="ml-1 text-[#F5222D]">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={form.name}
|
||||
@@ -467,9 +682,10 @@ export default function LightAppManagementPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center text-[14px] font-medium">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<Link2 className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用地址</span>
|
||||
<span className="ml-1 text-[#F5222D]">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={form.url}
|
||||
@@ -481,9 +697,10 @@ export default function LightAppManagementPage() {
|
||||
|
||||
{form.appType === "iframe" && (
|
||||
<div className="animate-in fade-in space-y-1.5">
|
||||
<label className="flex items-center text-[14px] font-medium">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<FolderTree className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>挂载菜单</span>
|
||||
<span className="ml-1 text-[#F5222D]">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={form.mountLocation}
|
||||
@@ -501,7 +718,11 @@ export default function LightAppManagementPage() {
|
||||
)}
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[14px] font-medium">应用描述 (可选)</label>
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="w-2 shrink-0" />
|
||||
<AlignLeft className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用描述 (可选)</span>
|
||||
</label>
|
||||
<Textarea
|
||||
value={form.description}
|
||||
onChange={(e) => patchForm({ description: e.target.value })}
|
||||
@@ -513,10 +734,11 @@ export default function LightAppManagementPage() {
|
||||
|
||||
<div className="space-y-2.5 pt-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="flex items-center text-[14px] font-medium">
|
||||
<span className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<Sliders className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>路由参数</span>
|
||||
<span
|
||||
className="ml-1 inline-flex"
|
||||
className="inline-flex"
|
||||
title="参数会在加载此应用时作为 query 参数拼接在 URL 的尾部"
|
||||
>
|
||||
<HelpCircle className="h-3.5 w-3.5 text-[#A6A6A6]" />
|
||||
|
||||
Reference in new issue
Block a user