mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 10:42:45 +08:00
New warehouse
This commit is contained in:
11
web/assets/logo.svg
Normal file
11
web/assets/logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#2563eb"/>
|
||||
<stop offset="100%" stop-color="#60a5fa"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="8" y="8" width="112" height="112" rx="20" fill="url(#g)"/>
|
||||
<path d="M64 28 L86 64 L64 100 L42 64 Z" fill="#fff" opacity="0.95"/>
|
||||
<circle cx="64" cy="64" r="10" fill="#2563eb"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 491 B |
355
web/assets/themes.json
Normal file
355
web/assets/themes.json
Normal file
@@ -0,0 +1,355 @@
|
||||
{
|
||||
"Default": {
|
||||
"--color-white": "#FFFFFF",
|
||||
"--color-black": "#000000",
|
||||
"--lay-color-white": "#FAFAFA",
|
||||
"--lay-color-black": "#333333",
|
||||
"--lay-color-red-1": "#FFF1E8",
|
||||
"--lay-color-red-2": "#FFD7C0",
|
||||
"--lay-color-red-3": "#FFBB99",
|
||||
"--lay-color-red-4": "#FF9C71",
|
||||
"--lay-color-red-5": "#FF7A4A",
|
||||
"--lay-color-red-6": "#FF5722",
|
||||
"--lay-color-red-7": "#D23B15",
|
||||
"--lay-color-red-8": "#A6250B",
|
||||
"--lay-color-red-9": "#791404",
|
||||
"--lay-color-red-10": "#4D0800",
|
||||
"--lay-color-blue-1": "#E8F9FF",
|
||||
"--lay-color-blue-2": "#C0ECFF",
|
||||
"--lay-color-blue-3": "#97DCFF",
|
||||
"--lay-color-blue-4": "#6FCAFF",
|
||||
"--lay-color-blue-5": "#46B5FF",
|
||||
"--lay-color-blue-6": "#1E9FFF",
|
||||
"--lay-color-blue-7": "#1379D2",
|
||||
"--lay-color-blue-8": "#0A58A6",
|
||||
"--lay-color-blue-9": "#043A79",
|
||||
"--lay-color-blue-10": "#00214D",
|
||||
"--lay-color-lightblue-1": "#E8FDFF",
|
||||
"--lay-color-lightblue-2": "#C1F4FB",
|
||||
"--lay-color-lightblue-3": "#9CEAF7",
|
||||
"--lay-color-lightblue-4": "#77DDF4",
|
||||
"--lay-color-lightblue-5": "#53CEF0",
|
||||
"--lay-color-lightblue-6": "#31BDEC",
|
||||
"--lay-color-lightblue-7": "#1F95C4",
|
||||
"--lay-color-lightblue-8": "#10709C",
|
||||
"--lay-color-lightblue-9": "#064E74",
|
||||
"--lay-color-lightblue-10": "#002F4D",
|
||||
"--lay-color-layuigreen-1": "#E8FFF9",
|
||||
"--lay-color-layuigreen-2": "#B5F1E3",
|
||||
"--lay-color-layuigreen-3": "#87E3D1",
|
||||
"--lay-color-layuigreen-4": "#5DD6C1",
|
||||
"--lay-color-layuigreen-5": "#37C8B5",
|
||||
"--lay-color-layuigreen-6": "#16BAAA",
|
||||
"--lay-color-layuigreen-7": "#0E9F95",
|
||||
"--lay-color-layuigreen-8": "#08837F",
|
||||
"--lay-color-layuigreen-9": "#036868",
|
||||
"--lay-color-layuigreen-10": "#004A4D",
|
||||
"--lay-color-green-1": "#E8FFF2",
|
||||
"--lay-color-green-2": "#B5F1D1",
|
||||
"--lay-color-green-3": "#86E2B4",
|
||||
"--lay-color-green-4": "#5CD49C",
|
||||
"--lay-color-green-5": "#37C588",
|
||||
"--lay-color-green-6": "#16B777",
|
||||
"--lay-color-green-7": "#0E9C68",
|
||||
"--lay-color-green-8": "#088259",
|
||||
"--lay-color-green-9": "#036749",
|
||||
"--lay-color-green-10": "#004D38",
|
||||
"--lay-color-orange-1": "#FFFCE8",
|
||||
"--lay-color-orange-2": "#FFF5BA",
|
||||
"--lay-color-orange-3": "#FFEA8B",
|
||||
"--lay-color-orange-4": "#FFDC5D",
|
||||
"--lay-color-orange-5": "#FFCB2E",
|
||||
"--lay-color-orange-6": "#FFB800",
|
||||
"--lay-color-orange-7": "#D29000",
|
||||
"--lay-color-orange-8": "#A66C00",
|
||||
"--lay-color-orange-9": "#794B00",
|
||||
"--lay-color-orange-10": "#4D2D00",
|
||||
"--lay-color-cyan-1": "#E8F6FF",
|
||||
"--lay-color-cyan-2": "#B9CEDD",
|
||||
"--lay-color-cyan-3": "#8FA7BB",
|
||||
"--lay-color-cyan-4": "#6A829A",
|
||||
"--lay-color-cyan-5": "#4A5F78",
|
||||
"--lay-color-cyan-6": "#2F4056",
|
||||
"--lay-color-cyan-7": "#223654",
|
||||
"--lay-color-cyan-8": "#162C51",
|
||||
"--lay-color-cyan-9": "#0B214F",
|
||||
"--lay-color-cyan-10": "#00174D",
|
||||
"--lay-color-purple-1": "#FDE8FF",
|
||||
"--lay-color-purple-2": "#EDBEF4",
|
||||
"--lay-color-purple-3": "#DC97E8",
|
||||
"--lay-color-purple-4": "#C972DD",
|
||||
"--lay-color-purple-5": "#B651D1",
|
||||
"--lay-color-purple-6": "#A233C6",
|
||||
"--lay-color-purple-7": "#8120A8",
|
||||
"--lay-color-purple-8": "#631289",
|
||||
"--lay-color-purple-9": "#48076B",
|
||||
"--lay-color-purple-10": "#2F004D",
|
||||
"--lay-color-black-1": "#E8F8FF",
|
||||
"--lay-color-black-2": "#BFD0D8",
|
||||
"--lay-color-black-3": "#98A8B1",
|
||||
"--lay-color-black-4": "#73818A",
|
||||
"--lay-color-black-5": "#505B63",
|
||||
"--lay-color-black-6": "#2F363C",
|
||||
"--lay-color-black-7": "#23303C",
|
||||
"--lay-color-black-8": "#18293C",
|
||||
"--lay-color-black-9": "#0C213C",
|
||||
"--lay-color-black-10": "#00183C",
|
||||
"--lay-color-gray-1": "#FAFAFA",
|
||||
"--lay-color-gray-2": "#F6F6F6",
|
||||
"--lay-color-gray-3": "#EEEEEE",
|
||||
"--lay-color-gray-4": "#E2E2E2",
|
||||
"--lay-color-gray-5": "#DDDDDD",
|
||||
"--lay-color-gray-6": "#D2D2D2",
|
||||
"--lay-color-gray-7": "#CCCCCC",
|
||||
"--lay-color-gray-8": "#C2C2C2",
|
||||
"--lay-color-gray-9": "#AAAAAA",
|
||||
"--lay-color-gray-10": "#939393",
|
||||
"--lay-color-gray-11": "#858585",
|
||||
"--lay-color-gray-12": "#7b7b7b",
|
||||
"--lay-color-gray-13": "#686868",
|
||||
"--lay-color-primary": "var(--lay-color-layuigreen-6)",
|
||||
"--lay-color-primary-hover": "var(--lay-color-layuigreen-5)",
|
||||
"--lay-color-primary-active": "var(--lay-color-layuigreen-7)",
|
||||
"--lay-color-primary-disabled": "var(--lay-color-layuigreen-3)",
|
||||
"--lay-color-primary-light": "var(--lay-color-layuigreen-4)",
|
||||
"--lay-color-secondary": "var(--lay-color-green-6)",
|
||||
"--lay-color-secondary-hover": "var(--lay-color-green-5)",
|
||||
"--lay-color-secondary-active": "var(--lay-color-green-7)",
|
||||
"--lay-color-secondary-disabled": "var(--lay-color-green-3)",
|
||||
"--lay-color-secondary-light": "var(--lay-color-green-4)",
|
||||
"--lay-color-info": "var(--lay-color-lightblue-6)",
|
||||
"--lay-color-info-hover": "var(--lay-color-lightblue-5)",
|
||||
"--lay-color-info-active": "var(--lay-color-lightblue-7)",
|
||||
"--lay-color-info-disabled": "var(--lay-color-lightblue-3)",
|
||||
"--lay-color-info-light": "var(--lay-color-lightblue-4)",
|
||||
"--lay-color-normal": "var(--lay-color-blue-6)",
|
||||
"--lay-color-normal-hover": "var(--lay-color-blue-5)",
|
||||
"--lay-color-normal-active": "var(--lay-color-blue-7)",
|
||||
"--lay-color-normal-disabled": "var(--lay-color-blue-3)",
|
||||
"--lay-color-normal-light": "var(--lay-color-blue-4)",
|
||||
"--lay-color-warning": "var(--lay-color-orange-6)",
|
||||
"--lay-color-warning-hover": "var(--lay-color-orange-5)",
|
||||
"--lay-color-warning-active": "var(--lay-color-orange-7)",
|
||||
"--lay-color-warning-disabled": "var(--lay-color-orange-3)",
|
||||
"--lay-color-warning-light": "var(--lay-color-orange-4)",
|
||||
"--lay-color-success": "var(--lay-color-green-6)",
|
||||
"--lay-color-success-hover": "var(--lay-color-green-5)",
|
||||
"--lay-color-success-active": "var(--lay-color-green-7)",
|
||||
"--lay-color-success-disabled": "var(--lay-color-green-3)",
|
||||
"--lay-color-success-light": "var(--lay-color-green-4)",
|
||||
"--lay-color-danger": "var(--lay-color-red-6)",
|
||||
"--lay-color-danger-hover": "var(--lay-color-red-5)",
|
||||
"--lay-color-danger-active": "var(--lay-color-red-7)",
|
||||
"--lay-color-danger-disabled": "var(--lay-color-red-3)",
|
||||
"--lay-color-danger-light": "var(--lay-color-red-4)",
|
||||
"--lay-color-bg-1": "#17171A",
|
||||
"--lay-color-bg-2": "#232324",
|
||||
"--lay-color-bg-3": "#2a2a2b",
|
||||
"--lay-color-bg-4": "#313132",
|
||||
"--lay-color-bg-5": "#373739",
|
||||
"--lay-color-bg-white": "#f6f6f6",
|
||||
"--lay-color-text-1": "rgba(255,255,255,.9)",
|
||||
"--lay-color-text-2": "rgba(255,255,255,.7)",
|
||||
"--lay-color-text-3": "rgba(255,255,255,.5)",
|
||||
"--lay-color-text-4": "rgba(255,255,255,.3)",
|
||||
"--lay-color-border-1": "#2e2e30",
|
||||
"--lay-color-border-2": "#484849",
|
||||
"--lay-color-border-3": "#5f5f60",
|
||||
"--lay-color-border-4": "#929293",
|
||||
"--lay-color-fill-1": "rgba(255,255,255,.04)",
|
||||
"--lay-color-fill-2": "rgba(255,255,255,.08)",
|
||||
"--lay-color-fill-3": "rgba(255,255,255,.12)",
|
||||
"--lay-color-fill-4": "rgba(255,255,255,.16)",
|
||||
"--lay-color-hover": "var(--lay-color-fill-3)",
|
||||
"--lay-color-active": "var(--lay-color-fill-3)",
|
||||
"--lay-shadow-1": "0 4px 6px rgba(0, 0, 0, 6%), 0 1px 10px rgba(0, 0, 0, 8%), 0 2px 4px rgba(0, 0, 0, 12%)",
|
||||
"--lay-shadow-2": "0 8px 10px rgba(0, 0, 0, 12%), 0 3px 14px rgba(0, 0, 0, 10%), 0 5px 5px rgba(0, 0, 0, 16%)",
|
||||
"--lay-shadow-3": "0 16px 24px rgba(0, 0, 0, 14%), 0 6px 30px rgba(0, 0, 0, 12%), 0 8px 10px rgba(0, 0, 0, 20%)"
|
||||
},
|
||||
"ColorPaletteDark": {
|
||||
"--lay-color-red-1": "#4D0800",
|
||||
"--lay-color-red-2": "#791505",
|
||||
"--lay-color-red-3": "#A62A11",
|
||||
"--lay-color-red-4": "#D24622",
|
||||
"--lay-color-red-5": "#FF6839",
|
||||
"--lay-color-red-6": "#FF7948",
|
||||
"--lay-color-red-7": "#FF9C71",
|
||||
"--lay-color-red-8": "#FFBC9A",
|
||||
"--lay-color-red-9": "#FFD9C3",
|
||||
"--lay-color-red-10": "#FFF3EB",
|
||||
"--lay-color-blue-1": "#00214D",
|
||||
"--lay-color-blue-2": "#033A79",
|
||||
"--lay-color-blue-3": "#0F5AA6",
|
||||
"--lay-color-blue-4": "#1F7FD2",
|
||||
"--lay-color-blue-5": "#35A9FF",
|
||||
"--lay-color-blue-6": "#44B4FF",
|
||||
"--lay-color-blue-7": "#70CAFF",
|
||||
"--lay-color-blue-8": "#9BDDFF",
|
||||
"--lay-color-blue-9": "#C6EEFF",
|
||||
"--lay-color-blue-10": "#F2FCFF",
|
||||
"--lay-color-lightblue-1": "#002F4D",
|
||||
"--lay-color-lightblue-2": "#044D74",
|
||||
"--lay-color-lightblue-3": "#12719C",
|
||||
"--lay-color-lightblue-4": "#2797C4",
|
||||
"--lay-color-lightblue-5": "#42C1EC",
|
||||
"--lay-color-lightblue-6": "#56CFF0",
|
||||
"--lay-color-lightblue-7": "#79DDF4",
|
||||
"--lay-color-lightblue-8": "#9DEAF7",
|
||||
"--lay-color-lightblue-9": "#C3F4FB",
|
||||
"--lay-color-lightblue-10": "#EAFDFF",
|
||||
"--lay-color-layuigreen-1": "#004A4D",
|
||||
"--lay-color-layuigreen-2": "#046868",
|
||||
"--lay-color-layuigreen-3": "#0E837F",
|
||||
"--lay-color-layuigreen-4": "#1C9F96",
|
||||
"--lay-color-layuigreen-5": "#2EBAAC",
|
||||
"--lay-color-layuigreen-6": "#40C8B6",
|
||||
"--lay-color-layuigreen-7": "#64D6C2",
|
||||
"--lay-color-layuigreen-8": "#8CE3D2",
|
||||
"--lay-color-layuigreen-9": "#B9F1E4",
|
||||
"--lay-color-layuigreen-10": "#EAFFFA",
|
||||
"--lay-color-green-1": "#004D38",
|
||||
"--lay-color-green-2": "#046749",
|
||||
"--lay-color-green-3": "#0E825B",
|
||||
"--lay-color-green-4": "#1C9C6D",
|
||||
"--lay-color-green-5": "#2EB780",
|
||||
"--lay-color-green-6": "#3FC58B",
|
||||
"--lay-color-green-7": "#64D4A0",
|
||||
"--lay-color-green-8": "#8CE2B7",
|
||||
"--lay-color-green-9": "#BAF1D3",
|
||||
"--lay-color-green-10": "#EBFFF4",
|
||||
"--lay-color-orange-1": "#4D2D00",
|
||||
"--lay-color-orange-2": "#794C04",
|
||||
"--lay-color-orange-3": "#A66F0A",
|
||||
"--lay-color-orange-4": "#D29613",
|
||||
"--lay-color-orange-5": "#FFC11F",
|
||||
"--lay-color-orange-6": "#FFC926",
|
||||
"--lay-color-orange-7": "#FFDB57",
|
||||
"--lay-color-orange-8": "#FFE987",
|
||||
"--lay-color-orange-9": "#FFF5B8",
|
||||
"--lay-color-orange-10": "#FFFCE8",
|
||||
"--lay-color-cyan-1": "#00174D",
|
||||
"--lay-color-cyan-2": "#0B214F",
|
||||
"--lay-color-cyan-3": "#162C51",
|
||||
"--lay-color-cyan-4": "#233754",
|
||||
"--lay-color-cyan-5": "#304056",
|
||||
"--lay-color-cyan-6": "#546478",
|
||||
"--lay-color-cyan-7": "#75879A",
|
||||
"--lay-color-cyan-8": "#99ABBB",
|
||||
"--lay-color-cyan-9": "#C2D2DD",
|
||||
"--lay-color-cyan-10": "#EFF9FF",
|
||||
"--lay-color-purple-1": "#2F004D",
|
||||
"--lay-color-purple-2": "#47056B",
|
||||
"--lay-color-purple-3": "#631389",
|
||||
"--lay-color-purple-4": "#8326A8",
|
||||
"--lay-color-purple-5": "#A53FC6",
|
||||
"--lay-color-purple-6": "#B755D1",
|
||||
"--lay-color-purple-7": "#CA77DD",
|
||||
"--lay-color-purple-8": "#DD9BE8",
|
||||
"--lay-color-purple-9": "#EEC3F4",
|
||||
"--lay-color-purple-10": "#FDEDFF"
|
||||
},
|
||||
"ColorPaletteLight": {
|
||||
"--lay-color-red-1": "#FFF1E8",
|
||||
"--lay-color-red-2": "#FFD7C0",
|
||||
"--lay-color-red-3": "#FFBB99",
|
||||
"--lay-color-red-4": "#FF9C71",
|
||||
"--lay-color-red-5": "#FF7A4A",
|
||||
"--lay-color-red-6": "#FF5722",
|
||||
"--lay-color-red-7": "#D23B15",
|
||||
"--lay-color-red-8": "#A6250B",
|
||||
"--lay-color-red-9": "#791404",
|
||||
"--lay-color-red-10": "#4D0800",
|
||||
"--lay-color-blue-1": "#E8F9FF",
|
||||
"--lay-color-blue-2": "#C0ECFF",
|
||||
"--lay-color-blue-3": "#97DCFF",
|
||||
"--lay-color-blue-4": "#6FCAFF",
|
||||
"--lay-color-blue-5": "#46B5FF",
|
||||
"--lay-color-blue-6": "#1E9FFF",
|
||||
"--lay-color-blue-7": "#1379D2",
|
||||
"--lay-color-blue-8": "#0A58A6",
|
||||
"--lay-color-blue-9": "#043A79",
|
||||
"--lay-color-blue-10": "#00214D",
|
||||
"--lay-color-lightblue-1": "#E8FDFF",
|
||||
"--lay-color-lightblue-2": "#C1F4FB",
|
||||
"--lay-color-lightblue-3": "#9CEAF7",
|
||||
"--lay-color-lightblue-4": "#77DDF4",
|
||||
"--lay-color-lightblue-5": "#53CEF0",
|
||||
"--lay-color-lightblue-6": "#31BDEC",
|
||||
"--lay-color-lightblue-7": "#1F95C4",
|
||||
"--lay-color-lightblue-8": "#10709C",
|
||||
"--lay-color-lightblue-9": "#064E74",
|
||||
"--lay-color-lightblue-10": "#002F4D",
|
||||
"--lay-color-layuigreen-1": "#E8FFF9",
|
||||
"--lay-color-layuigreen-2": "#B5F1E3",
|
||||
"--lay-color-layuigreen-3": "#87E3D1",
|
||||
"--lay-color-layuigreen-4": "#5DD6C1",
|
||||
"--lay-color-layuigreen-5": "#37C8B5",
|
||||
"--lay-color-layuigreen-6": "#16BAAA",
|
||||
"--lay-color-layuigreen-7": "#0E9F95",
|
||||
"--lay-color-layuigreen-8": "#08837F",
|
||||
"--lay-color-layuigreen-9": "#036868",
|
||||
"--lay-color-layuigreen-10": "#004A4D",
|
||||
"--lay-color-green-1": "#E8FFF2",
|
||||
"--lay-color-green-2": "#B5F1D1",
|
||||
"--lay-color-green-3": "#86E2B4",
|
||||
"--lay-color-green-4": "#5CD49C",
|
||||
"--lay-color-green-5": "#37C588",
|
||||
"--lay-color-green-6": "#16B777",
|
||||
"--lay-color-green-7": "#0E9C68",
|
||||
"--lay-color-green-8": "#088259",
|
||||
"--lay-color-green-9": "#036749",
|
||||
"--lay-color-green-10": "#004D38",
|
||||
"--lay-color-orange-1": "#FFFCE8",
|
||||
"--lay-color-orange-2": "#FFF5BA",
|
||||
"--lay-color-orange-3": "#FFEA8B",
|
||||
"--lay-color-orange-4": "#FFDC5D",
|
||||
"--lay-color-orange-5": "#FFCB2E",
|
||||
"--lay-color-orange-6": "#FFB800",
|
||||
"--lay-color-orange-7": "#D29000",
|
||||
"--lay-color-orange-8": "#A66C00",
|
||||
"--lay-color-orange-9": "#794B00",
|
||||
"--lay-color-orange-10": "#4D2D00",
|
||||
"--lay-color-cyan-1": "#E8F6FF",
|
||||
"--lay-color-cyan-2": "#B9CEDD",
|
||||
"--lay-color-cyan-3": "#8FA7BB",
|
||||
"--lay-color-cyan-4": "#6A829A",
|
||||
"--lay-color-cyan-5": "#4A5F78",
|
||||
"--lay-color-cyan-6": "#2F4056",
|
||||
"--lay-color-cyan-7": "#223654",
|
||||
"--lay-color-cyan-8": "#162C51",
|
||||
"--lay-color-cyan-9": "#0B214F",
|
||||
"--lay-color-cyan-10": "#00174D",
|
||||
"--lay-color-purple-1": "#FDE8FF",
|
||||
"--lay-color-purple-2": "#EDBEF4",
|
||||
"--lay-color-purple-3": "#DC97E8",
|
||||
"--lay-color-purple-4": "#C972DD",
|
||||
"--lay-color-purple-5": "#B651D1",
|
||||
"--lay-color-purple-6": "#A233C6",
|
||||
"--lay-color-purple-7": "#8120A8",
|
||||
"--lay-color-purple-8": "#631289",
|
||||
"--lay-color-purple-9": "#48076B",
|
||||
"--lay-color-purple-10": "#2F004D"
|
||||
},
|
||||
"editable": {
|
||||
"--lay-color-bg-1": "#17171A",
|
||||
"--lay-color-bg-2": "#232324",
|
||||
"--lay-color-bg-3": "#2a2a2b",
|
||||
"--lay-color-bg-4": "#313132",
|
||||
"--lay-color-bg-5": "#373739",
|
||||
"--lay-color-bg-white": "#f6f6f6",
|
||||
"--lay-color-text-1": "rgba(255,255,255,.9)",
|
||||
"--lay-color-text-2": "rgba(255,255,255,.7)",
|
||||
"--lay-color-text-3": "rgba(255,255,255,.5)",
|
||||
"--lay-color-text-4": "rgba(255,255,255,.3)",
|
||||
"--lay-color-border-1": "#2e2e30",
|
||||
"--lay-color-border-2": "#484849",
|
||||
"--lay-color-border-3": "#5f5f60",
|
||||
"--lay-color-border-4": "#929293",
|
||||
"--lay-color-fill-1": "rgba(255,255,255,.04)",
|
||||
"--lay-color-fill-2": "rgba(255,255,255,.08)",
|
||||
"--lay-color-fill-3": "rgba(255,255,255,.12)",
|
||||
"--lay-color-fill-4": "rgba(255,255,255,.16)",
|
||||
"--lay-color-hover": "var(--lay-color-fill-3)",
|
||||
"--lay-color-active": "var(--lay-color-fill-3)"
|
||||
}
|
||||
}
|
||||
67
web/public.go
Normal file
67
web/public.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// TemplatesFS 嵌入模板的文件系统
|
||||
//
|
||||
//go:embed template/*.html template/admin/*.html
|
||||
var templatesFS embed.FS
|
||||
|
||||
// StaticFS 嵌入静态资源的文件系统(包含 CSS/JS 的 static 与 图片/字体等资源的 assets)
|
||||
//
|
||||
//go:embed static/* assets/*
|
||||
var staticFS embed.FS
|
||||
|
||||
// getDistRootFS 获取基于 server.dist 的本地文件系统
|
||||
// 当 server.dist 非空且路径存在时,返回对应的本地只读 FS;否则返回 nil
|
||||
func getDistRootFS() fs.FS {
|
||||
// 从配置中读取 server.dist
|
||||
distPath := viper.GetString("server.dist")
|
||||
if distPath == "" {
|
||||
return nil
|
||||
}
|
||||
// 归一化路径,兼容相对/绝对
|
||||
absPath := distPath
|
||||
if !filepath.IsAbs(distPath) {
|
||||
if p, err := filepath.Abs(distPath); err == nil {
|
||||
absPath = p
|
||||
}
|
||||
}
|
||||
// 检查目录是否存在
|
||||
if info, err := os.Stat(absPath); err == nil && info.IsDir() {
|
||||
return os.DirFS(absPath)
|
||||
}
|
||||
log.Printf("server.dist 路径无效或不可访问:%s,将回退使用嵌入资源", distPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTemplates 解析模板
|
||||
// 优先从 server.dist 指定目录加载(当配置非空且有效),否则回退到嵌入模板
|
||||
func ParseTemplates() (*template.Template, error) { // Go 顶级函数不支持箭头写法
|
||||
if distFS := getDistRootFS(); distFS != nil {
|
||||
// 期望 dist 目录下存在 template 与 template/admin 结构
|
||||
// 如:{dist}/template/*.html 与 {dist}/template/admin/*.html
|
||||
return template.ParseFS(distFS, "template/*.html", "template/admin/*.html")
|
||||
}
|
||||
// 默认:使用嵌入模板
|
||||
return template.ParseFS(templatesFS, "template/*.html", "template/admin/*.html")
|
||||
}
|
||||
|
||||
// GetStaticFS 返回静态资源文件系统(包含 static 与 assets 目录)
|
||||
// 优先使用 server.dist 指定的本地目录;否则回退到嵌入静态资源
|
||||
func GetStaticFS() (fs.FS, error) { // Go 顶级函数不支持箭头写法
|
||||
if distFS := getDistRootFS(); distFS != nil {
|
||||
// 直接返回以 dist 根为起点的 FS,routes 中会再基于此 FS Sub 出 static 与 assets
|
||||
return distFS, nil
|
||||
}
|
||||
return staticFS, nil
|
||||
}
|
||||
104
web/static/css/admin.css
Normal file
104
web/static/css/admin.css
Normal file
@@ -0,0 +1,104 @@
|
||||
wc-include{padding: 15px;display: block;}
|
||||
#app {display: none;}
|
||||
.layui-layout-right .layui-nav-bar {background-color: unset !important;}
|
||||
.layui-layout-admin .layui-side {top: 0 !important;z-index: 1001;}
|
||||
.layui-layout-admin .layui-logo {position: relative !important;height: 60px !important;top: -2px !important;}
|
||||
|
||||
/* Logo文字美化样式 */
|
||||
.logo-enhanced {
|
||||
font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif !important;
|
||||
font-size: 18px !important;
|
||||
font-weight: 600 !important;
|
||||
color: #ffffff !important;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.3) !important;
|
||||
letter-spacing: 1px !important;
|
||||
}
|
||||
|
||||
.layui-side,
|
||||
.layui-header,
|
||||
.layui-body,
|
||||
.layui-footer {transition: left 0.3s;}
|
||||
.collapse .layui-layout-admin .layui-side,
|
||||
.collapse .layui-layout-admin .layui-header {left: -200px;}
|
||||
.collapse .layui-layout-admin .layui-footer,
|
||||
.collapse .layui-layout-admin .layui-body {left: 0px;}
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {animation: none;mix-blend-mode: normal;}
|
||||
::view-transition-old(root) {z-index: 9999;}
|
||||
::view-transition-new(root) {z-index: 1;}
|
||||
.dark::view-transition-old(root) {z-index: 1;}
|
||||
.dark::view-transition-new(root) {z-index: 9999;}
|
||||
|
||||
/* 以下为自定义样式 */
|
||||
.system-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-bottom: 20px; }
|
||||
.system-info-item { padding: 16px; border-radius: 8px; background: var(--card); border: 1px solid var(--border); }
|
||||
.system-info-label { font-size: 14px; color: var(--muted); margin-bottom: 8px; }
|
||||
.system-info-value { font-size: 16px; font-weight: 600; color: var(--fg); }
|
||||
|
||||
/* ===================== 滚动条美化与布局约束(右侧滑块条) ===================== */
|
||||
/*
|
||||
作用:
|
||||
1. 统一 Admin 布局下内容区(.layui-body)为局部滚动容器,只在头部与页脚之间滚动
|
||||
2. 美化 .layui-body 的滚动条样式,增强可用性与观感
|
||||
3. 不影响登录页等非 Admin 布局页面(仅在 .layui-layout-admin 作用域内生效)
|
||||
*/
|
||||
:root {
|
||||
/* 头部与页脚的高度变量,便于后续维护/调整 */
|
||||
--admin-header-h: 60px;
|
||||
--admin-footer-h: 0px; /* 当前页脚未启用,如启用可改为 44px 等 */
|
||||
}
|
||||
|
||||
/* Admin 主容器占满视口,高度锁定,避免出现浏览器右侧全局滚动条 */
|
||||
.layui-layout-admin {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 头部/页脚高度同步到变量,确保与内容区上下边界垂直齐平 */
|
||||
.layui-layout-admin .layui-header {
|
||||
height: var(--admin-header-h);
|
||||
line-height: var(--admin-header-h);
|
||||
}
|
||||
.layui-layout-admin .layui-footer {
|
||||
height: var(--admin-footer-h);
|
||||
line-height: var(--admin-footer-h);
|
||||
}
|
||||
|
||||
/* 内容区设为局部滚动容器,顶部/底部与头部/页脚精确对齐 */
|
||||
.layui-layout-admin .layui-body {
|
||||
/* 仅约束垂直方向,左右定位保持与 Layui 默认一致,兼容现有折叠动画 */
|
||||
top: var(--admin-header-h) !important;
|
||||
bottom: var(--admin-footer-h) !important;
|
||||
overflow: auto;
|
||||
|
||||
/* Firefox 滚动条样式(细滚动条+自定义颜色) */
|
||||
scrollbar-width: thin; /* 细滚动条 */
|
||||
scrollbar-color: var(--lay-color-secondary) var(--lay-color-bg-3); /* thumb 与 track 颜色 */
|
||||
}
|
||||
|
||||
/* WebKit 滚动条样式(Chrome/Edge/Safari) */
|
||||
.layui-layout-admin .layui-body::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
.layui-layout-admin .layui-body::-webkit-scrollbar-track {
|
||||
background: var(--lay-color-bg-2);
|
||||
border-left: 1px solid var(--lay-color-border-2);
|
||||
}
|
||||
.layui-layout-admin .layui-body::-webkit-scrollbar-thumb {
|
||||
/* 渐变+内边透明边框,获得圆润质感 */
|
||||
background: linear-gradient(180deg, var(--lay-color-gray-7), var(--lay-color-gray-9));
|
||||
border-radius: 8px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.layui-layout-admin .layui-body::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--lay-color-secondary); /* 悬停高亮,强化可交互性 */
|
||||
}
|
||||
.layui-layout-admin .layui-body::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* ===================== END 滚动条美化与布局约束 ===================== */
|
||||
178
web/static/css/home.css
Normal file
178
web/static/css/home.css
Normal file
@@ -0,0 +1,178 @@
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
.card-container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.header h1 {
|
||||
color: #333;
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
}
|
||||
.header p {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.progress-container {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
.progress-steps::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
height: 2px;
|
||||
background: #e6e6e6;
|
||||
z-index: 1;
|
||||
}
|
||||
.progress-line {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
height: 2px;
|
||||
background: #5FB878;
|
||||
transition: width 0.5s ease;
|
||||
z-index: 2;
|
||||
width: 0%;
|
||||
}
|
||||
.step {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
.step-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: #e6e6e6;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 10px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.step.active .step-circle {
|
||||
background: #5FB878;
|
||||
color: white;
|
||||
}
|
||||
.step.completed .step-circle {
|
||||
background: #5FB878;
|
||||
color: white;
|
||||
}
|
||||
.step-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.step.active .step-text {
|
||||
color: #5FB878;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-item label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
.layui-input {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 12px 15px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
.layui-input:focus {
|
||||
border-color: #5FB878;
|
||||
box-shadow: 0 0 0 2px rgba(95, 184, 120, 0.2);
|
||||
}
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
background: linear-gradient(45deg, #5FB878, #42B983);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.submit-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(95, 184, 120, 0.4);
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #5FB878;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 修复 select 下拉在某些浏览器中文字垂直被裁剪问题 */
|
||||
select.layui-input,
|
||||
select.layui-select {
|
||||
/* 统一高度,避免被 padding 挤压导致文字显示不全 */
|
||||
height: 40px;
|
||||
line-height: 40px; /* 对多数浏览器有效,确保文字垂直居中 */
|
||||
padding: 0 15px; /* 与 .layui-input 保持一致的水平内边距 */
|
||||
box-sizing: border-box; /* 使高度计算更可控,不受 padding 影响 */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 兼容性优化:在部分内核下 select 需要明确字体大小与行高匹配 */
|
||||
select.layui-input option,
|
||||
select.layui-select option {
|
||||
line-height: 40px;
|
||||
}
|
||||
330
web/static/js/admin.js
Executable file
330
web/static/js/admin.js
Executable file
@@ -0,0 +1,330 @@
|
||||
const VERSION = '2.10.1';
|
||||
const layuicss = `https://unpkg.com/layui@${VERSION}/dist/css/layui.css`;
|
||||
const layuijs = `https://unpkg.com/layui@${VERSION}/dist/layui.js`;
|
||||
const rootPath = (function (src) {
|
||||
src = (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') ? document.currentScript.src : document.scripts[document.scripts.length - 1].src;
|
||||
return src.substring(0, src.lastIndexOf('/') + 1);
|
||||
})();
|
||||
|
||||
const app = document.querySelector('#app')
|
||||
|
||||
addLink({ href: layuicss }).then(() => {
|
||||
app.style.display = 'block';
|
||||
});
|
||||
|
||||
addLink({ id: 'layui_theme_css', href: `./static/src/layui-theme-dark-selector.css` });
|
||||
|
||||
// TODO 弃用,下个版本只支持选择器模式
|
||||
//addLink({ id: 'layui_theme_css', href: `${rootPath}dist/layui-theme-dark.css` });
|
||||
|
||||
loadScript(layuijs, function () {
|
||||
layui
|
||||
.config({
|
||||
base: './static/lib/',
|
||||
})
|
||||
.extend({
|
||||
drawer: 'drawer/drawer',
|
||||
});
|
||||
layui.use(['drawer', 'colorMode'], function () {
|
||||
const { $, element, form, layer, util, dropdown, drawer, colorMode } = layui;
|
||||
|
||||
const APPERANCE_KEY = 'layui-theme-demo-prefer-dark';
|
||||
|
||||
const theme = colorMode.init({
|
||||
selector: 'html',
|
||||
attribute: 'class',
|
||||
initialValue: 'dark',
|
||||
modes: {
|
||||
light: '',
|
||||
dark: 'dark',
|
||||
},
|
||||
storageKey: APPERANCE_KEY,
|
||||
onChanged(mode, defaultHandler) {
|
||||
const isAppearanceTransition = document.startViewTransition && !window.matchMedia(`(prefers-reduced-motion: reduce)`).matches;
|
||||
const isDark = mode === 'dark';
|
||||
|
||||
$('#change-theme').attr('class', `layui-icon layui-icon-${isDark ? 'moon' : 'light'}`);
|
||||
|
||||
if (!isAppearanceTransition) {
|
||||
defaultHandler();
|
||||
} else {
|
||||
rippleViewTransition(isDark, function () {
|
||||
defaultHandler();
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
routerTo({path: location.hash.slice(1) || 'dashboard'});
|
||||
|
||||
dropdown.render({
|
||||
elem: '#change-theme',
|
||||
align: 'center',
|
||||
data: [
|
||||
{
|
||||
title: '深色模式',
|
||||
id: 'dark',
|
||||
icon: 'layui-icon-moon',
|
||||
},
|
||||
{
|
||||
title: '浅色模式',
|
||||
id: 'light',
|
||||
icon: 'layui-icon-light',
|
||||
},
|
||||
{
|
||||
title: '跟随系统',
|
||||
id: 'auto',
|
||||
icon: 'layui-icon-console',
|
||||
},
|
||||
],
|
||||
templet(d) {
|
||||
return `
|
||||
<span style="display: flex;">
|
||||
<i class="layui-icon ${d.icon}" style="margin-right: 8px"></i>
|
||||
${d.title}
|
||||
</span>`.trim();
|
||||
},
|
||||
click(obj) {
|
||||
const { id: mode } = obj;
|
||||
theme.setMode(mode);
|
||||
},
|
||||
});
|
||||
|
||||
util.event('lay-header-event', {
|
||||
menuLeft() {
|
||||
$('body').toggleClass('collapse');
|
||||
},
|
||||
menuRight() {
|
||||
drawer.open({
|
||||
area: '600px',
|
||||
url: './static/tpl/theme.html',
|
||||
hideOnClose: true,
|
||||
id: 'drawer-theme-tpl',
|
||||
shade: 0.01,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
element.on('nav(nav-side)', function (elem) {
|
||||
var path = elem.data('path');
|
||||
if (path) {
|
||||
routerTo({path});
|
||||
if ($(window).width() <= 768) {
|
||||
$('body').toggleClass('collapse', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#layuiv').text(layui.v);
|
||||
|
||||
/*
|
||||
* 后台通用脚本
|
||||
* 说明:统一处理全局的退出登录逻辑,遵循后端 jsonResponse 的返回格式:
|
||||
* code: 0 表示成功,非0表示失败
|
||||
* msg: 提示信息
|
||||
* data: 业务数据
|
||||
*/
|
||||
|
||||
// 绑定退出登录按钮事件(箭头函数写法)
|
||||
const bindLogout = () => {
|
||||
const btn = document.getElementById('logout-btn');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
handleLogout();
|
||||
});
|
||||
};
|
||||
|
||||
// 执行退出登录(箭头函数写法)
|
||||
// 功能:弹出确认框 -> 显示加载层 -> 调用 /admin/logout -> 依据 code===0 判断
|
||||
const handleLogout = () => {
|
||||
layer.confirm('确定要退出登录吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
|
||||
// 显示加载层
|
||||
const loadIndex = layer.load(2, {
|
||||
content: '正在退出登录...'
|
||||
});
|
||||
|
||||
// 调用登出接口
|
||||
fetch('/admin/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
const ok = data && data.code === 0;
|
||||
const msg = (data && (data.msg || data.message)) || (ok ? '退出登录成功' : '退出登录失败');
|
||||
if (ok) {
|
||||
layer.msg(msg, {
|
||||
icon: 1,
|
||||
time: 1000
|
||||
}, () => {
|
||||
// 跳转到登录页或后端返回的地址
|
||||
const redirect = (data && data.data && data.data.redirect) || '/admin/login';
|
||||
window.location.href = redirect;
|
||||
});
|
||||
} else {
|
||||
layer.msg(msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('登出请求失败:', error);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 页面就绪后绑定事件(箭头函数写法)
|
||||
(() => {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', bindLogout);
|
||||
} else {
|
||||
bindLogout();
|
||||
}
|
||||
})();
|
||||
|
||||
// 刷新页面功能处理
|
||||
const handleRefresh = () => {
|
||||
layer.confirm('确定要刷新内容吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
|
||||
// 获取当前hash值,确定当前页面路径
|
||||
let currentPath = window.location.hash.replace('#', '') || 'dashboard';
|
||||
|
||||
// 显示加载层
|
||||
const loadIndex = layer.load(2, {
|
||||
content: '正在刷新...'
|
||||
});
|
||||
|
||||
// 延迟一下再刷新内容,让用户看到加载效果
|
||||
setTimeout(() => {
|
||||
// 重新加载当前内容页面
|
||||
routerTo({ path: currentPath });
|
||||
layer.close(loadIndex);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
// 绑定刷新按钮点击事件
|
||||
$('#refresh-btn').on('click', handleRefresh);
|
||||
|
||||
function routerTo({
|
||||
elem = '#router-view',
|
||||
path = 'dashboard',
|
||||
prefix = 'admin/', //路由前缀
|
||||
suffix = '', //路由后缀
|
||||
} = {}) {
|
||||
var routerView = $(elem);
|
||||
var url = prefix + path + suffix;
|
||||
|
||||
var loadTimer = setTimeout(() => {
|
||||
layer.load(2);
|
||||
}, 100);
|
||||
|
||||
history.replaceState({}, '', `#${path}`); // 因为并没有处理路由
|
||||
routerView.attr('src', url)
|
||||
routerView.off('load').on('load',function(){
|
||||
element.render();
|
||||
form.render();
|
||||
clearTimeout(loadTimer);
|
||||
layer.closeLast('loading');
|
||||
})
|
||||
|
||||
// 选中, 展开菜单
|
||||
$('#ws-nav-side')
|
||||
.find("[data-path='" + path + "']")
|
||||
.parent('dd')
|
||||
.addClass('layui-this')
|
||||
.closest('.layui-nav-item')
|
||||
.addClass('layui-nav-itemed');
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function rippleViewTransition(isDark, callback) {
|
||||
// 移植自 https://github.com/vuejs/vitepress/pull/2347
|
||||
// 支持 Chrome 111+
|
||||
|
||||
// 兼容 jQuery 3 下隐式 event 全局对象不可用的问题
|
||||
if (!window.event) {
|
||||
window.event = new MouseEvent('click', {
|
||||
clientX: document.documentElement.clientWidth,
|
||||
clientY: 60,
|
||||
});
|
||||
}
|
||||
|
||||
const x = event.clientX;
|
||||
const y = event.clientY;
|
||||
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
|
||||
const transition = document.startViewTransition(function () {
|
||||
callback && callback();
|
||||
});
|
||||
transition.ready.then(function () {
|
||||
var clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: isDark ? clipPath : [...clipPath].reverse(),
|
||||
},
|
||||
{
|
||||
duration: 300,
|
||||
easing: 'ease-in',
|
||||
pseudoElement: isDark ? '::view-transition-new(root)' : '::view-transition-old(root)',
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function addStyle(id, cssStr) {
|
||||
const el = document.getElementById(id) || document.createElement('style');
|
||||
if (!el.isConnected) {
|
||||
el.type = 'text/css';
|
||||
el.id = id;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.textContent = cssStr;
|
||||
}
|
||||
|
||||
function addLink(opt) {
|
||||
return new Promise((resolve) => {
|
||||
const link = Object.assign(document.createElement('link'), {
|
||||
rel: 'stylesheet',
|
||||
onload: () => resolve({ ...opt, status: 'success' }),
|
||||
onerror: () => resolve({ ...opt, status: 'error' }), // 为了在 Promise.all 的使用场景
|
||||
...opt,
|
||||
});
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
function loadScript(url, callback) {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = 'async';
|
||||
script.src = url;
|
||||
document.body.appendChild(script);
|
||||
if (script.readyState) {
|
||||
script.onreadystatechange = function () {
|
||||
if (script.readyState == 'complete' || script.readyState == 'loaded') {
|
||||
script.onreadystatechange = null;
|
||||
callback && callback();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
script.onload = function () {
|
||||
callback && callback();
|
||||
};
|
||||
}
|
||||
}
|
||||
83
web/static/lib/README.md
Normal file
83
web/static/lib/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# ColorMode 模块(WIP)
|
||||
|
||||
开箱即用的主题切换(深色/浅色/自定义)模块,具有自动数据持久性。
|
||||
|
||||
**基本使用**
|
||||
|
||||
```js
|
||||
layui.use(['colorMode'], function () {
|
||||
var colorMode = layui.colorMode
|
||||
|
||||
var theme = colorMode.init()
|
||||
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
**配置**
|
||||
|
||||
模块仅处理 DOM 属性更改,以便在 CSS 中应用正确的选择器,不会处理实际的样式,主题或 CSS。
|
||||
默认情况下,使用 auto 模式(与用户的浏览器首选项匹配),将类 dark 应用于 html 标签时启用深色模式,返回一个对象,用来获取和改变主题。
|
||||
|
||||
```js
|
||||
var theme = colorMode.init()
|
||||
|
||||
theme.mode() // 'dark' | 'light'
|
||||
|
||||
theme.setMode('dark') // 设置为深色模式并持久化到 localstorage
|
||||
|
||||
theme.setMode('auto') // 设置为 auto 模式
|
||||
```
|
||||
|
||||
也可以自定义以使其适用于大多数场景
|
||||
|
||||
```js
|
||||
var theme = colorMode.init({
|
||||
selector: 'body',
|
||||
attribute: 'theme-mode',
|
||||
initialValue: 'light',
|
||||
modes: {
|
||||
auto: '',
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
contrast: 'dark contrast',
|
||||
},
|
||||
storage: localStorage,
|
||||
storageKey: 'xxx-theme-mode',
|
||||
disableTransition: true,
|
||||
})
|
||||
```
|
||||
|
||||
如果上述配置仍不能满足您的需求,可以使用 onChanged 选项完全控制处理更新的方式
|
||||
|
||||
```js
|
||||
var theme = colorMode.init({
|
||||
onChanged: function(mode, defaultHandler){
|
||||
// 自定义更新方式
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**API**
|
||||
|
||||
```ts
|
||||
/**
|
||||
* @typedef {object} initOptions
|
||||
* @prop {string} [selector='html'] - 应用于目标元素的 CSS 选择器
|
||||
* @prop {string} [attribute='class'] - 应用于目标元素的 HTML 属性
|
||||
* @prop {string} [initialValue='auto'] - 初始颜色模式
|
||||
* @prop {Object.<string, string>} [modes]- 颜色模式。value 为添加到 HTML 属性上的值
|
||||
* @prop {(mode: string, defaultHandler: () => void) => void} [onChanged] - 用于处理更新的自定义处理程序,指定时,默认行为将被覆盖。mode 为颜色模式,defaultHandler 为默认处理程序
|
||||
* @prop {Storage} [storage=localStorage] - 将数据持久化到 localStorage/sessionStorage 的键。传递 `null` 以禁用持久性
|
||||
* @prop {string | null} [storageKey='color-scheme'] - 持久化使用的 key
|
||||
* @prop {boolean} [disableTransition=true] - 禁用切换时的过渡 {@link https://paco.me/writing/disable-theme-transitions}
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {initOptions} options
|
||||
* @returns {{ mode: () => string; setMode: (mode: string) => void;}}
|
||||
*/
|
||||
colorMode.init(options)
|
||||
```
|
||||
191
web/static/lib/colorMode.js
Executable file
191
web/static/lib/colorMode.js
Executable file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* WIP
|
||||
* 移植自 https://github.com/vueuse/vueuse/tree/main/packages/core/useColorMode
|
||||
*/
|
||||
// @ts-ignore
|
||||
layui.define(['jquery'], function (exports) {
|
||||
'use strict';
|
||||
|
||||
/** @type {jQuery}*/
|
||||
var $ = layui.jquery;
|
||||
|
||||
var MOD_NAME = 'colorMode';
|
||||
var defaultWindow = window;
|
||||
var document = defaultWindow.document;
|
||||
|
||||
var colorMode = {
|
||||
/**
|
||||
* @typedef {object} initOptions
|
||||
* @prop {string} [selector="html"] - 应用于目标元素的 CSS 选择器
|
||||
* @prop {string} [attribute="class"] - 应用于目标元素的 HTML 属性
|
||||
* @prop {string} [initialValue='auto'] - 初始颜色模式
|
||||
* @prop {Object.<string, string>} [modes]- 颜色模式。value 为添加到 HTML 属性上的值
|
||||
* @prop {(mode: string, defaultHandler: (window?: Window) => void) => void} [onChanged] - 用于处理更新的自定义处理程序,指定时,默认行为将被覆盖。
|
||||
* @prop {Storage} [storage=localStorage] - 将数据持久化到 localStorage/sessionStorage 的键。传递 `null` 以禁用持久性
|
||||
* @prop {string | null} [storageKey='color-scheme'] - 持久化使用的 key
|
||||
* @prop {boolean} [disableTransition=true] - 禁用切换时的过渡 {@link https://paco.me/writing/disable-theme-transitions}
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {initOptions} options
|
||||
* @returns {{mode: () => string; setMode: (mode: string, window?: Window) => void; }}
|
||||
*/
|
||||
init: function (options) {
|
||||
var defaults = {
|
||||
selector: 'html',
|
||||
attribute: 'class',
|
||||
initialValue: 'auto',
|
||||
modes: {
|
||||
auto: '',
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
storage: localStorage,
|
||||
storageKey: 'color-scheme',
|
||||
disableTransition: true,
|
||||
};
|
||||
|
||||
var opts = $.extend(true, {}, defaults, options);
|
||||
|
||||
// 当前颜色模式
|
||||
var state;
|
||||
// 系统颜色模式
|
||||
var system;
|
||||
// 初始化 storage
|
||||
var store =
|
||||
opts.storageKey == null
|
||||
? opts.initialValue
|
||||
: (function () {
|
||||
var v = opts.storage.getItem(opts.storageKey);
|
||||
if (!v) {
|
||||
opts.storage.setItem(opts.storageKey, opts.initialValue);
|
||||
return opts.initialValue;
|
||||
}
|
||||
return v;
|
||||
})();
|
||||
|
||||
/**
|
||||
* 更新 HTML 属性值
|
||||
* @param {String} selector
|
||||
* @param {String} attribute
|
||||
* @param {String} value
|
||||
* @param {Window} win
|
||||
*/
|
||||
var updateHTMLAttrs = function (selector, attribute, value, win) {
|
||||
win = win || defaultWindow;
|
||||
var document = win.document;
|
||||
var el = typeof selector === 'string' ? document.querySelector(selector) : undefined;
|
||||
if (!el) return;
|
||||
|
||||
/**@type HTMLStyleElement */
|
||||
var style;
|
||||
|
||||
if (opts.disableTransition) {
|
||||
style = document.createElement('style');
|
||||
style.appendChild(
|
||||
document.createTextNode(
|
||||
'*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'
|
||||
)
|
||||
);
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
if (attribute === 'class') {
|
||||
var current = value.split(/\s/g);
|
||||
$.each(opts.modes, function (_, modeval) {
|
||||
$.each((modeval || '').split(/\s/g), function (_, v) {
|
||||
if (!v) return;
|
||||
if (current.indexOf(v) !== -1) {
|
||||
el.classList.add(v);
|
||||
} else {
|
||||
el.classList.remove(v);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
el.setAttribute(attribute, value);
|
||||
}
|
||||
|
||||
if (opts.disableTransition) {
|
||||
// 调用 getComputedStyle 强制浏览器重绘
|
||||
// @ts-expect-error 未使用的变量
|
||||
var _ = window.getComputedStyle(style).opacity;
|
||||
document.head.removeChild(style);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新状态
|
||||
* @param {String} mode - 颜色模式
|
||||
*/
|
||||
var updateState = function (mode) {
|
||||
store = opts.storageKey == null ? mode : opts.storage.getItem(opts.storageKey);
|
||||
|
||||
state = store === 'auto' ? system : store;
|
||||
};
|
||||
|
||||
var prefersColorScheme = function () {
|
||||
var isSupported = window && 'matchMedia' in window && typeof window.matchMedia === 'function';
|
||||
if (!isSupported) {
|
||||
system = 'light';
|
||||
onChanged(system);
|
||||
return;
|
||||
}
|
||||
|
||||
var darkThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
var update = function () {
|
||||
var preferredDark = darkThemeMediaQuery.matches;
|
||||
system = preferredDark ? 'dark' : 'light';
|
||||
onChanged(system);
|
||||
};
|
||||
update();
|
||||
if ('addEventListener' in darkThemeMediaQuery) {
|
||||
darkThemeMediaQuery.addEventListener('change', update);
|
||||
} else {
|
||||
// @ts-ignore 已弃用
|
||||
darkThemeMediaQuery.addListener(update);
|
||||
}
|
||||
};
|
||||
|
||||
prefersColorScheme();
|
||||
|
||||
function defaultOnChanged(win) {
|
||||
updateHTMLAttrs(opts.selector, opts.attribute, opts.modes[state], win);
|
||||
}
|
||||
|
||||
function onChanged(mode, win) {
|
||||
updateState(mode);
|
||||
if (opts.onChanged) {
|
||||
opts.onChanged(state, defaultOnChanged);
|
||||
} else {
|
||||
defaultOnChanged(win);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
setMode: function (mode, win) {
|
||||
if (opts.storageKey) {
|
||||
opts.storage.setItem(opts.storageKey, mode);
|
||||
}
|
||||
onChanged(mode, win);
|
||||
},
|
||||
mode: function () {
|
||||
return state;
|
||||
},
|
||||
};
|
||||
},
|
||||
addStyle: function (id, cssStr) {
|
||||
var el = /** @type {HTMLStyleElement} */ (document.getElementById(id) || document.createElement('style'));
|
||||
if (!el.isConnected) {
|
||||
el.type = 'text/css';
|
||||
el.id = id;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.textContent = cssStr;
|
||||
},
|
||||
};
|
||||
|
||||
exports(MOD_NAME, colorMode);
|
||||
});
|
||||
317
web/static/lib/drawer/drawer.css
Normal file
317
web/static/lib/drawer/drawer.css
Normal file
@@ -0,0 +1,317 @@
|
||||
.layer-drawer.layui-layer {
|
||||
border-radius: 0 !important;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.layer-drawer.layui-layer.position-absolute {
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
.layer-drawer-anim,
|
||||
.layer-drawer-anim.layui-anim {
|
||||
-webkit-animation-duration: .3s;
|
||||
animation-duration: .3s;
|
||||
-webkit-animation-timing-function: cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
animation-timing-function: cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
}
|
||||
|
||||
/* right to left */
|
||||
@keyframes layer-rl {
|
||||
from {
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
-ms-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-rl {
|
||||
from {
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
-ms-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-rl {
|
||||
-webkit-animation-name: layer-rl;
|
||||
animation-name: layer-rl;
|
||||
}
|
||||
|
||||
/* right to left close */
|
||||
@keyframes layer-rl-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
-ms-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-rl-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(100%, 0, 0);
|
||||
-ms-transform: translate3d(100%, 0, 0);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-rl-close,
|
||||
.layer-anim-rl.layer-anim-close {
|
||||
-webkit-animation-name: layer-rl-close;
|
||||
animation-name: layer-rl-close;
|
||||
}
|
||||
|
||||
/* left to right */
|
||||
@-webkit-keyframes layer-lr {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(-100%, 0, 0);
|
||||
-ms-transform: translate3d(-100%, 0, 0);
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-lr {
|
||||
from {
|
||||
-webkit-transform: translate3d(-100%, 0, 0);
|
||||
-ms-transform: translate3d(-100%, 0, 0);
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-lr {
|
||||
-webkit-animation-name: layer-lr;
|
||||
animation-name: layer-lr
|
||||
}
|
||||
|
||||
/* left to right close */
|
||||
@-webkit-keyframes layer-lr-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(-100%, 0, 0);
|
||||
-ms-transform: translate3d(-100%, 0, 0);
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-lr-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(-100%, 0, 0);
|
||||
-ms-transform: translate3d(-100%, 0, 0);
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-lr-close,
|
||||
.layer-anim-lr.layer-anim-close {
|
||||
-webkit-animation-name: layer-lr-close;
|
||||
animation-name: layer-lr-close
|
||||
}
|
||||
|
||||
/* top to bottom */
|
||||
@-webkit-keyframes layer-tb {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
-ms-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-tb {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
-ms-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-tb {
|
||||
-webkit-animation-name: layer-tb;
|
||||
animation-name: layer-tb
|
||||
}
|
||||
|
||||
/* top to bottom close */
|
||||
@-webkit-keyframes layer-tb-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
-ms-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-tb-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, -100%, 0);
|
||||
-ms-transform: translate3d(0, -100%, 0);
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-tb-close,
|
||||
.layer-anim-tb.layer-anim-close {
|
||||
-webkit-animation-name: layer-tb-close;
|
||||
animation-name: layer-tb-close
|
||||
}
|
||||
|
||||
/* bottom to top */
|
||||
@-webkit-keyframes layer-bt {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
-ms-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-bt {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
-ms-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-bt {
|
||||
-webkit-animation-name: layer-bt;
|
||||
animation-name: layer-bt
|
||||
}
|
||||
|
||||
/* bottom to top close */
|
||||
@-webkit-keyframes layer-bt-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
-ms-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-bt-close {
|
||||
from {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-ms-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translate3d(0, 100%, 0);
|
||||
-ms-transform: translate3d(0, 100%, 0);
|
||||
transform: translate3d(0, 100%, 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-bt-close,
|
||||
.layer-anim-bt.layer-anim-close {
|
||||
-webkit-animation-name: layer-bt-close;
|
||||
animation-name: layer-bt-close
|
||||
}
|
||||
200
web/static/lib/drawer/drawer.js
Executable file
200
web/static/lib/drawer/drawer.js
Executable file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* 抽屉模块
|
||||
*/
|
||||
layui.define(['jquery', 'layer'], function (exports) {
|
||||
('use strict');
|
||||
|
||||
var MOD_NAME = 'drawer';
|
||||
var $ = layui.jquery;
|
||||
var layer = layui.layer;
|
||||
|
||||
layui.link(layui.cache.base + 'drawer/drawer.css');
|
||||
var drawer = new (function () {
|
||||
this.open = function (option) {
|
||||
return layerDrawer(option);
|
||||
};
|
||||
this.title = layer.title;
|
||||
this.style = layer.style;
|
||||
this.close = layer.close;
|
||||
this.closeAll = layer.closeAll;
|
||||
})();
|
||||
|
||||
/**
|
||||
*
|
||||
* 封装 layer.open
|
||||
*
|
||||
* @param {object} option, `type`, `anim`, `move`, `fixed`, `skin`,`maxWidth`, `maxHeight`, `moveOut`, `moveEnd` 不可用,其它参数和 layer.open 一致, 新增 `iframe`和 `url`参数
|
||||
* @returns {number} 原生 layer 的 index
|
||||
*/
|
||||
function layerDrawer(option) {
|
||||
var opt = normalizeOption(option);
|
||||
if (opt.target) appendToTarget(opt);
|
||||
if (opt.url) loadFragment(opt);
|
||||
if (opt.shade) {
|
||||
$('<style/>')
|
||||
.attr('id', 'layer-drawer')
|
||||
.html('.layui-layer-shade{opacity: 0;transition: opacity .35s cubic-bezier(0.34, 0.69, 0.1, 1);}') // fadeIn
|
||||
.appendTo('head');
|
||||
|
||||
option.end = Aspect(option.end, undefined, function (layero, index) {
|
||||
$('#layer-drawer').remove();
|
||||
});
|
||||
}
|
||||
return layer.open(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 HTML 片段到 layer content
|
||||
* @param {object} option 设置选项
|
||||
*/
|
||||
function loadFragment(option) {
|
||||
option.success = Aspect(option.success, function (layero) {
|
||||
$.ajax({
|
||||
url: option.url,
|
||||
dataType: 'html',
|
||||
success: function (result) {
|
||||
layero.children('.layui-layer-content').html(result);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*将 layer 附加到指定节点
|
||||
* @param {object} opt 设置选项
|
||||
*/
|
||||
function appendToTarget(opt) {
|
||||
var targetDOM = $(opt.target);
|
||||
var contentDOM = opt.content;
|
||||
|
||||
contentDOM.appendTo(targetDOM);
|
||||
opt.skin = getDrawerAnimationClass(opt.offset, true);
|
||||
opt.offset = calcOffset(opt.offset, opt.area, targetDOM);
|
||||
// 处理关闭后偶现 DOM 仍显示的问题
|
||||
opt.end = Aspect(opt.end, function () {
|
||||
contentDOM.css('display', 'none');
|
||||
});
|
||||
if (opt.shade) {
|
||||
opt.success = Aspect(opt.success, function (layero, index) {
|
||||
var shadeDOM = $('#layui-layer-shade' + index);
|
||||
shadeDOM.css('position', 'absolute');
|
||||
shadeDOM.appendTo(layero.parent());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化 layer.open 选项
|
||||
* @param {object} option layer.open 的选项
|
||||
* @returns 规范化后的选项
|
||||
*/
|
||||
function normalizeOption(option) {
|
||||
option.type = option.iframe ? 2 : 1;
|
||||
option.anim = -1;
|
||||
option.move = false;
|
||||
option.fixed = true;
|
||||
option.content = option.iframe ? option.iframe : option.content;
|
||||
if (option.offset === undefined) option.offset = 'r';
|
||||
option.area = calcDrawerArea(option.offset, option.area);
|
||||
option.skin = getDrawerAnimationClass(option.offset);
|
||||
if (option.title === undefined) option.title = false;
|
||||
if (option.closeBtn === undefined) option.closeBtn = false;
|
||||
if (option.shade === undefined) option.shade = 0.3;
|
||||
if (option.shadeClose === undefined) option.shadeClose = true;
|
||||
if (option.resize === undefined) option.resize = false;
|
||||
if (option.success === undefined) option.success = function () {}; // 处理遮罩需要
|
||||
if (option.end === undefined) option.end = function () {};
|
||||
return option;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算抽屉宽高
|
||||
* @param {string} offset 抽屉方向 l = 左, r = 右, t = 上, b = 下
|
||||
* @param {string[] | string} drawerArea 抽屉大小,字符串数组格式[width, height]:["200px","100%"],字符串格式:"30%" "200px"。
|
||||
* @returns{string[]} 抽屉宽高数组
|
||||
*/
|
||||
function calcDrawerArea(offset, drawerArea) {
|
||||
if (drawerArea instanceof Array) {
|
||||
return drawerArea;
|
||||
}
|
||||
drawerArea = drawerArea === undefined || drawerArea === 'auto' ? '30%' : drawerArea;
|
||||
if (offset === 'l' || offset === 'r') {
|
||||
return [drawerArea, '100%'];
|
||||
} else if (offset === 't' || offset === 'b') {
|
||||
return ['100%', drawerArea];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取抽屉动画类,指定挂载容器时需要设置绝对定位
|
||||
* @param {string} offset 抽屉方向 l = 左, r = 右, t = 上, b = 下
|
||||
* @param {boolean} [isAbsolute] 是否是绝对定位
|
||||
* @returns {string} 抽屉入场动画类
|
||||
*/
|
||||
function getDrawerAnimationClass(offset, isAbsolute) {
|
||||
var prefixClass = 'layer-drawer layer-drawer-anim layui-anim layer-anim-';
|
||||
var suffix = 'rl';
|
||||
if (offset === 'l') {
|
||||
suffix = 'lr';
|
||||
} else if (offset === 'r') {
|
||||
suffix = 'rl';
|
||||
} else if (offset === 't') {
|
||||
suffix = 'tb';
|
||||
} else if (offset === 'b') {
|
||||
suffix = 'bt';
|
||||
}
|
||||
return prefixClass + suffix + (isAbsolute ? ' position-absolute ' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定挂载容器重新计算 offset
|
||||
*
|
||||
* layer 源码中使用窗口宽高计算位置,所以此
|
||||
* @param {string} offset 位置
|
||||
* @param {string | string[]} area 范围大小
|
||||
* @param {*} targetEl 挂载节点
|
||||
* @returns 包含抽屉位置信息的数组,[top, left]
|
||||
*/
|
||||
function calcOffset(offset, area, targetEl) {
|
||||
// https://gitee.com/layui/layui/blob/main/src/modules/layer.js#L560
|
||||
if (offset === undefined || offset === 'l' || offset === 't') {
|
||||
offset = 'lt';
|
||||
} else if (offset === 'r') {
|
||||
// https://gitee.com/layui/layui/blob/main/src/modules/layer.js#L554
|
||||
area = area instanceof Array ? area[0] : area;
|
||||
var left = /%$/.test(area)
|
||||
? targetEl.innerWidth() * (1 - window.parseFloat(area) / 100)
|
||||
: targetEl.innerWidth() - window.parseFloat(area);
|
||||
offset = ['0', left];
|
||||
} else if (offset === 'b') {
|
||||
area = area instanceof Array ? area[1] : area;
|
||||
var top = /%$/.test(area)
|
||||
? targetEl.innerHeight() * (1 - window.parseFloat(area) / 100)
|
||||
: targetEl.innerHeight() - window.parseFloat(area);
|
||||
offset = [top, '0'];
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简易的切面
|
||||
* @param {Function} target 被通知的对象,原函数
|
||||
* @param {Function | undefined} [before] 前置通知
|
||||
* @param {Function | undefined} [after] 后置通知
|
||||
* @returns 代理函数
|
||||
*/
|
||||
function Aspect(target, before, after) {
|
||||
function proxyFunc() {
|
||||
if (before && typeof before === 'function') {
|
||||
before.apply(this, arguments);
|
||||
}
|
||||
target.apply(this, arguments);
|
||||
if (after && typeof after === 'function') {
|
||||
after.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
return proxyFunc;
|
||||
}
|
||||
|
||||
exports(MOD_NAME, drawer);
|
||||
});
|
||||
142
web/static/lib/include.js
Executable file
142
web/static/lib/include.js
Executable file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @typedef {object} IncludeFile
|
||||
*
|
||||
* @prop {boolean} ok
|
||||
* @prop {number} status
|
||||
* @prop {string} html
|
||||
*/
|
||||
|
||||
/** @type {Map<string,IncludeFile | Promise<IncludeFile>>} */
|
||||
const includeFiles = new Map();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {'cors' | 'no-cors' | 'same-origin'} [mode='cors']
|
||||
*
|
||||
* @returns {Promise<IncludeFile>}
|
||||
*/
|
||||
export function requestInclude(src, mode = 'cors'){
|
||||
const prev = includeFiles.get(src);
|
||||
if (prev !== undefined) {
|
||||
return Promise.resolve(prev);
|
||||
}
|
||||
const fileDataPromise = fetch(src, { mode: mode }).then(async response => {
|
||||
const res = {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
html: await response.text()
|
||||
};
|
||||
includeFiles.set(src, res);
|
||||
return res;
|
||||
});
|
||||
includeFiles.set(src, fileDataPromise);
|
||||
return fileDataPromise;
|
||||
}
|
||||
|
||||
class HtmlImport extends HTMLElement {
|
||||
constructor () {
|
||||
super();
|
||||
}
|
||||
|
||||
static get observedAttributes () {
|
||||
return ['src', 'mode', 'allow-scripts'];
|
||||
}
|
||||
|
||||
get src() {
|
||||
return this.getAttribute('src') || '';
|
||||
}
|
||||
|
||||
set src(value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode') || 'cors';
|
||||
}
|
||||
|
||||
set mode(value) {
|
||||
this.setAttribute('mode', value);
|
||||
}
|
||||
|
||||
get allowScripts() {
|
||||
return this.hasAttribute('allow-scripts');
|
||||
}
|
||||
|
||||
set allowScripts(value) {
|
||||
this.toggleAttribute('allow-scripts', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 innerHTML 中的 <script></script>
|
||||
* @param {HTMLScriptElement} scripts
|
||||
*/
|
||||
async executeScript(scripts) {
|
||||
const execQueue = function (script) {
|
||||
const newScript = document.createElement('script');
|
||||
[...script.attributes].forEach(attr => newScript.setAttribute(attr.name, attr.value));
|
||||
newScript.textContent = script.textContent;
|
||||
script.parentNode && script.parentNode.replaceChild(newScript, script);
|
||||
return script.src ? new Promise((resolve) => {
|
||||
newScript.async = false;
|
||||
newScript.addEventListener('load', e => resolve(e));
|
||||
newScript.addEventListener('error', e => resolve(e));
|
||||
}) : Promise.resolve();
|
||||
};
|
||||
// 按 <script> 顺序执行,确保上下文关联
|
||||
for (const script of scripts) {
|
||||
await execQueue(script);
|
||||
// console.log(`${script.src||script} loaded`, Date.now());
|
||||
}
|
||||
}
|
||||
|
||||
async handleSrcChange() {
|
||||
try {
|
||||
const src = this.src;
|
||||
const file = await requestInclude(src, this.mode);
|
||||
|
||||
if (src !== this.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.ok) {
|
||||
this.emit('error', { detail: { status: file.status } });
|
||||
return;
|
||||
}
|
||||
|
||||
this.innerHTML = file.html;
|
||||
|
||||
if (this.allowScripts) {
|
||||
await this.executeScript(this.querySelectorAll('script'));
|
||||
}
|
||||
|
||||
this.emit('load');
|
||||
} catch {
|
||||
this.emit('error', { detail: { status: -1 } });
|
||||
}
|
||||
}
|
||||
|
||||
attributeChangedCallback (name) {
|
||||
if (name == 'src') {
|
||||
this.handleSrcChange();
|
||||
}
|
||||
}
|
||||
|
||||
emit(name, options) {
|
||||
const event = new CustomEvent(name, {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: true,
|
||||
detail: {},
|
||||
...options
|
||||
});
|
||||
|
||||
this.dispatchEvent(event);
|
||||
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get('wc-include')) {
|
||||
customElements.define('wc-include', HtmlImport);
|
||||
}
|
||||
11360
web/static/lib/less.js
Executable file
11360
web/static/lib/less.js
Executable file
File diff suppressed because it is too large
Load Diff
202
web/static/src/css-variables.css
Normal file
202
web/static/src/css-variables.css
Normal file
@@ -0,0 +1,202 @@
|
||||
:root{
|
||||
/* =====色板===== */
|
||||
/*常量,不随明暗主题变化*/
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
--lay-color-white: #FAFAFA;
|
||||
--lay-color-black: #333333;
|
||||
|
||||
--lay-color-red-1: #FFF1E8;
|
||||
--lay-color-red-2: #FFD7C0;
|
||||
--lay-color-red-3: #FFBB99;
|
||||
--lay-color-red-4: #FF9C71;
|
||||
--lay-color-red-5: #FF7A4A;
|
||||
--lay-color-red-6: #FF5722;
|
||||
--lay-color-red-7: #D23B15;
|
||||
--lay-color-red-8: #A6250B;
|
||||
--lay-color-red-9: #791404;
|
||||
--lay-color-red-10: #4D0800;
|
||||
|
||||
--lay-color-blue-1: #E8F9FF;
|
||||
--lay-color-blue-2: #C0ECFF;
|
||||
--lay-color-blue-3: #97DCFF;
|
||||
--lay-color-blue-4: #6FCAFF;
|
||||
--lay-color-blue-5: #46B5FF;
|
||||
--lay-color-blue-6: #1E9FFF;
|
||||
--lay-color-blue-7: #1379D2;
|
||||
--lay-color-blue-8: #0A58A6;
|
||||
--lay-color-blue-9: #043A79;
|
||||
--lay-color-blue-10: #00214D;
|
||||
|
||||
--lay-color-lightblue-1: #E8FDFF;
|
||||
--lay-color-lightblue-2: #C1F4FB;
|
||||
--lay-color-lightblue-3: #9CEAF7;
|
||||
--lay-color-lightblue-4: #77DDF4;
|
||||
--lay-color-lightblue-5: #53CEF0;
|
||||
--lay-color-lightblue-6: #31BDEC;
|
||||
--lay-color-lightblue-7: #1F95C4;
|
||||
--lay-color-lightblue-8: #10709C;
|
||||
--lay-color-lightblue-9: #064E74;
|
||||
--lay-color-lightblue-10: #002F4D;
|
||||
|
||||
--lay-color-layuigreen-1: #E8FFF9;
|
||||
--lay-color-layuigreen-2: #B5F1E3;
|
||||
--lay-color-layuigreen-3: #87E3D1;
|
||||
--lay-color-layuigreen-4: #5DD6C1;
|
||||
--lay-color-layuigreen-5: #37C8B5;
|
||||
--lay-color-layuigreen-6: #16BAAA;
|
||||
--lay-color-layuigreen-7: #0E9F95;
|
||||
--lay-color-layuigreen-8: #08837F;
|
||||
--lay-color-layuigreen-9: #036868;
|
||||
--lay-color-layuigreen-10: #004A4D;
|
||||
|
||||
--lay-color-green-1: #E8FFF2;
|
||||
--lay-color-green-2: #B5F1D1;
|
||||
--lay-color-green-3: #86E2B4;
|
||||
--lay-color-green-4: #5CD49C;
|
||||
--lay-color-green-5: #37C588;
|
||||
--lay-color-green-6: #16B777;
|
||||
--lay-color-green-7: #0E9C68;
|
||||
--lay-color-green-8: #088259;
|
||||
--lay-color-green-9: #036749;
|
||||
--lay-color-green-10: #004D38;
|
||||
|
||||
--lay-color-orange-1: #FFFCE8;
|
||||
--lay-color-orange-2: #FFF5BA;
|
||||
--lay-color-orange-3: #FFEA8B;
|
||||
--lay-color-orange-4: #FFDC5D;
|
||||
--lay-color-orange-5: #FFCB2E;
|
||||
--lay-color-orange-6: #FFB800;
|
||||
--lay-color-orange-7: #D29000;
|
||||
--lay-color-orange-8: #A66C00;
|
||||
--lay-color-orange-9: #794B00;
|
||||
--lay-color-orange-10: #4D2D00;
|
||||
|
||||
--lay-color-cyan-1: #E8F6FF;
|
||||
--lay-color-cyan-2: #B9CEDD;
|
||||
--lay-color-cyan-3: #8FA7BB;
|
||||
--lay-color-cyan-4: #6A829A;
|
||||
--lay-color-cyan-5: #4A5F78;
|
||||
--lay-color-cyan-6: #2F4056;
|
||||
--lay-color-cyan-7: #223654;
|
||||
--lay-color-cyan-8: #162C51;
|
||||
--lay-color-cyan-9: #0B214F;
|
||||
--lay-color-cyan-10: #00174D;
|
||||
|
||||
--lay-color-purple-1: #FDE8FF;
|
||||
--lay-color-purple-2: #EDBEF4;
|
||||
--lay-color-purple-3: #DC97E8;
|
||||
--lay-color-purple-4: #C972DD;
|
||||
--lay-color-purple-5: #B651D1;
|
||||
--lay-color-purple-6: #A233C6;
|
||||
--lay-color-purple-7: #8120A8;
|
||||
--lay-color-purple-8: #631289;
|
||||
--lay-color-purple-9: #48076B;
|
||||
--lay-color-purple-10: #2F004D;
|
||||
|
||||
--lay-color-black-1: #E8F8FF;
|
||||
--lay-color-black-2: #BFD0D8;
|
||||
--lay-color-black-3: #98A8B1;
|
||||
--lay-color-black-4: #73818A;
|
||||
--lay-color-black-5: #505B63;
|
||||
--lay-color-black-6: #2F363C;
|
||||
--lay-color-black-7: #23303C;
|
||||
--lay-color-black-8: #18293C;
|
||||
--lay-color-black-9: #0C213C;
|
||||
--lay-color-black-10: #00183C;
|
||||
|
||||
--lay-color-gray-1: #FAFAFA;
|
||||
--lay-color-gray-2: #F6F6F6;
|
||||
--lay-color-gray-3: #EEEEEE;
|
||||
--lay-color-gray-4: #E2E2E2;
|
||||
--lay-color-gray-5: #DDDDDD;
|
||||
--lay-color-gray-6: #D2D2D2;
|
||||
--lay-color-gray-7: #CCCCCC;
|
||||
--lay-color-gray-8: #C2C2C2;
|
||||
--lay-color-gray-9: #AAAAAA;
|
||||
--lay-color-gray-10: #939393;
|
||||
|
||||
--lay-color-gray-11: #858585;
|
||||
--lay-color-gray-12: #7b7b7b;
|
||||
--lay-color-gray-13: #686868;
|
||||
|
||||
/* =====语义===== */
|
||||
/* 主色 */
|
||||
--lay-color-primary: var(--lay-color-layuigreen-6);
|
||||
--lay-color-primary-hover: var(--lay-color-layuigreen-5);
|
||||
--lay-color-primary-active: var(--lay-color-layuigreen-7);
|
||||
--lay-color-primary-disabled: var(--lay-color-layuigreen-3);
|
||||
--lay-color-primary-light: var(--lay-color-layuigreen-4);
|
||||
|
||||
/* 次色 */
|
||||
--lay-color-secondary: var(--lay-color-green-6);
|
||||
--lay-color-secondary-hover: var(--lay-color-green-5);
|
||||
--lay-color-secondary-active: var(--lay-color-green-7);
|
||||
--lay-color-secondary-disabled: var(--lay-color-green-3);
|
||||
--lay-color-secondary-light: var(--lay-color-green-4);
|
||||
|
||||
/* 引导 */
|
||||
--lay-color-info: var(--lay-color-lightblue-6);
|
||||
--lay-color-info-hover: var(--lay-color-lightblue-5);
|
||||
--lay-color-info-active: var(--lay-color-lightblue-7);
|
||||
--lay-color-info-disabled: var(--lay-color-lightblue-3);
|
||||
--lay-color-info-light: var(--lay-color-lightblue-4);
|
||||
|
||||
/* 百搭 */
|
||||
--lay-color-normal: var(--lay-color-blue-6);
|
||||
--lay-color-normal-hover: var(--lay-color-blue-5);
|
||||
--lay-color-normal-active: var(--lay-color-blue-7);
|
||||
--lay-color-normal-disabled: var(--lay-color-blue-3);
|
||||
--lay-color-normal-light: var(--lay-color-blue-4);
|
||||
|
||||
/* 警示 */
|
||||
--lay-color-warning: var(--lay-color-orange-6);
|
||||
--lay-color-warning-hover: var(--lay-color-orange-5);
|
||||
--lay-color-warning-active: var(--lay-color-orange-7);
|
||||
--lay-color-warning-disabled: var(--lay-color-orange-3);
|
||||
--lay-color-warning-light: var(--lay-color-orange-4);
|
||||
|
||||
/* 成功 */
|
||||
--lay-color-success: var(--lay-color-green-6);
|
||||
--lay-color-success-hover: var(--lay-color-green-5);
|
||||
--lay-color-success-active: var(--lay-color-green-7);
|
||||
--lay-color-success-disabled: var(--lay-color-green-3);
|
||||
--lay-color-success-light: var(--lay-color-green-4);
|
||||
|
||||
/* 错误 */
|
||||
--lay-color-danger: var(--lay-color-red-6);
|
||||
--lay-color-danger-hover: var(--lay-color-red-5);
|
||||
--lay-color-danger-active: var(--lay-color-red-7);
|
||||
--lay-color-danger-disabled: var(--lay-color-red-3);
|
||||
--lay-color-danger-light: var(--lay-color-red-4);
|
||||
|
||||
--lay-color-bg-1: #17171A; /*整体背景*/
|
||||
--lay-color-bg-2: #232324; /*一级容器背景,卡片,面板*/
|
||||
--lay-color-bg-3: #2a2a2b; /*二级容器背景*/
|
||||
--lay-color-bg-4: #313132; /*三级容器背景*/
|
||||
--lay-color-bg-5: #373739; /*下拉弹出框、Tooltip 背景颜色*/
|
||||
--lay-color-bg-white: #f6f6f6; /*白色背景*/
|
||||
|
||||
--lay-color-text-1: rgba(255,255,255,.9); /*强调/正文标题*/
|
||||
--lay-color-text-2: rgba(255,255,255,.7); /*次强调/语句*/
|
||||
--lay-color-text-3: rgba(255,255,255,.5); /*次要信息*/
|
||||
--lay-color-text-4: rgba(255,255,255,.3);/*禁用状态文字 */
|
||||
|
||||
--lay-color-border-1: #2e2e30;
|
||||
--lay-color-border-2: #484849;
|
||||
--lay-color-border-3: #5f5f60;
|
||||
--lay-color-border-4: #929293;
|
||||
|
||||
--lay-color-fill-1: rgba(255,255,255,.04);/*浅/禁用*/
|
||||
--lay-color-fill-2: rgba(255,255,255,.08);/*常规/白底悬浮*/
|
||||
--lay-color-fill-3: rgba(255,255,255,.12); /*深/灰底悬浮*/
|
||||
--lay-color-fill-4: rgba(255,255,255,.16);/*重/特殊场景*/
|
||||
|
||||
--lay-color-hover: var(--lay-color-fill-3); /*bg*/
|
||||
--lay-color-active: var(--lay-color-fill-3); /*bg*/
|
||||
|
||||
--lay-shadow-1: 0 4px 6px rgba(0, 0, 0, 6%), 0 1px 10px rgba(0, 0, 0, 8%), 0 2px 4px rgba(0, 0, 0, 12%);/*基础/下层投影 卡片面板*/
|
||||
--lay-shadow-2: 0 8px 10px rgba(0, 0, 0, 12%), 0 3px 14px rgba(0, 0, 0, 10%), 0 5px 5px rgba(0, 0, 0, 16%);/*中层投影 下拉菜单,选择器*/
|
||||
--lay-shadow-3: 0 16px 24px rgba(0, 0, 0, 14%), 0 6px 30px rgba(0, 0, 0, 12%), 0 8px 10px rgba(0, 0, 0, 20%);/*上层投影 弹窗*/
|
||||
}
|
||||
1899
web/static/src/layui-theme-dark-selector.css
Normal file
1899
web/static/src/layui-theme-dark-selector.css
Normal file
File diff suppressed because it is too large
Load Diff
1
web/static/src/layui-theme-dark-selector.css.map
Normal file
1
web/static/src/layui-theme-dark-selector.css.map
Normal file
File diff suppressed because one or more lines are too long
738
web/static/src/layui-theme-dark.css
Normal file
738
web/static/src/layui-theme-dark.css
Normal file
@@ -0,0 +1,738 @@
|
||||
:root{
|
||||
/* =====色板===== */
|
||||
/*常量,不随明暗主题变化*/
|
||||
--color-white: #FFFFFF;
|
||||
--color-black: #000000;
|
||||
|
||||
--lay-color-white: #FAFAFA;
|
||||
--lay-color-black: #333333;
|
||||
|
||||
--lay-color-red-1: #FFF1E8;
|
||||
--lay-color-red-2: #FFD7C0;
|
||||
--lay-color-red-3: #FFBB99;
|
||||
--lay-color-red-4: #FF9C71;
|
||||
--lay-color-red-5: #FF7A4A;
|
||||
--lay-color-red-6: #FF5722;
|
||||
--lay-color-red-7: #D23B15;
|
||||
--lay-color-red-8: #A6250B;
|
||||
--lay-color-red-9: #791404;
|
||||
--lay-color-red-10: #4D0800;
|
||||
|
||||
--lay-color-blue-1: #E8F9FF;
|
||||
--lay-color-blue-2: #C0ECFF;
|
||||
--lay-color-blue-3: #97DCFF;
|
||||
--lay-color-blue-4: #6FCAFF;
|
||||
--lay-color-blue-5: #46B5FF;
|
||||
--lay-color-blue-6: #1E9FFF;
|
||||
--lay-color-blue-7: #1379D2;
|
||||
--lay-color-blue-8: #0A58A6;
|
||||
--lay-color-blue-9: #043A79;
|
||||
--lay-color-blue-10: #00214D;
|
||||
|
||||
--lay-color-lightblue-1: #E8FDFF;
|
||||
--lay-color-lightblue-2: #C1F4FB;
|
||||
--lay-color-lightblue-3: #9CEAF7;
|
||||
--lay-color-lightblue-4: #77DDF4;
|
||||
--lay-color-lightblue-5: #53CEF0;
|
||||
--lay-color-lightblue-6: #31BDEC;
|
||||
--lay-color-lightblue-7: #1F95C4;
|
||||
--lay-color-lightblue-8: #10709C;
|
||||
--lay-color-lightblue-9: #064E74;
|
||||
--lay-color-lightblue-10: #002F4D;
|
||||
|
||||
--lay-color-layuigreen-1: #E8FFF9;
|
||||
--lay-color-layuigreen-2: #B5F1E3;
|
||||
--lay-color-layuigreen-3: #87E3D1;
|
||||
--lay-color-layuigreen-4: #5DD6C1;
|
||||
--lay-color-layuigreen-5: #37C8B5;
|
||||
--lay-color-layuigreen-6: #16BAAA;
|
||||
--lay-color-layuigreen-7: #0E9F95;
|
||||
--lay-color-layuigreen-8: #08837F;
|
||||
--lay-color-layuigreen-9: #036868;
|
||||
--lay-color-layuigreen-10: #004A4D;
|
||||
|
||||
--lay-color-green-1: #E8FFF2;
|
||||
--lay-color-green-2: #B5F1D1;
|
||||
--lay-color-green-3: #86E2B4;
|
||||
--lay-color-green-4: #5CD49C;
|
||||
--lay-color-green-5: #37C588;
|
||||
--lay-color-green-6: #16B777;
|
||||
--lay-color-green-7: #0E9C68;
|
||||
--lay-color-green-8: #088259;
|
||||
--lay-color-green-9: #036749;
|
||||
--lay-color-green-10: #004D38;
|
||||
|
||||
--lay-color-orange-1: #FFFCE8;
|
||||
--lay-color-orange-2: #FFF5BA;
|
||||
--lay-color-orange-3: #FFEA8B;
|
||||
--lay-color-orange-4: #FFDC5D;
|
||||
--lay-color-orange-5: #FFCB2E;
|
||||
--lay-color-orange-6: #FFB800;
|
||||
--lay-color-orange-7: #D29000;
|
||||
--lay-color-orange-8: #A66C00;
|
||||
--lay-color-orange-9: #794B00;
|
||||
--lay-color-orange-10: #4D2D00;
|
||||
|
||||
--lay-color-cyan-1: #E8F6FF;
|
||||
--lay-color-cyan-2: #B9CEDD;
|
||||
--lay-color-cyan-3: #8FA7BB;
|
||||
--lay-color-cyan-4: #6A829A;
|
||||
--lay-color-cyan-5: #4A5F78;
|
||||
--lay-color-cyan-6: #2F4056;
|
||||
--lay-color-cyan-7: #223654;
|
||||
--lay-color-cyan-8: #162C51;
|
||||
--lay-color-cyan-9: #0B214F;
|
||||
--lay-color-cyan-10: #00174D;
|
||||
|
||||
--lay-color-purple-1: #FDE8FF;
|
||||
--lay-color-purple-2: #EDBEF4;
|
||||
--lay-color-purple-3: #DC97E8;
|
||||
--lay-color-purple-4: #C972DD;
|
||||
--lay-color-purple-5: #B651D1;
|
||||
--lay-color-purple-6: #A233C6;
|
||||
--lay-color-purple-7: #8120A8;
|
||||
--lay-color-purple-8: #631289;
|
||||
--lay-color-purple-9: #48076B;
|
||||
--lay-color-purple-10: #2F004D;
|
||||
|
||||
--lay-color-black-1: #E8F8FF;
|
||||
--lay-color-black-2: #BFD0D8;
|
||||
--lay-color-black-3: #98A8B1;
|
||||
--lay-color-black-4: #73818A;
|
||||
--lay-color-black-5: #505B63;
|
||||
--lay-color-black-6: #2F363C;
|
||||
--lay-color-black-7: #23303C;
|
||||
--lay-color-black-8: #18293C;
|
||||
--lay-color-black-9: #0C213C;
|
||||
--lay-color-black-10: #00183C;
|
||||
|
||||
--lay-color-gray-1: #FAFAFA;
|
||||
--lay-color-gray-2: #F6F6F6;
|
||||
--lay-color-gray-3: #EEEEEE;
|
||||
--lay-color-gray-4: #E2E2E2;
|
||||
--lay-color-gray-5: #DDDDDD;
|
||||
--lay-color-gray-6: #D2D2D2;
|
||||
--lay-color-gray-7: #CCCCCC;
|
||||
--lay-color-gray-8: #C2C2C2;
|
||||
--lay-color-gray-9: #AAAAAA;
|
||||
--lay-color-gray-10: #939393;
|
||||
|
||||
--lay-color-gray-11: #858585;
|
||||
--lay-color-gray-12: #7b7b7b;
|
||||
--lay-color-gray-13: #686868;
|
||||
|
||||
/* =====语义===== */
|
||||
/* 主色 */
|
||||
--lay-color-primary: var(--lay-color-layuigreen-6);
|
||||
--lay-color-primary-hover: var(--lay-color-layuigreen-5);
|
||||
--lay-color-primary-active: var(--lay-color-layuigreen-7);
|
||||
--lay-color-primary-disabled: var(--lay-color-layuigreen-3);
|
||||
--lay-color-primary-light: var(--lay-color-layuigreen-4);
|
||||
|
||||
/* 次色 */
|
||||
--lay-color-secondary: var(--lay-color-green-6);
|
||||
--lay-color-secondary-hover: var(--lay-color-green-5);
|
||||
--lay-color-secondary-active: var(--lay-color-green-7);
|
||||
--lay-color-secondary-disabled: var(--lay-color-green-3);
|
||||
--lay-color-secondary-light: var(--lay-color-green-4);
|
||||
|
||||
/* 引导 */
|
||||
--lay-color-info: var(--lay-color-lightblue-6);
|
||||
--lay-color-info-hover: var(--lay-color-lightblue-5);
|
||||
--lay-color-info-active: var(--lay-color-lightblue-7);
|
||||
--lay-color-info-disabled: var(--lay-color-lightblue-3);
|
||||
--lay-color-info-light: var(--lay-color-lightblue-4);
|
||||
|
||||
/* 百搭 */
|
||||
--lay-color-normal: var(--lay-color-blue-6);
|
||||
--lay-color-normal-hover: var(--lay-color-blue-5);
|
||||
--lay-color-normal-active: var(--lay-color-blue-7);
|
||||
--lay-color-normal-disabled: var(--lay-color-blue-3);
|
||||
--lay-color-normal-light: var(--lay-color-blue-4);
|
||||
|
||||
/* 警示 */
|
||||
--lay-color-warning: var(--lay-color-orange-6);
|
||||
--lay-color-warning-hover: var(--lay-color-orange-5);
|
||||
--lay-color-warning-active: var(--lay-color-orange-7);
|
||||
--lay-color-warning-disabled: var(--lay-color-orange-3);
|
||||
--lay-color-warning-light: var(--lay-color-orange-4);
|
||||
|
||||
/* 成功 */
|
||||
--lay-color-success: var(--lay-color-green-6);
|
||||
--lay-color-success-hover: var(--lay-color-green-5);
|
||||
--lay-color-success-active: var(--lay-color-green-7);
|
||||
--lay-color-success-disabled: var(--lay-color-green-3);
|
||||
--lay-color-success-light: var(--lay-color-green-4);
|
||||
|
||||
/* 错误 */
|
||||
--lay-color-danger: var(--lay-color-red-6);
|
||||
--lay-color-danger-hover: var(--lay-color-red-5);
|
||||
--lay-color-danger-active: var(--lay-color-red-7);
|
||||
--lay-color-danger-disabled: var(--lay-color-red-3);
|
||||
--lay-color-danger-light: var(--lay-color-red-4);
|
||||
|
||||
--lay-color-bg-1: #17171A; /*整体背景*/
|
||||
--lay-color-bg-2: #232324; /*一级容器背景,卡片,面板*/
|
||||
--lay-color-bg-3: #2a2a2b; /*二级容器背景*/
|
||||
--lay-color-bg-4: #313132; /*三级容器背景*/
|
||||
--lay-color-bg-5: #373739; /*下拉弹出框、Tooltip 背景颜色*/
|
||||
--lay-color-bg-white: #f6f6f6; /*白色背景*/
|
||||
|
||||
--lay-color-text-1: rgba(255,255,255,.9); /*强调/正文标题*/
|
||||
--lay-color-text-2: rgba(255,255,255,.7); /*次强调/语句*/
|
||||
--lay-color-text-3: rgba(255,255,255,.5); /*次要信息*/
|
||||
--lay-color-text-4: rgba(255,255,255,.3);/*禁用状态文字 */
|
||||
|
||||
--lay-color-border-1: #2e2e30;
|
||||
--lay-color-border-2: #484849;
|
||||
--lay-color-border-3: #5f5f60;
|
||||
--lay-color-border-4: #929293;
|
||||
|
||||
--lay-color-fill-1: rgba(255,255,255,.04);/*浅/禁用*/
|
||||
--lay-color-fill-2: rgba(255,255,255,.08);/*常规/白底悬浮*/
|
||||
--lay-color-fill-3: rgba(255,255,255,.12); /*深/灰底悬浮*/
|
||||
--lay-color-fill-4: rgba(255,255,255,.16);/*重/特殊场景*/
|
||||
|
||||
--lay-color-hover: var(--lay-color-fill-3); /*bg*/
|
||||
--lay-color-active: var(--lay-color-fill-3); /*bg*/
|
||||
|
||||
--lay-shadow-1: 0 4px 6px rgba(0, 0, 0, 6%), 0 1px 10px rgba(0, 0, 0, 8%), 0 2px 4px rgba(0, 0, 0, 12%);/*基础/下层投影 卡片面板*/
|
||||
--lay-shadow-2: 0 8px 10px rgba(0, 0, 0, 12%), 0 3px 14px rgba(0, 0, 0, 10%), 0 5px 5px rgba(0, 0, 0, 16%);/*中层投影 下拉菜单,选择器*/
|
||||
--lay-shadow-3: 0 16px 24px rgba(0, 0, 0, 14%), 0 6px 30px rgba(0, 0, 0, 12%), 0 8px 10px rgba(0, 0, 0, 20%);/*上层投影 弹窗*/
|
||||
}
|
||||
blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,li,ol,p,pre,td,textarea,th,ul{-webkit-tap-highlight-color: rgba(0, 0, 0, 0)} /*danger: 勿改*/
|
||||
body{color:var(--lay-color-text-2);background-color: var(--lay-color-bg-1); color-scheme: dark;}
|
||||
hr{border-bottom:1px solid var(--lay-color-border-2)!important}
|
||||
a{color:var(--lay-color-text-1);}
|
||||
a:hover{color:var(--lay-color-text-3)}
|
||||
/* 三角形 */
|
||||
.layui-edge{border-color:transparent}
|
||||
.layui-edge-top{border-bottom-color:var(--lay-color-border-4)}
|
||||
.layui-edge-right{border-left-color:var(--lay-color-border-4)}
|
||||
.layui-edge-bottom{border-top-color:var(--lay-color-border-4)}
|
||||
.layui-edge-left{border-right-color:var(--lay-color-border-4)}
|
||||
/* 禁用文字 */
|
||||
.layui-disabled,.layui-disabled:hover{color:var(--lay-color-text-4)!important}
|
||||
/* 图标 */
|
||||
.layui-icon{-moz-osx-font-smoothing:grayscale}
|
||||
/* admin 布局 */
|
||||
.layui-layout-admin .layui-header{background-color:var(--lay-color-bg-2)}
|
||||
.layui-layout-admin .layui-footer{box-shadow:-1px 0 4px rgb(0 0 0 / 12%);background-color:var(--lay-color-bg-2)}
|
||||
.layui-layout-admin .layui-logo{color:var(--lay-color-primary);box-shadow:0 1px 2px 0 rgb(0 0 0 / 15%)}
|
||||
/* 引用 */
|
||||
.layui-elem-quote{border-left:5px solid var(--lay-color-secondary);background-color:var(--lay-color-fill-1)}
|
||||
.layui-quote-nm{border-color: var(--lay-color-fill-1)}
|
||||
/* 进度条 */
|
||||
.layui-progress{background-color: var(--lay-color-bg-3)}
|
||||
.layui-progress-bar{background-color:var( --lay-color-secondary)}
|
||||
.layui-progress-text{color:var(--lay-color-text-2)}
|
||||
.layui-progress-big .layui-progress-text{color: var(--lay-color-text-1)}
|
||||
/* 折叠面板 */
|
||||
.layui-colla-title{color: var(--lay-color-text-1);background-color: var(--lay-color-bg-2)}
|
||||
.layui-colla-content{color:var(--lay-color-text-2)}
|
||||
/* 卡片面板 */
|
||||
.layui-card{background-color: var(--lay-color-bg-2);box-shadow:var(--lay-shadow-1)}
|
||||
.layui-card-header{border-bottom:1px solid var(--lay-color-border-2);color:var(--lay-color-text-1)}
|
||||
/* 常规面板 */
|
||||
.layui-panel{box-shadow:var(--lay-shadow-1);background-color: var( --lay-color-bg-2);color: var(--lay-color-text-1)}
|
||||
.layui-menu-body-panel{box-shadow: var(--lay-shadow-2)}
|
||||
/* 窗口面板 */
|
||||
.layui-panel-window{border-top:5px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-2)}
|
||||
/* 背景颜色 */
|
||||
.layui-bg-red{background-color:var(--lay-color-red-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-orange{background-color:var(--lay-color-orange-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-green{background-color:var(--lay-color-layuigreen-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-cyan{background-color:var(--lay-color-cyan-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-blue{background-color: var(--lay-color-blue-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-black{background-color:var(--lay-color-black-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-purple{background-color: var(--lay-color-purple-6)!important; color: var(--lay-color-white)!important;}
|
||||
.layui-bg-gray{background-color:var(--lay-color-gray-1)!important;color: var(--lay-color-black-6)!important}
|
||||
/* 徽章 */
|
||||
.layui-badge-rim,.layui-border,.layui-colla-content,.layui-colla-item,.layui-collapse,.layui-elem-field,.layui-form-pane .layui-form-item[pane],.layui-form-pane .layui-form-label,.layui-input,.layui-input-split,.layui-panel,.layui-select,.layui-tab-bar,.layui-tab-card,.layui-tab-title,.layui-tab-title .layui-this:after,.layui-textarea{border-color: var(--lay-color-border-1)}
|
||||
/* 边框颜色 */
|
||||
.layui-border{color:var(--lay-color-text-1)!important}
|
||||
.layui-border-red{border-color:var(--lay-color-red-6)!important;color:var(--lay-color-red-6)!important}
|
||||
.layui-border-orange{border-color:var(--lay-color-orange-6)!important;color:var(--lay-color-orange-6)!important}
|
||||
.layui-border-green{border-color:var(--lay-color-layuigreen-6)!important;color:var(--lay-color-layuigreen-6)!important}
|
||||
.layui-border-cyan{border-color:var(--lay-color-cyan-6)!important;color:var(--lay-color-cyan-6)!important}
|
||||
.layui-border-blue{border-color: var(--lay-color-blue-6)!important;color: var(--lay-color-blue-6)!important}
|
||||
.layui-border-purple{border-color: var(--lay-color-purple-6)!important; color: var(--lay-color-purple-6)!important;}
|
||||
.layui-border-black{border-color:var(--lay-color-black-6)!important;color:var(--lay-color-text-1)!important}
|
||||
/* 文本区域 */
|
||||
.layui-text{color:var(--lay-color-text-2)}
|
||||
.layui-text-em,.layui-word-aux{color: var(--lay-color-text-3)!important}
|
||||
.layui-text a:not(.layui-btn){color:var(--lay-color-lightblue-6)}
|
||||
.layui-text blockquote:not(.layui-elem-quote){border-left:5px solid var(--lay-color-border-4)}
|
||||
/* 字体颜色 */
|
||||
.layui-font-red{color:var(--lay-color-red-6)!important}
|
||||
.layui-font-orange{color:var(--lay-color-orange-6)!important}
|
||||
.layui-font-green{color:var(--lay-color-layuigreen-6)!important}
|
||||
.layui-font-cyan{color:var(--lay-color-cyan-6)!important}
|
||||
.layui-font-blue{color:var(--lay-color-lightblue-6)!important}
|
||||
.layui-font-black{color:var(--lay-color-black)!important}
|
||||
.layui-font-purple{color:var(--lay-color-purple-6)!important;}
|
||||
.layui-font-gray{color:var(--lay-color-gray-7)!important}
|
||||
/* 按钮 */
|
||||
.layui-btn{border:1px solid transparent;background-color:var(--lay-color-primary);color: var(--lay-color-text-1)}
|
||||
.layui-btn:hover{color: var(--lay-color-text-2)}
|
||||
.layui-btn-primary{border-color:var(--lay-color-border-2);color:var(--lay-color-text-1);background-color: var(--lay-color-bg-4)}
|
||||
.layui-btn-primary:hover{border-color: transparent;color:var(--lay-color-text-2)}
|
||||
.layui-btn-normal{background-color: var(--lay-color-normal)}
|
||||
.layui-btn-warm{background-color:var(--lay-color-warning)}
|
||||
.layui-btn-danger{background-color:var(--lay-color-danger)}
|
||||
.layui-btn-checked{background-color:var(--lay-color-success)}
|
||||
.layui-btn-disabled,.layui-btn-disabled:active,.layui-btn-disabled:hover{border-color: var(--lay-color-border-2)!important;background-color: var(--lay-color-bg-2)!important;color: var(--lay-color-text-4)!important}
|
||||
.layui-btn-group .layui-btn{border-left:1px solid var(--lay-color-border-2)}
|
||||
.layui-btn-group .layui-btn-primary:hover{border-color:var(--lay-color-border-2);color:var(--lay-color-primary)}
|
||||
.layui-btn-group .layui-btn-primary:first-child{border-left:1px solid var(--lay-color-gray-5)}
|
||||
/*表单*/
|
||||
.layui-input,.layui-select,.layui-textarea{background-color: var(--lay-color-fill-2);color: var(--lay-color-text-2)}
|
||||
.layui-input:hover,.layui-textarea:hover{border-color: var(--lay-color-border-2)!important}
|
||||
.layui-input:focus,.layui-textarea:focus{border-color: var(--lay-color-secondary-hover)!important;background-color: var(--lay-color-bg-2);box-shadow: 0 0 0 3px rgba(22, 183, 119, 0.08);}
|
||||
.layui-input[disabled],.layui-select[disabled],.layui-textarea[disabled],.layui-input.layui-disabled,.layui-textarea.layui-disabled{background-color: var(--lay-color-fill-1);color: var(--lay-color-text-4);border-color: var(--lay-color-border-1)!important;box-shadow: 0 0 0 0;}
|
||||
.layui-form-danger+.layui-form-select .layui-input,.layui-form-danger:focus{border-color:var(--lay-color-danger)!important;box-shadow: 0 0 0 3px rgba(255, 87, 34, 0.08);}
|
||||
/* 输入框点缀 */
|
||||
.layui-input-prefix .layui-icon,.layui-input-split .layui-icon,.layui-input-suffix .layui-icon{color: var(--lay-color-gray-8)}
|
||||
.layui-input-wrap .layui-input:hover+.layui-input-split{border-color: var(--lay-color-border-2)}
|
||||
.layui-input-wrap .layui-input[disabled]:hover+.layui-input-split{border-color: var(--lay-color-border-1)}
|
||||
.layui-input-wrap .layui-input:focus+.layui-input-split{border-color: var(--lay-color-secondary-hover)}
|
||||
.layui-input-wrap .layui-input.layui-form-danger:focus + .layui-input-split{border-color: var(--lay-color-danger);}
|
||||
.layui-input-affix .layui-icon{color: var(--lay-color-text-2)}
|
||||
.layui-input-affix .layui-icon-clear{color:var(--lay-color-text-2)}
|
||||
.layui-input-affix .layui-icon:hover{color:var(--lay-color-text-3)}
|
||||
/* 数字输入框动态点缀 */
|
||||
.layui-input-wrap .layui-input-number .layui-icon-up{border-bottom-color:var(--lay-color-border-1)}
|
||||
.layui-input-wrap .layui-input[type="number"].layui-input-number-out-of-range{color:var(--lay-color-danger)}
|
||||
/* 下拉选择 */
|
||||
.layui-form-select{color:var(--lay-color-text-2)}
|
||||
.layui-form-select .layui-edge{border-top-color:var(--lay-color-gray-8)}
|
||||
.layui-form-select dl{border:1px solid var( --lay-color-border-2);background-color: var(--lay-color-bg-5);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-form-select dl dt{color:var(--lay-color-gray-8)}
|
||||
.layui-form-select dl dd:hover{background-color:var(--lay-color-active)}
|
||||
.layui-form-select dl dd.layui-select-tips{color:var(--lay-color-text-2)}
|
||||
.layui-form-select dl dd.layui-this{background-color: var(--lay-color-active);color: var(--lay-color-text-1)}
|
||||
.layui-form-select dl dd.layui-disabled,.layui-form-select dl dd:hover.layui-disabled{background-color: var(--lay-color-bg-5)}
|
||||
.layui-select-none{color:var(--lay-color-black-8)}
|
||||
.layui-select-disabled .layui-disabled{border-color:var(--lay-color-border-1)!important}
|
||||
.layui-select-disabled .layui-edge{border-top-color:var(--lay-color-gray-6)}
|
||||
/* 复选框 */
|
||||
.layui-form-checkbox{background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-checkbox>div{background-color:var(--lay-color-fill-3);color:var(--lay-color-text-2)}
|
||||
.layui-form-checkbox:hover>div{background-color: var(--lay-color-active)}
|
||||
.layui-form-checkbox>i{background-color: var(--lay-color-fill-1);border-top-color:var(--lay-color-border-1);border-right-color:var(--lay-color-border-1);border-bottom-color:var(--lay-color-border-1);border-left-color:initial;color:var(--lay-color-text-1)}
|
||||
.layui-form-checkbox:hover>i{border-color:var(--lay-color-border-2);color:var(--lay-color-text-4)}
|
||||
.layui-form-checked,.layui-form-checked:hover{border-color:var(--lay-color-secondary-active)}
|
||||
.layui-form-checked>div,.layui-form-checked:hover>div{background-color:var(--lay-color-secondary)}
|
||||
.layui-form-checked>i,.layui-form-checked:hover>i{color:var(--lay-color-secondary-hover)}
|
||||
.layui-form-checkbox.layui-checkbox-disabled>div{background-color: var(--lay-color-fill-3) !important;}
|
||||
/* 复选框-默认风格 */
|
||||
.layui-form-checkbox[lay-skin=primary]{background-image:none;background-color:initial;border-color:initial!important}
|
||||
.layui-form-checkbox[lay-skin=primary]>div{background-image:none;background-color:initial;color:var(--lay-color-text-2)}
|
||||
.layui-form-checkbox[lay-skin=primary]>i{border-color:var(--lay-color-border-1);background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-checkbox[lay-skin=primary]:hover>i{border-color:var(--lay-color-secondary-hover);color:var(--lay-color-text-1)}
|
||||
.layui-form-checked[lay-skin=primary]>i{background-color:var(--lay-color-secondary);color:var(--lay-color-text-1);border-color:var(--lay-color-secondary-active)!important}
|
||||
.layui-checkbox-disabled[lay-skin=primary] >div{background:none!important;color:var(--lay-color-text-4)!important}
|
||||
.layui-form-checked.layui-checkbox-disabled[lay-skin=primary]>i{background-color:var(--lay-color-fill-1)!important;border-color:var(--lay-color-border-2)!important}
|
||||
.layui-checkbox-disabled[lay-skin=primary]:hover>i{border-color:var(--lay-color-border-1)}
|
||||
.layui-form-checkbox[lay-skin="primary"]>.layui-icon-indeterminate:before{background-color: var(--lay-color-secondary-hover);opacity: 1;}
|
||||
.layui-form-checkbox[lay-skin="primary"]:hover>.layui-icon-indeterminate:before{opacity: 1;}
|
||||
.layui-form-checkbox[lay-skin="primary"]>.layui-icon-indeterminate{border-color: var(--lay-color-secondary-hover);}
|
||||
/* 复选框-开关风格 */
|
||||
.layui-form-switch{border-color:var(--lay-color-border-2);background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-switch>i{background-color:var(--lay-color-gray-4)}
|
||||
.layui-form-switch.layui-checkbox-disabled>i{background-color:var(--lay-color-gray-7);}
|
||||
.layui-form-switch>div{color:var(--lay-color-gray-8)!important}
|
||||
.layui-form-onswitch{border-color:var(--lay-color-secondary-active);background-color:var(--lay-color-secondary)}
|
||||
.layui-form-onswitch>i{background-color:var(--lay-color-gray-4)}
|
||||
.layui-form-onswitch>div{color:var(--lay-color-text-1)!important}
|
||||
.layui-checkbox-disabled{border-color:var(--lay-color-border-2)!important}
|
||||
.layui-checkbox-disabled>div{background-color:var(--lay-color-fill-3)!important;color: var(--lay-color-text-4)!important;}
|
||||
.layui-checkbox-disabled>i{border-color:var(--lay-color-border-2)!important}
|
||||
.layui-checkbox-disabled:hover>i{color:var(--lay-color-text-1)!important}
|
||||
.layui-form-switch.layui-checkbox-disabled>div{background-color:initial!important;color: var(--lay-color-text-3)!important;}
|
||||
/*复选框背景优化*/
|
||||
.layui-form-checkbox>i:before{opacity:0;filter:alpha(opacity=0)}
|
||||
.layui-form-checkbox:hover>i:before{opacity:1;filter:alpha(opacity=100)}
|
||||
.layui-form-checked.layui-checkbox-disabled:hover>i:before,.layui-form-checked:hover>i:before,.layui-form-checked>i:before{opacity:1;filter:alpha(opacity=100)}
|
||||
.layui-form-checkbox[lay-skin=primary]:hover>i:before{opacity:0;filter:alpha(opacity=0)}
|
||||
.layui-form-checked[lay-skin=primary]:hover>i:before{opacity:1;filter:alpha(opacity=100)}
|
||||
.layui-checkbox-disabled:hover>i:before{opacity:0;filter:alpha(opacity=0)}
|
||||
/*单选框*/
|
||||
.layui-form-radio>i{color:var(--lay-color-gray-8)}
|
||||
.layui-form-radio:hover>*,.layui-form-radioed,.layui-form-radioed>i{color:var(--lay-color-secondary)}
|
||||
.layui-radio-disabled>i{color:var(--lay-color-text-4)!important}
|
||||
.layui-radio-disabled>*{color:var(--lay-color-text-4)!important}
|
||||
/* 表单方框风格 */
|
||||
.layui-form-pane .layui-form-label{background-color:var(--lay-color-bg-2)}
|
||||
/** 分页 **/
|
||||
.layui-laypage a,.layui-laypage button,.layui-laypage input,.layui-laypage select,.layui-laypage span{border:1px solid var(--lay-color-border-2)}
|
||||
.layui-laypage a,.layui-laypage span{background-color: var(--lay-color-bg-2);color: var(--lay-color-text-2)}
|
||||
.layui-laypage a[data-page]{color:var(--lay-color-text-2)}
|
||||
.layui-laypage a:hover{color: var(--lay-color-primary)}
|
||||
.layui-laypage .layui-laypage-spr{color:var(--lay-color-text-3)}
|
||||
.layui-laypage .layui-laypage-curr em{color: var(--lay-color-white)}
|
||||
.layui-laypage .layui-laypage-curr .layui-laypage-em{background-color: var(--lay-color-primary)}
|
||||
.layui-laypage .layui-laypage-skip{color:var(--lay-color-text-3)}
|
||||
.layui-laypage button,.layui-laypage input{background-color: var(--lay-color-bg-2)}
|
||||
.layui-laypage input:focus,.layui-laypage select:focus{border-color: var(--lay-color-primary)!important}
|
||||
/** 流加载 **/
|
||||
.layui-flow-more{color:var(--lay-color-text-1)}
|
||||
.layui-flow-more a cite{background-color: var(--lay-color-bg-4);color: var(--lay-color-text-1)}
|
||||
.layui-flow-more a i{color:var(--lay-color-text-2)}
|
||||
/** 表格 **/
|
||||
.layui-table{background-color: var(--lay-color-bg-2);color: var(--lay-color-text-2)}
|
||||
.layui-table-mend{background-color: var(--lay-color-bg-2)}
|
||||
.layui-table-click,.layui-table-hover,.layui-table[lay-even] tbody tr:nth-child(even){background-color:var(--lay-color-fill-3)}
|
||||
.layui-table-checked{background-color: var(--lay-color-fill-2);color: var(--lay-color-text-1)}
|
||||
.layui-table-checked.layui-table-hover,.layui-table-checked.layui-table-click{background-color: var(--lay-color-fill-3);}
|
||||
.layui-table td,.layui-table th,.layui-table-col-set,.layui-table-fixed-r,.layui-table-grid-down,.layui-table-header,.layui-table-mend,.layui-table-page,.layui-table-tips-main,.layui-table-tool,.layui-table-total,.layui-table-view,.layui-table[lay-skin=line],.layui-table[lay-skin=row]{border-color: var(--lay-color-border-2)}
|
||||
.layui-table-view:after {background-color: var(--lay-color-border-2);}
|
||||
.layui-table-view .layui-table td[data-edit]:hover:after{border:1px solid var(--lay-color-primary-active)}
|
||||
.layui-table-loading-icon .layui-icon{color:var(--lay-color-gray-8);}
|
||||
.layui-table-page{background-color: var(--lay-color-bg-2);}
|
||||
.layui-table-page .layui-laypage a,
|
||||
.layui-table-page .layui-laypage span{border: none;}
|
||||
.layui-table-tool{background-color: var(--lay-color-bg-2);}
|
||||
.layui-table-tool .layui-inline[lay-event]{color:var(--lay-color-text-3);border:1px solid var(--lay-color-border-2)}
|
||||
.layui-table-tool .layui-inline[lay-event]:hover{border:1px solid var(--lay-color-border-3)}
|
||||
.layui-table-tool-panel{color: var(--lay-color-text-1); border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-5);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-table-tool-panel li:hover{background-color:var(--lay-color-active)}
|
||||
.layui-table-col-set{background-color: var(--lay-color-white)}
|
||||
.layui-table-sort .layui-table-sort-asc{border-bottom-color:var(--lay-color-gray-8)}
|
||||
.layui-table-sort .layui-table-sort-asc:hover{border-bottom-color:var(--lay-color-gray-11)}
|
||||
.layui-table-sort .layui-table-sort-desc{border-top-color:var(--lay-color-gray-8)}
|
||||
.layui-table-sort .layui-table-sort-desc:hover{border-top-color:var(--lay-color-gray-11)}
|
||||
.layui-table-sort[lay-sort=asc] .layui-table-sort-asc{border-bottom-color:var(--lay-color-gray-13)}
|
||||
.layui-table-sort[lay-sort=desc] .layui-table-sort-desc{border-top-color:var(--lay-color-gray-13)}
|
||||
.layui-table-cell .layui-table-link{color: var(--lay-color-lightblue-5)}
|
||||
.layui-table-body .layui-none{color:var(--lay-color-gray-8)}
|
||||
.layui-table-fixed-l{box-shadow:1px 0 8px rgba(0,0,0,1)}
|
||||
.layui-table-fixed-r{box-shadow:-1px 0 8px rgba(0,0,0,1)}
|
||||
.layui-table-edit{box-shadow:var(--lay-shadow-1);background-color: var(--lay-color-bg-2)}
|
||||
.layui-table-edit:focus{border-color:var(--lay-color-secondary)!important}
|
||||
select.layui-table-edit{border-color:var(--lay-color-border-2)}
|
||||
.layui-table-grid-down{background-color: var(--lay-color-bg-5);color:var(--lay-color-gray-8)}
|
||||
.layui-table-grid-down:hover{background-color:var(--lay-color-bg-5)}
|
||||
/* 单元格多行展开风格 */
|
||||
.layui-table-cell-c{background-color: var(--lay-color-gray-13);color: var(--lay-color-text-1); border-color: var(--lay-color-border-3);}
|
||||
.layui-table-cell-c:hover{border-color: var(--lay-color-secondary-hover);}
|
||||
/* 单元格 TIPS 展开风格 */
|
||||
body .layui-table-tips .layui-layer-content{box-shadow:var(--lay-shadow-3)}
|
||||
.layui-table-tips-main{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-3)}
|
||||
.layui-table-tips-c{background-color:var(--lay-color-gray-13);color: var(--lay-color-text-1)}
|
||||
.layui-table-tips-c:hover{background-color:var(--lay-color-gray-10)}
|
||||
/** 文件上传 **/
|
||||
.layui-upload-choose{color:var(--lay-color-gray-8)}
|
||||
.layui-upload-drag{border:1px dashed var( --lay-color-border-2);background-color: var(--lay-color-bg-4);color: var(--lay-color-text-2)}
|
||||
.layui-upload-drag .layui-icon{color: var(--lay-color-primary)}
|
||||
.layui-upload-drag[lay-over]{border-color: var(--lay-color-primary)}
|
||||
/* 基础菜单元素 */
|
||||
.layui-menu{background-color: var(--lay-color-bg-2)}
|
||||
.layui-menu li{color: var(--lay-color-text-1)}
|
||||
.layui-menu li:hover{background-color: var(--lay-color-bg-5)}
|
||||
.layui-menu li.layui-disabled,.layui-menu li.layui-disabled *{color:var(--lay-color-text-4)!important}
|
||||
.layui-menu .layui-menu-item-group>.layui-menu-body-title{color: var(--lay-color-text-3)}
|
||||
.layui-menu .layui-menu-item-none{color: var(--lay-color-text-3);}
|
||||
.layui-menu .layui-menu-item-divider{border-bottom:1px solid var(--lay-color-border-2)}
|
||||
.layui-menu .layui-menu-item-group:hover,
|
||||
.layui-menu .layui-menu-item-none:hover,
|
||||
.layui-menu .layui-menu-item-divider:hover{background: none;}
|
||||
.layui-menu .layui-menu-item-up>.layui-menu-body-title{color: var(--lay-color-text-1)}
|
||||
.layui-menu .layui-menu-item-down:hover>.layui-menu-body-title>.layui-icon,.layui-menu .layui-menu-item-up>.layui-menu-body-title:hover>.layui-icon{color: var(--lay-color-text-1)}
|
||||
.layui-menu .layui-menu-item-checked,.layui-menu .layui-menu-item-checked2{background-color:var(--lay-color-active)!important;color:var(--lay-color-secondary)}
|
||||
.layui-menu .layui-menu-item-checked a,.layui-menu .layui-menu-item-checked2 a{color:var(--lay-color-secondary)}
|
||||
.layui-menu .layui-menu-item-checked:after{border-right:3px solid var(--lay-color-secondary)}
|
||||
.layui-menu-body-title a{color: var(--lay-color-text-1)}
|
||||
.layui-menu-lg .layui-menu-body-title a:hover,.layui-menu-lg li:hover{color:var(--lay-color-secondary)}
|
||||
/* 下拉菜单 */
|
||||
.layui-dropdown{background-color: var(--lay-color-bg-5)}
|
||||
.layui-dropdown.layui-panel,.layui-dropdown .layui-panel{background-color: var(--lay-color-bg-5);box-shadow: var(--lay-shadow-2)}
|
||||
.layui-dropdown.layui-panel .layui-menu{background-color: var(--lay-color-bg-5)}
|
||||
/** 导航菜单 **/
|
||||
.layui-nav{background-color:var(--lay-color-black-6);color: var(--lay-color-white)}
|
||||
.layui-nav .layui-nav-item a{color: var(--lay-color-text-1);}
|
||||
.layui-nav .layui-this:after,.layui-nav-bar{background-color:var(--lay-color-secondary)}
|
||||
.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{color: var(--lay-color-text-1)}
|
||||
.layui-nav-child{box-shadow:var(--lay-shadow-2);border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-5)}
|
||||
.layui-nav .layui-nav-child a{color: var(--lay-color-text-1)}
|
||||
.layui-nav .layui-nav-child a:hover{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-nav-child dd.layui-this{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-nav-tree .layui-nav-child dd.layui-this,.layui-nav-tree .layui-nav-child dd.layui-this a,.layui-nav-tree .layui-this,.layui-nav-tree .layui-this>a,.layui-nav-tree .layui-this>a:hover{background-color: var(--lay-color-primary);color: var(--lay-color-white)}
|
||||
.layui-nav-itemed>a,.layui-nav-tree .layui-nav-title a,.layui-nav-tree .layui-nav-title a:hover{color: var(--lay-color-white)!important}
|
||||
.layui-nav-tree .layui-nav-bar{background-color:var(--lay-color-primary)}
|
||||
.layui-nav-tree .layui-nav-child{background: none; background-color:rgba(0, 0, 0, .3); border: none; box-shadow: none;}
|
||||
.layui-nav-tree .layui-nav-child a{color: var(--lay-color-white);color: var(--lay-color-text-1)}
|
||||
.layui-nav-tree .layui-nav-child a:hover{background: none; color: var(--lay-color-white)}
|
||||
.layui-nav.layui-bg-gray,.layui-nav-tree.layui-bg-gray{background-color: var(--lay-color-bg-2) !important;color: var(--lay-color-text-1);}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-child{background-color: rgba(0, 0, 0, .3) !important;}
|
||||
.layui-nav-tree.layui-bg-gray a,.layui-nav.layui-bg-gray .layui-nav-item a{color: var(--lay-color-text-1)}
|
||||
.layui-nav.layui-bg-gray .layui-nav-child{background-color: var(--lay-color-bg-5);}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-itemed>a{color: var(--lay-color-text-1)!important}
|
||||
.layui-nav.layui-bg-gray .layui-this a{color:var(--lay-color-secondary)}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-child dd.layui-this,.layui-nav-tree.layui-bg-gray .layui-nav-child dd.layui-this a,.layui-nav-tree.layui-bg-gray .layui-this,.layui-nav-tree.layui-bg-gray .layui-this>a{color:var(--lay-color-secondary)!important}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-bar{background-color:var(--lay-color-secondary)}
|
||||
/** 面包屑 **/
|
||||
.layui-breadcrumb a{color:var(--lay-color-gray-7)!important}
|
||||
.layui-breadcrumb a:hover{color:var(--lay-color-secondary)!important}
|
||||
.layui-breadcrumb a cite{color:var(--lay-color-gray-8)}
|
||||
.layui-breadcrumb span[lay-separator]{color:var(--lay-color-gray-7)}
|
||||
/** Tab 选项卡 **/
|
||||
.layui-tab .layui-tab-title:after{border-bottom-color: var(--lay-color-border-1);}
|
||||
.layui-tab-title .layui-this{color: var(--lay-color-text-2)}
|
||||
.layui-tab-title .layui-this:after{border-bottom-color: var(--lay-color-bg-1)}
|
||||
.layui-tab-bar{background-color: var(--lay-color-bg-3)}
|
||||
.layui-tab-more li.layui-this:after{border-bottom-color:var(--lay-color-border-1)}
|
||||
.layui-tab-title li .layui-tab-close{color:var(--lay-color-gray-8)}
|
||||
.layui-tab-title li .layui-tab-close:hover{background-color:var(--lay-color-danger);color: var(--lay-color-white)}
|
||||
.layui-tab-brief>.layui-tab-title .layui-this{color:var( --lay-color-primary)}
|
||||
.layui-tab-brief>.layui-tab-more li.layui-this:after,.layui-tab-brief>.layui-tab-title .layui-this:after{border-bottom:2px solid var(--lay-color-secondary)}
|
||||
.layui-tab-card{box-shadow: var(--lay-shadow-1)}
|
||||
.layui-tab-card>.layui-tab-title{background-color: var(--lay-color-bg-2)}
|
||||
.layui-tab-card>.layui-tab-title .layui-this{background-color: var(--lay-color-bg-1)}
|
||||
.layui-tab-card>.layui-tab-title .layui-this:after{border-bottom-color: var(--lay-color-bg-1)}
|
||||
.layui-tab-card>.layui-tab-more .layui-this{color:var(--lay-color-secondary)}
|
||||
|
||||
/** tabs 标签页 **/
|
||||
.layui-tabs-header:after,
|
||||
.layui-tabs-scroll:after{border-bottom-color: var(--lay-color-border-1);}
|
||||
.layui-tabs-card>.layui-tabs-header .layui-this{background-color: transparent;}
|
||||
.layui-tabs-card>.layui-tabs-header .layui-this:after{border-color: var(--lay-color-border-1); border-bottom-color: var(--lay-color-bg-1);}
|
||||
.layui-tabs-card.layui-panel>.layui-tabs-header .layui-this:after{border-bottom-color: var(--lay-color-bg-2);}
|
||||
.layui-tabs-bar .layui-icon{background-color: var(--lay-color-bg-1); color: var(--lay-color-text-2); border-color: var(--lay-color-border-1); box-shadow: 2px 0 5px 0 rgb(0 0 0 / 32%);}
|
||||
.layui-tabs-bar .layui-icon-next{box-shadow: -2px 0 5px 0 rgb(0 0 0 / 32%);}
|
||||
|
||||
/*时间线*/
|
||||
.layui-timeline-axis{background-color: var(--lay-color-bg-4);color:var(--lay-color-secondary)}
|
||||
.layui-timeline-axis:hover{color:var(--lay-color-red-6)}
|
||||
.layui-timeline-item:before{background-color: var(--lay-color-bg-3)}
|
||||
/*徽章*/
|
||||
.layui-badge,.layui-badge-dot,.layui-badge-rim{background-color:var(--lay-color-red-6);color: var(--lay-color-white)}
|
||||
.layui-badge-rim{background-color: var(--lay-color-white);color:var(--lay-color-black-6)}
|
||||
/* carousel 轮播 */
|
||||
.layui-carousel{background-color:var(--lay-color-gray-2)}
|
||||
.layui-carousel>[carousel-item]:before{color:var(--lay-color-gray-8);-moz-osx-font-smoothing:grayscale}
|
||||
.layui-carousel>[carousel-item]>*{background-color:var(--lay-color-gray-2)}
|
||||
.layui-carousel-arrow{background-color:rgba(0,0,0,.2);color: var(--lay-color-white)}
|
||||
.layui-carousel-arrow:hover,.layui-carousel-ind ul:hover{background-color:var(--lay-color-black)}
|
||||
.layui-carousel[lay-indicator=outside] .layui-carousel-ind ul{background-color:var(--lay-color-black)}
|
||||
.layui-carousel-ind ul{background-color:rgba(0,0,0,.2)}
|
||||
.layui-carousel-ind ul li{background-color:var(--lay-color-gray-3);background-color: var(--lay-color-text-3)}
|
||||
.layui-carousel-ind ul li:hover{background-color: var(--lay-color-white)}
|
||||
.layui-carousel-ind ul li.layui-this{background-color: var(--lay-color-white)}
|
||||
/** fixbar **/
|
||||
.layui-fixbar li{background-color:var(--lay-color-black-5);color: var(--lay-color-text-1)}
|
||||
/** 表情面板 **/
|
||||
body .layui-util-face .layui-layer-content{background-color: var(--lay-color-bg-5);color:var(--lay-color-text-2)}
|
||||
.layui-util-face ul{border:1px solid var(--lay-color-border-3);background-color: var(--lay-color-bg-5);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-util-face ul li{border:1px solid var(--lay-color-border-2)}
|
||||
.layui-util-face ul li:hover{border:1px solid var(--lay-color-red-7);background: var(--lay-color-text-1)}
|
||||
/** 代码文本修饰 **/
|
||||
.layui-code{border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-white);color: var(--lay-color-text-2)}
|
||||
/** 穿梭框 **/
|
||||
.layui-transfer-box,.layui-transfer-header,.layui-transfer-search{border-color: var(--lay-color-border-2)}
|
||||
.layui-transfer-box{background-color: var(--lay-color-bg-2)}
|
||||
.layui-transfer-search .layui-icon-search{color:var(--lay-color-gray-8)}
|
||||
.layui-transfer-active .layui-btn{background-color:var( --lay-color-secondary);border-color:var( --lay-color-secondary);color: var(--lay-color-white)}
|
||||
.layui-transfer-active .layui-btn-disabled{background-color:var(--lay-color-gray-2);border-color:var(--lay-color-gray-3);color:var(--lay-color-gray-8)}
|
||||
.layui-transfer-data li:hover{background-color:var(--lay-color-active)}
|
||||
/* chrome 105 */
|
||||
.layui-transfer-data li:hover:has([lay-filter="layTransferCheckbox"][disabled]){background-color:var(--lay-color-bg-2)}
|
||||
.layui-transfer-data .layui-none{color:var(--lay-color-gray-7)}
|
||||
/** 评分组件 **/
|
||||
.layui-rate li i.layui-icon{color:var(--lay-color-orange-6)}
|
||||
/** 颜色选择器 **/
|
||||
.layui-colorpicker{border:1px solid var(--lay-color-border-1)}
|
||||
.layui-colorpicker:hover{border-color: var(--lay-color-border-2)}
|
||||
.layui-colorpicker-trigger-span{border:1px solid var(--lay-color-border-1)}
|
||||
.layui-colorpicker-trigger-i{color: var(--lay-color-white)}
|
||||
.layui-colorpicker-trigger-i.layui-icon-close{color:var(--lay-color-black-7)}
|
||||
.layui-colorpicker-main{background: var(--lay-color-bg-2);border:1px solid var( --lay-color-border-2);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-colorpicker-basis-white{background:linear-gradient(90deg, #fff,hsla(0,0%,100%,0))} /* danger: 勿改*/
|
||||
.layui-colorpicker-basis-black{background:linear-gradient(0deg,#000,transparent)} /* danger: 勿改*/
|
||||
.layui-colorpicker-basis-cursor{border:1px solid var(--lay-color-white)}
|
||||
.layui-colorpicker-side{background:linear-gradient(linear-gradient(#F00, #FF0, #0F0, #0FF, #00F, #F0F, #F00))} /* danger: 勿改*/
|
||||
.layui-colorpicker-side-slider{box-shadow:var(--lay-shadow-1);background: var(--lay-color-white);border:1px solid var(--lay-color-gray-2)}
|
||||
.layui-colorpicker-alpha-slider{box-shadow:var(--lay-shadow-1);background: var(--lay-color-white);border:1px solid var(--lay-color-gray-2)}
|
||||
.layui-colorpicker-pre.layui-this{box-shadow:var(--lay-shadow-1)}
|
||||
.layui-colorpicker-pre.selected{box-shadow:var(--lay-shadow-1)}
|
||||
.layui-colorpicker-main-input input.layui-input{color: var(--lay-color-text-2)}
|
||||
/** 滑块 **/
|
||||
.layui-slider{background: var( --lay-color-bg-5)}
|
||||
.layui-slider-step{background: var(--lay-color-fill-4)}
|
||||
.layui-slider-wrap-btn{background: var(--lay-color-bg-4)}
|
||||
.layui-slider-tips{color: var(--lay-color-text-1);background:var(--lay-color-black);box-shadow: var(--lay-shadow-3)}
|
||||
.layui-slider-tips:after{border-color:var(--lay-color-black) transparent transparent transparent}
|
||||
.layui-slider-input{border:1px solid var(--lay-color-border-1)}
|
||||
.layui-slider-input-btn{border-left:1px solid var(--lay-color-border-1)}
|
||||
.layui-slider-input-btn i{color:var(--lay-color-gray-9)}
|
||||
.layui-slider-input-btn i:first-child{border-bottom:1px solid var(--lay-color-border-1)}
|
||||
.layui-slider-input-btn i:hover{color:var(--lay-color-primary)}
|
||||
/** 树组件 **/
|
||||
.layui-tree-line .layui-tree-set .layui-tree-set:after{border-top:1px dotted var(--lay-color-gray-7)}
|
||||
.layui-tree-entry:hover{background-color: var(--lay-color-bg-4)}
|
||||
.layui-tree-line .layui-tree-entry:hover{background-color:var(--lay-color-black)}
|
||||
.layui-tree-line .layui-tree-entry:hover .layui-tree-txt{color:var(--lay-color-text-3)}
|
||||
.layui-tree-entry:hover:has(span.layui-tree-txt.layui-disabled){background-color: transparent !important}
|
||||
.layui-tree-line .layui-tree-set:before{border-left:1px dotted var(--lay-color-gray-7)}
|
||||
.layui-tree-iconClick{color:var(--lay-color-gray-7)}
|
||||
.layui-tree-icon{border:1px solid var(--lay-color-gray-8)}
|
||||
.layui-tree-icon .layui-icon{color:var(--lay-color-text-1)}
|
||||
.layui-tree-iconArrow:after{border-color:transparent transparent transparent var(--lay-color-gray-7)}
|
||||
.layui-tree-txt{color:var(--lay-color-text-2)}
|
||||
.layui-tree-search{color:var(--lay-color-black-7)}
|
||||
.layui-tree-btnGroup .layui-icon:hover{color:var(--lay-color-text-2)}
|
||||
.layui-tree-editInput{background-color:var(--lay-color-fill-2)}
|
||||
.layui-tree-emptyText{color:var(--lay-color-text-2)}
|
||||
/*code 不处理*/
|
||||
.layui-code-view{border:1px solid var(--lay-color-border-1);}
|
||||
.layui-code-view:not(.layui-code-hl){background-color: var(--lay-color-bg-2);color: var(--lay-color-text-2);}
|
||||
.layui-code-header{border-bottom: 1px solid var(--lay-color-border-1); background-color: var(--lay-color-bg-2)}
|
||||
.layui-code-header > .layui-code-header-about{color: var(--lay-color-text-2);}
|
||||
.layui-code-view:not(.layui-code-hl) .layui-code-ln-side{border-color: var(--lay-color-border-1); background-color: var(--lay-color-bg-2);}
|
||||
.layui-code-nowrap > .layui-code-ln-side{background: none !important;}
|
||||
.layui-code-fixbar > span{color: var(--lay-color-text-3);}
|
||||
.layui-code-fixbar > span:hover{color: var(--lay-color-secondary-hover);}
|
||||
|
||||
.layui-code-theme-dark,
|
||||
.layui-code-theme-dark > .layui-code-header{border-color: rgb(126 122 122 / 15%); background-color: #1f1f1f;}
|
||||
.layui-code-theme-dark{border-width: 1px; color: #ccc;}
|
||||
.layui-code-theme-dark > .layui-code-ln-side{border-right-color: #2a2a2a; background: none; color: #6e7681;}
|
||||
|
||||
.layui-code-view.layui-code-hl > .layui-code-ln-side{background-color: transparent;}
|
||||
.layui-code-theme-dark.layui-code-hl,
|
||||
.layui-code-theme-dark.layui-code-hl > .layui-code-ln-side{border-color: rgb(126 122 122 / 15%);}
|
||||
|
||||
.layui-code-full{background-color: var(--lay-color-bg-1)}
|
||||
/*日期选择器*/
|
||||
.layui-laydate-header i{color:var(--lay-color-gray-8)}
|
||||
.laydate-day-holidays:before{color:var(--lay-color-red-6)}
|
||||
.layui-laydate .layui-this .laydate-day-holidays:before{color: var(--lay-color-white)}
|
||||
.layui-laydate-footer span{border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-5)}
|
||||
.layui-laydate-footer span:hover{color:var(--lay-color-secondary)}
|
||||
.layui-laydate-footer span.layui-laydate-preview{border-color:transparent!important;}
|
||||
.layui-laydate-footer span.layui-laydate-preview:hover{color:var(--lay-color-text-1) !important}
|
||||
.layui-laydate-shortcut+.layui-laydate-main{border-left:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate .layui-laydate-list{background-color: var(--lay-color-bg-5)}
|
||||
.layui-laydate-hint{color:var(--lay-color-danger)}
|
||||
.layui-laydate-range .laydate-main-list-1 .layui-laydate-content,.layui-laydate-range .laydate-main-list-1 .layui-laydate-header{border-left:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate,.layui-laydate-hint{border-color: var(--lay-color-border-2);box-shadow:var(--lay-shadow-3);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-laydate{box-shadow: var(--lay-shadow-2)}
|
||||
.layui-laydate-hint{border-color:var(--lay-color-border-1)}
|
||||
.layui-laydate-header{border-bottom:1px solid var( --lay-color-border-2)}
|
||||
.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:var(--lay-color-secondary)}
|
||||
.layui-laydate-content th{color: var(--lay-color-text-1)}
|
||||
.layui-laydate-content td{color: var(--lay-color-text-1)}
|
||||
.layui-laydate-content td.laydate-day-now{color:var(--lay-color-secondary)}
|
||||
.layui-laydate-content td.laydate-day-now:after{border:1px solid var(--lay-color-secondary)}
|
||||
.layui-laydate-linkage .layui-laydate-content td.laydate-selected>div{background-color:var(--lay-color-green-8);}
|
||||
.layui-laydate-linkage .laydate-selected:hover>div{background-color:var(--lay-color-green-8)!important}
|
||||
.layui-laydate-content td>div:hover,.layui-laydate-list li:hover,.layui-laydate-shortcut>li:hover{background-color: var(--lay-color-fill-2);color: var(--lay-color-text-2)}
|
||||
.layui-laydate-content td.laydate-disabled>div:hover{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-4)}
|
||||
.laydate-time-list li ol{border:1px solid var(--lay-color-border-2)}
|
||||
.laydate-time-list>li:hover{background: 0 0;}
|
||||
.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color: var(--lay-color-text-3)}
|
||||
.layui-laydate-linkage .laydate-selected.laydate-day-next>div,.layui-laydate-linkage .laydate-selected.laydate-day-prev>div{background: none!important}
|
||||
.layui-laydate-footer{border-top:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate-hint{color:var(--lay-color-danger)}
|
||||
.laydate-day-mark::after{background-color:var(--lay-color-secondary)}
|
||||
.layui-laydate-footer span[lay-type=date]{color:var(--lay-color-secondary)}
|
||||
.layui-laydate .layui-this,.layui-laydate .layui-this>div{background-color:var(--lay-color-secondary)!important;color: var(--lay-color-white)!important}
|
||||
.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{color: var(--lay-color-text-4)!important}
|
||||
.layui-laydate .layui-this.laydate-disabled,.layui-laydate .layui-this.laydate-disabled>div{background-color: var(--lay-color-fill-1) !important;color: var(--lay-color-text-4) !important;}
|
||||
.laydate-theme-molv .layui-laydate-header{background-color:var(--lay-color-primary)}
|
||||
.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:var(--lay-color-gray-2)}
|
||||
.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color: var(--lay-color-white)}
|
||||
.laydate-theme-molv .layui-laydate-content{border:1px solid var(--lay-color-border-2)}
|
||||
.laydate-theme-molv .layui-this, .laydate-theme-molv .layui-this>div{background-color: var(--lay-color-primary) !important;}
|
||||
.laydate-theme-molv .layui-laydate-footer{border:1px solid var(--lay-color-border-2)}
|
||||
.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead{border:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate-linkage.laydate-theme-grid .laydate-selected,.layui-laydate-linkage.laydate-theme-grid .laydate-selected:hover{background-color:var(--lay-color-gray-3)!important;color:var(--lay-color-primary)!important}
|
||||
.layui-laydate-linkage.laydate-theme-grid .laydate-selected.laydate-day-next,.layui-laydate-linkage.laydate-theme-grid .laydate-selected.laydate-day-prev{color:var(--lay-color-gray-6)!important}
|
||||
.layui-laydate.laydate-theme-circle .layui-laydate-content table td.layui-this{background-color:transparent!important}
|
||||
/*layer*/
|
||||
.layui-layer{background-color: var(--lay-color-bg-3);box-shadow:var(--lay-shadow-3)}
|
||||
.layui-layer-border{border:1px solid var(--lay-color-border-2);box-shadow:var(--lay-shadow-3)}
|
||||
.layui-layer-move{background-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-title{border-bottom:1px solid var(--lay-color-border-2);color: var(--lay-color-text-1)}
|
||||
.layui-layer-setwin span{color: var(--lay-color-text-1)}
|
||||
.layui-layer-setwin .layui-layer-min:before{border-bottom-color:var(--lay-color-text-1)}
|
||||
.layui-layer-setwin .layui-layer-min:hover:before{border-bottom-color:var(--lay-color-info-hover)}
|
||||
.layui-layer-setwin .layui-layer-max:after,.layui-layer-setwin .layui-layer-max:before{border:1px solid var(--lay-color-text-3)}
|
||||
.layui-layer-setwin .layui-layer-max:hover:after,.layui-layer-setwin .layui-layer-max:hover:before{border-color:var(--lay-color-info-hover)}
|
||||
.layui-layer-setwin .layui-layer-maxmin:after,.layui-layer-setwin .layui-layer-maxmin:before{background-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-setwin .layui-layer-close2{color:var(--lay-color-text-1);background-color:var(--lay-color-gray-10)}
|
||||
.layui-layer-setwin .layui-layer-close2:hover{background-color:var(--lay-color-normal)}
|
||||
.layui-layer-btn a{border:1px solid var(--lay-color-border-2);background-color: var( --lay-color-bg-3);color: var(--lay-color-text-2)}
|
||||
.layui-layer-btn .layui-layer-btn0{border-color: transparent;background-color: var(--lay-color-normal);color: var(--lay-color-text-1)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-layer-face{color:var(--lay-color-gray-9)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-tips{color:var(--lay-color-warning)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-success{color: var(--lay-color-success)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-error{top: 19px; color: var(--lay-color-danger)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-question{color: var(--lay-color-warning);}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-lock{color: var(--lay-color-gray-10)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-face-cry{color:var(--lay-color-danger)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-face-smile{color:var(--lay-color-success)}
|
||||
.layui-layer-rim{border:6px solid var(--lay-color-gray-8);border:6px solid var(--lay-color-border-2)}
|
||||
.layui-layer-msg{border:1px solid var( --lay-color-border-1)}
|
||||
.layui-layer-hui{background-color: var(--lay-color-bg-3);color: var(--lay-color-text-1)}
|
||||
.layui-layer-hui .layui-layer-close{color: var(--lay-color-white)}
|
||||
.layui-layer-loading-icon{color:var(--lay-color-gray-9)}
|
||||
.layui-layer-loading-2:after,.layui-layer-loading-2:before{border:3px solid var(--lay-color-gray-6)}
|
||||
.layui-layer-loading-2:after{border-color:transparent;border-left-color: var(--lay-color-normal)}
|
||||
.layui-layer-tips .layui-layer-content{box-shadow: var(--lay-shadow-3);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-tips i.layui-layer-TipsG{border-color:transparent}
|
||||
.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{border-right-color:var(--lay-color-black)}
|
||||
.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{border-bottom-color:var(--lay-color-black)}
|
||||
.layui-layer-lan .layui-layer-title{background:var(--lay-color-blue-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-lan .layui-layer-btn{border-top:1px solid var(--lay-color-border-3)}
|
||||
.layui-layer-lan .layui-layer-btn a{background: var(--lay-color-white);border-color:var(--lay-color-border-3);color: var(--lay-color-black-7)}
|
||||
.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background: var(--lay-color-gray-7)}
|
||||
.layui-layer-molv .layui-layer-title{background:var(--lay-color-layuigreen-6);color: var(--lay-color-text-1)}
|
||||
.layui-layer-molv .layui-layer-btn a{background:var(--lay-color-layuigreen-6);border-color:var(--lay-color-layuigreen-6)}
|
||||
.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:var(--lay-color-gray-7)}
|
||||
.layui-layer-win10{border-color: var(--lay-color-border-2)}
|
||||
.layui-layer-win10 .layui-layer-btn{background-color: var(--lay-color-bg-2);border-color: var(--lay-color-border-2)}
|
||||
.layui-layer-win10.layui-layer-dialog .layui-layer-content{color: var(--lay-color-blue-7)}
|
||||
.layui-layer-win10 .layui-layer-btn .layui-layer-btn0{border-color: var(--lay-color-blue-9);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-win10 .layui-layer-btn .layui-layer-btn1{border-color: var(--lay-color-border-2);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-win10 .layui-layer-btn a:hover{background-color: var(--lay-color-blue-10);border-color: var(--lay-color-blue-8)}
|
||||
.layui-layer-prompt .layui-layer-input{border:1px solid var(--lay-color-border-2);color: var(--lay-color-text-2)}
|
||||
.layui-layer-tab{box-shadow:var(--lay-shadow-3)}
|
||||
.layui-layer-tab .layui-layer-title span.layui-this{border-left:1px solid var(--lay-color-border-2);border-right:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-3)}
|
||||
.layui-layer-photos{background: none; box-shadow: none;}
|
||||
.layui-layer-photos-prev,.layui-layer-photos-next{color:var(--lay-color-gray-9)}
|
||||
.layui-layer-photos-prev:hover,.layui-layer-photos-next:hover{color:var(--lay-color-text-1)}
|
||||
.layui-layer-photos-toolbar{background-color:#333;background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-photos-toolbar *{color: var(--lay-color-text-1)}
|
||||
.layui-layer-photos-toolbar a:hover{color: var(--lay-color-text-2)}
|
||||
.layui-layer-photos-header > span:hover{background-color: var(--lay-color-fill-2)}
|
||||
.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{border-right-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{border-bottom-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-prompt .layui-layer-input{border:1px solid var(--lay-color-border-2);color:var(--lay-color-text-1);background-color:var(--lay-color-black)}
|
||||
.layui-layer-prompt .layui-layer-input:focus{outline:0}
|
||||
|
||||
/*fix style*/
|
||||
.layui-layer-loading{background:0 0;box-shadow:0 0}
|
||||
.layui-btn-primary{border-color:transparent}
|
||||
.layui-btn-group .layui-btn:first-child{border-left:none}
|
||||
.layui-btn-group .layui-btn-primary:hover{border-top-color:transparent; border-bottom-color: transparent;}
|
||||
.layui-menu li:hover{background-color:var(--lay-color-fill-2)}
|
||||
.layui-nav-child dd.layui-this{background-color:var(--lay-color-fill-2)}
|
||||
.layui-nav .layui-nav-child a:hover{background-color:var(--lay-color-fill-2)}
|
||||
.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{background-color: var(--lay-color-fill-2)}
|
||||
.layui-nav-child dd.layui-this{background-color: var(--lay-color-fill-2)}
|
||||
.layui-tab-card>.layui-tab-title .layui-this:after{border-bottom-color:var(--lay-color-bg-1)}
|
||||
.layui-form-select dl dd:hover{background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-select dl dd.layui-this{background-color:var(--lay-color-fill-2)}
|
||||
.layui-laypage button{color:var(--lay-color-text-1)}
|
||||
.layui-table[lay-even] tbody tr:nth-child(even){background-color:var(--lay-color-fill-4)}
|
||||
.layui-menu .layui-menu-item-checked,.layui-menu .layui-menu-item-checked2{background-color:var(--lay-color-fill-2)!important}
|
||||
.layui-input-split{background-color: var(--lay-color-bg-2);}
|
||||
.layui-input-wrap .layui-input-prefix.layui-input-split{border-width: 1px;}
|
||||
.layui-input-wrap .layui-input-split:has(+.layui-input:hover) {border-color: var(--lay-color-border-2);}
|
||||
.layui-input-wrap .layui-input-split:has(+.layui-input:focus) {border-color: var(--lay-color-secondary-hover);}
|
||||
.layui-layer-tab .layui-layer-title span:first-child{border-left: none !important;}
|
||||
.layui-slider-input.layui-input,
|
||||
.layui-slider-input .layui-input {background-color: var(--lay-color-bg-2);}
|
||||
|
||||
/*# sourceMappingURL=layui-theme-dark.css.map */
|
||||
1
web/static/src/layui-theme-dark.css.map
Normal file
1
web/static/src/layui-theme-dark.css.map
Normal file
File diff suppressed because one or more lines are too long
534
web/static/src/override.css
Normal file
534
web/static/src/override.css
Normal file
@@ -0,0 +1,534 @@
|
||||
blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,li,ol,p,pre,td,textarea,th,ul{-webkit-tap-highlight-color: rgba(0, 0, 0, 0)} /*danger: 勿改*/
|
||||
body{color:var(--lay-color-text-2);background-color: var(--lay-color-bg-1); color-scheme: dark;}
|
||||
hr{border-bottom:1px solid var(--lay-color-border-2)!important}
|
||||
a{color:var(--lay-color-text-1);}
|
||||
a:hover{color:var(--lay-color-text-3)}
|
||||
/* 三角形 */
|
||||
.layui-edge{border-color:transparent}
|
||||
.layui-edge-top{border-bottom-color:var(--lay-color-border-4)}
|
||||
.layui-edge-right{border-left-color:var(--lay-color-border-4)}
|
||||
.layui-edge-bottom{border-top-color:var(--lay-color-border-4)}
|
||||
.layui-edge-left{border-right-color:var(--lay-color-border-4)}
|
||||
/* 禁用文字 */
|
||||
.layui-disabled,.layui-disabled:hover{color:var(--lay-color-text-4)!important}
|
||||
/* 图标 */
|
||||
.layui-icon{-moz-osx-font-smoothing:grayscale}
|
||||
/* admin 布局 */
|
||||
.layui-layout-admin .layui-header{background-color:var(--lay-color-bg-2)}
|
||||
.layui-layout-admin .layui-footer{box-shadow:-1px 0 4px rgb(0 0 0 / 12%);background-color:var(--lay-color-bg-2)}
|
||||
.layui-layout-admin .layui-logo{color:var(--lay-color-primary);box-shadow:0 1px 2px 0 rgb(0 0 0 / 15%)}
|
||||
/* 引用 */
|
||||
.layui-elem-quote{border-left:5px solid var(--lay-color-secondary);background-color:var(--lay-color-fill-1)}
|
||||
.layui-quote-nm{border-color: var(--lay-color-fill-1)}
|
||||
/* 进度条 */
|
||||
.layui-progress{background-color: var(--lay-color-bg-3)}
|
||||
.layui-progress-bar{background-color:var( --lay-color-secondary)}
|
||||
.layui-progress-text{color:var(--lay-color-text-2)}
|
||||
.layui-progress-big .layui-progress-text{color: var(--lay-color-text-1)}
|
||||
/* 折叠面板 */
|
||||
.layui-colla-title{color: var(--lay-color-text-1);background-color: var(--lay-color-bg-2)}
|
||||
.layui-colla-content{color:var(--lay-color-text-2)}
|
||||
/* 卡片面板 */
|
||||
.layui-card{background-color: var(--lay-color-bg-2);box-shadow:var(--lay-shadow-1)}
|
||||
.layui-card-header{border-bottom:1px solid var(--lay-color-border-2);color:var(--lay-color-text-1)}
|
||||
/* 常规面板 */
|
||||
.layui-panel{box-shadow:var(--lay-shadow-1);background-color: var( --lay-color-bg-2);color: var(--lay-color-text-1)}
|
||||
.layui-menu-body-panel{box-shadow: var(--lay-shadow-2)}
|
||||
/* 窗口面板 */
|
||||
.layui-panel-window{border-top:5px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-2)}
|
||||
/* 背景颜色 */
|
||||
.layui-bg-red{background-color:var(--lay-color-red-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-orange{background-color:var(--lay-color-orange-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-green{background-color:var(--lay-color-layuigreen-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-cyan{background-color:var(--lay-color-cyan-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-blue{background-color: var(--lay-color-blue-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-black{background-color:var(--lay-color-black-6)!important;color: var(--lay-color-white)!important}
|
||||
.layui-bg-purple{background-color: var(--lay-color-purple-6)!important; color: var(--lay-color-white)!important;}
|
||||
.layui-bg-gray{background-color:var(--lay-color-gray-1)!important;color: var(--lay-color-black-6)!important}
|
||||
/* 徽章 */
|
||||
.layui-badge-rim,.layui-border,.layui-colla-content,.layui-colla-item,.layui-collapse,.layui-elem-field,.layui-form-pane .layui-form-item[pane],.layui-form-pane .layui-form-label,.layui-input,.layui-input-split,.layui-panel,.layui-select,.layui-tab-bar,.layui-tab-card,.layui-tab-title,.layui-tab-title .layui-this:after,.layui-textarea{border-color: var(--lay-color-border-1)}
|
||||
/* 边框颜色 */
|
||||
.layui-border{color:var(--lay-color-text-1)!important}
|
||||
.layui-border-red{border-color:var(--lay-color-red-6)!important;color:var(--lay-color-red-6)!important}
|
||||
.layui-border-orange{border-color:var(--lay-color-orange-6)!important;color:var(--lay-color-orange-6)!important}
|
||||
.layui-border-green{border-color:var(--lay-color-layuigreen-6)!important;color:var(--lay-color-layuigreen-6)!important}
|
||||
.layui-border-cyan{border-color:var(--lay-color-cyan-6)!important;color:var(--lay-color-cyan-6)!important}
|
||||
.layui-border-blue{border-color: var(--lay-color-blue-6)!important;color: var(--lay-color-blue-6)!important}
|
||||
.layui-border-purple{border-color: var(--lay-color-purple-6)!important; color: var(--lay-color-purple-6)!important;}
|
||||
.layui-border-black{border-color:var(--lay-color-black-6)!important;color:var(--lay-color-text-1)!important}
|
||||
/* 文本区域 */
|
||||
.layui-text{color:var(--lay-color-text-2)}
|
||||
.layui-text-em,.layui-word-aux{color: var(--lay-color-text-3)!important}
|
||||
.layui-text a:not(.layui-btn){color:var(--lay-color-lightblue-6)}
|
||||
.layui-text blockquote:not(.layui-elem-quote){border-left:5px solid var(--lay-color-border-4)}
|
||||
/* 字体颜色 */
|
||||
.layui-font-red{color:var(--lay-color-red-6)!important}
|
||||
.layui-font-orange{color:var(--lay-color-orange-6)!important}
|
||||
.layui-font-green{color:var(--lay-color-layuigreen-6)!important}
|
||||
.layui-font-cyan{color:var(--lay-color-cyan-6)!important}
|
||||
.layui-font-blue{color:var(--lay-color-lightblue-6)!important}
|
||||
.layui-font-black{color:var(--lay-color-black)!important}
|
||||
.layui-font-purple{color:var(--lay-color-purple-6)!important;}
|
||||
.layui-font-gray{color:var(--lay-color-gray-7)!important}
|
||||
/* 按钮 */
|
||||
.layui-btn{border:1px solid transparent;background-color:var(--lay-color-primary);color: var(--lay-color-text-1)}
|
||||
.layui-btn:hover{color: var(--lay-color-text-2)}
|
||||
.layui-btn-primary{border-color:var(--lay-color-border-2);color:var(--lay-color-text-1);background-color: var(--lay-color-bg-4)}
|
||||
.layui-btn-primary:hover{border-color: transparent;color:var(--lay-color-text-2)}
|
||||
.layui-btn-normal{background-color: var(--lay-color-normal)}
|
||||
.layui-btn-warm{background-color:var(--lay-color-warning)}
|
||||
.layui-btn-danger{background-color:var(--lay-color-danger)}
|
||||
.layui-btn-checked{background-color:var(--lay-color-success)}
|
||||
.layui-btn-disabled,.layui-btn-disabled:active,.layui-btn-disabled:hover{border-color: var(--lay-color-border-2)!important;background-color: var(--lay-color-bg-2)!important;color: var(--lay-color-text-4)!important}
|
||||
.layui-btn-group .layui-btn{border-left:1px solid var(--lay-color-border-2)}
|
||||
.layui-btn-group .layui-btn-primary:hover{border-color:var(--lay-color-border-2);color:var(--lay-color-primary)}
|
||||
.layui-btn-group .layui-btn-primary:first-child{border-left:1px solid var(--lay-color-gray-5)}
|
||||
/*表单*/
|
||||
.layui-input,.layui-select,.layui-textarea{background-color: var(--lay-color-fill-2);color: var(--lay-color-text-2)}
|
||||
.layui-input:hover,.layui-textarea:hover{border-color: var(--lay-color-border-2)!important}
|
||||
.layui-input:focus,.layui-textarea:focus{border-color: var(--lay-color-secondary-hover)!important;background-color: var(--lay-color-bg-2);box-shadow: 0 0 0 3px rgba(22, 183, 119, 0.08);}
|
||||
.layui-input[disabled],.layui-select[disabled],.layui-textarea[disabled],.layui-input.layui-disabled,.layui-textarea.layui-disabled{background-color: var(--lay-color-fill-1);color: var(--lay-color-text-4);border-color: var(--lay-color-border-1)!important;box-shadow: 0 0 0 0;}
|
||||
.layui-form-danger+.layui-form-select .layui-input,.layui-form-danger:focus{border-color:var(--lay-color-danger)!important;box-shadow: 0 0 0 3px rgba(255, 87, 34, 0.08);}
|
||||
/* 输入框点缀 */
|
||||
.layui-input-prefix .layui-icon,.layui-input-split .layui-icon,.layui-input-suffix .layui-icon{color: var(--lay-color-gray-8)}
|
||||
.layui-input-wrap .layui-input:hover+.layui-input-split{border-color: var(--lay-color-border-2)}
|
||||
.layui-input-wrap .layui-input[disabled]:hover+.layui-input-split{border-color: var(--lay-color-border-1)}
|
||||
.layui-input-wrap .layui-input:focus+.layui-input-split{border-color: var(--lay-color-secondary-hover)}
|
||||
.layui-input-wrap .layui-input.layui-form-danger:focus + .layui-input-split{border-color: var(--lay-color-danger);}
|
||||
.layui-input-affix .layui-icon{color: var(--lay-color-text-2)}
|
||||
.layui-input-affix .layui-icon-clear{color:var(--lay-color-text-2)}
|
||||
.layui-input-affix .layui-icon:hover{color:var(--lay-color-text-3)}
|
||||
/* 数字输入框动态点缀 */
|
||||
.layui-input-wrap .layui-input-number .layui-icon-up{border-bottom-color:var(--lay-color-border-1)}
|
||||
.layui-input-wrap .layui-input[type="number"].layui-input-number-out-of-range{color:var(--lay-color-danger)}
|
||||
/* 下拉选择 */
|
||||
.layui-form-select{color:var(--lay-color-text-2)}
|
||||
.layui-form-select .layui-edge{border-top-color:var(--lay-color-gray-8)}
|
||||
.layui-form-select dl{border:1px solid var( --lay-color-border-2);background-color: var(--lay-color-bg-5);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-form-select dl dt{color:var(--lay-color-gray-8)}
|
||||
.layui-form-select dl dd:hover{background-color:var(--lay-color-active)}
|
||||
.layui-form-select dl dd.layui-select-tips{color:var(--lay-color-text-2)}
|
||||
.layui-form-select dl dd.layui-this{background-color: var(--lay-color-active);color: var(--lay-color-text-1)}
|
||||
.layui-form-select dl dd.layui-disabled,.layui-form-select dl dd:hover.layui-disabled{background-color: var(--lay-color-bg-5)}
|
||||
.layui-select-none{color:var(--lay-color-black-8)}
|
||||
.layui-select-disabled .layui-disabled{border-color:var(--lay-color-border-1)!important}
|
||||
.layui-select-disabled .layui-edge{border-top-color:var(--lay-color-gray-6)}
|
||||
/* 复选框 */
|
||||
.layui-form-checkbox{background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-checkbox>div{background-color:var(--lay-color-fill-3);color:var(--lay-color-text-2)}
|
||||
.layui-form-checkbox:hover>div{background-color: var(--lay-color-active)}
|
||||
.layui-form-checkbox>i{background-color: var(--lay-color-fill-1);border-top-color:var(--lay-color-border-1);border-right-color:var(--lay-color-border-1);border-bottom-color:var(--lay-color-border-1);border-left-color:initial;color:var(--lay-color-text-1)}
|
||||
.layui-form-checkbox:hover>i{border-color:var(--lay-color-border-2);color:var(--lay-color-text-4)}
|
||||
.layui-form-checked,.layui-form-checked:hover{border-color:var(--lay-color-secondary-active)}
|
||||
.layui-form-checked>div,.layui-form-checked:hover>div{background-color:var(--lay-color-secondary)}
|
||||
.layui-form-checked>i,.layui-form-checked:hover>i{color:var(--lay-color-secondary-hover)}
|
||||
.layui-form-checkbox.layui-checkbox-disabled>div{background-color: var(--lay-color-fill-3) !important;}
|
||||
/* 复选框-默认风格 */
|
||||
.layui-form-checkbox[lay-skin=primary]{background-image:none;background-color:initial;border-color:initial!important}
|
||||
.layui-form-checkbox[lay-skin=primary]>div{background-image:none;background-color:initial;color:var(--lay-color-text-2)}
|
||||
.layui-form-checkbox[lay-skin=primary]>i{border-color:var(--lay-color-border-1);background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-checkbox[lay-skin=primary]:hover>i{border-color:var(--lay-color-secondary-hover);color:var(--lay-color-text-1)}
|
||||
.layui-form-checked[lay-skin=primary]>i{background-color:var(--lay-color-secondary);color:var(--lay-color-text-1);border-color:var(--lay-color-secondary-active)!important}
|
||||
.layui-checkbox-disabled[lay-skin=primary] >div{background:none!important;color:var(--lay-color-text-4)!important}
|
||||
.layui-form-checked.layui-checkbox-disabled[lay-skin=primary]>i{background-color:var(--lay-color-fill-1)!important;border-color:var(--lay-color-border-2)!important}
|
||||
.layui-checkbox-disabled[lay-skin=primary]:hover>i{border-color:var(--lay-color-border-1)}
|
||||
.layui-form-checkbox[lay-skin="primary"]>.layui-icon-indeterminate:before{background-color: var(--lay-color-secondary-hover);opacity: 1;}
|
||||
.layui-form-checkbox[lay-skin="primary"]:hover>.layui-icon-indeterminate:before{opacity: 1;}
|
||||
.layui-form-checkbox[lay-skin="primary"]>.layui-icon-indeterminate{border-color: var(--lay-color-secondary-hover);}
|
||||
/* 复选框-开关风格 */
|
||||
.layui-form-switch{border-color:var(--lay-color-border-2);background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-switch>i{background-color:var(--lay-color-gray-4)}
|
||||
.layui-form-switch.layui-checkbox-disabled>i{background-color:var(--lay-color-gray-7);}
|
||||
.layui-form-switch>div{color:var(--lay-color-gray-8)!important}
|
||||
.layui-form-onswitch{border-color:var(--lay-color-secondary-active);background-color:var(--lay-color-secondary)}
|
||||
.layui-form-onswitch>i{background-color:var(--lay-color-gray-4)}
|
||||
.layui-form-onswitch>div{color:var(--lay-color-text-1)!important}
|
||||
.layui-checkbox-disabled{border-color:var(--lay-color-border-2)!important}
|
||||
.layui-checkbox-disabled>div{background-color:var(--lay-color-fill-3)!important;color: var(--lay-color-text-4)!important;}
|
||||
.layui-checkbox-disabled>i{border-color:var(--lay-color-border-2)!important}
|
||||
.layui-checkbox-disabled:hover>i{color:var(--lay-color-text-1)!important}
|
||||
.layui-form-switch.layui-checkbox-disabled>div{background-color:initial!important;color: var(--lay-color-text-3)!important;}
|
||||
/*复选框背景优化*/
|
||||
.layui-form-checkbox>i:before{opacity:0;filter:alpha(opacity=0)}
|
||||
.layui-form-checkbox:hover>i:before{opacity:1;filter:alpha(opacity=100)}
|
||||
.layui-form-checked.layui-checkbox-disabled:hover>i:before,.layui-form-checked:hover>i:before,.layui-form-checked>i:before{opacity:1;filter:alpha(opacity=100)}
|
||||
.layui-form-checkbox[lay-skin=primary]:hover>i:before{opacity:0;filter:alpha(opacity=0)}
|
||||
.layui-form-checked[lay-skin=primary]:hover>i:before{opacity:1;filter:alpha(opacity=100)}
|
||||
.layui-checkbox-disabled:hover>i:before{opacity:0;filter:alpha(opacity=0)}
|
||||
/*单选框*/
|
||||
.layui-form-radio>i{color:var(--lay-color-gray-8)}
|
||||
.layui-form-radio:hover>*,.layui-form-radioed,.layui-form-radioed>i{color:var(--lay-color-secondary)}
|
||||
.layui-radio-disabled>i{color:var(--lay-color-text-4)!important}
|
||||
.layui-radio-disabled>*{color:var(--lay-color-text-4)!important}
|
||||
/* 表单方框风格 */
|
||||
.layui-form-pane .layui-form-label{background-color:var(--lay-color-bg-2)}
|
||||
/** 分页 **/
|
||||
.layui-laypage a,.layui-laypage button,.layui-laypage input,.layui-laypage select,.layui-laypage span{border:1px solid var(--lay-color-border-2)}
|
||||
.layui-laypage a,.layui-laypage span{background-color: var(--lay-color-bg-2);color: var(--lay-color-text-2)}
|
||||
.layui-laypage a[data-page]{color:var(--lay-color-text-2)}
|
||||
.layui-laypage a:hover{color: var(--lay-color-primary)}
|
||||
.layui-laypage .layui-laypage-spr{color:var(--lay-color-text-3)}
|
||||
.layui-laypage .layui-laypage-curr em{color: var(--lay-color-white)}
|
||||
.layui-laypage .layui-laypage-curr .layui-laypage-em{background-color: var(--lay-color-primary)}
|
||||
.layui-laypage .layui-laypage-skip{color:var(--lay-color-text-3)}
|
||||
.layui-laypage button,.layui-laypage input{background-color: var(--lay-color-bg-2)}
|
||||
.layui-laypage input:focus,.layui-laypage select:focus{border-color: var(--lay-color-primary)!important}
|
||||
/** 流加载 **/
|
||||
.layui-flow-more{color:var(--lay-color-text-1)}
|
||||
.layui-flow-more a cite{background-color: var(--lay-color-bg-4);color: var(--lay-color-text-1)}
|
||||
.layui-flow-more a i{color:var(--lay-color-text-2)}
|
||||
/** 表格 **/
|
||||
.layui-table{background-color: var(--lay-color-bg-2);color: var(--lay-color-text-2)}
|
||||
.layui-table-mend{background-color: var(--lay-color-bg-2)}
|
||||
.layui-table-click,.layui-table-hover,.layui-table[lay-even] tbody tr:nth-child(even){background-color:var(--lay-color-fill-3)}
|
||||
.layui-table-checked{background-color: var(--lay-color-fill-2);color: var(--lay-color-text-1)}
|
||||
.layui-table-checked.layui-table-hover,.layui-table-checked.layui-table-click{background-color: var(--lay-color-fill-3);}
|
||||
.layui-table td,.layui-table th,.layui-table-col-set,.layui-table-fixed-r,.layui-table-grid-down,.layui-table-header,.layui-table-mend,.layui-table-page,.layui-table-tips-main,.layui-table-tool,.layui-table-total,.layui-table-view,.layui-table[lay-skin=line],.layui-table[lay-skin=row]{border-color: var(--lay-color-border-2)}
|
||||
.layui-table-view:after {background-color: var(--lay-color-border-2);}
|
||||
.layui-table-view .layui-table td[data-edit]:hover:after{border:1px solid var(--lay-color-primary-active)}
|
||||
.layui-table-loading-icon .layui-icon{color:var(--lay-color-gray-8);}
|
||||
.layui-table-page{background-color: var(--lay-color-bg-2);}
|
||||
.layui-table-page .layui-laypage a,
|
||||
.layui-table-page .layui-laypage span{border: none;}
|
||||
.layui-table-tool{background-color: var(--lay-color-bg-2);}
|
||||
.layui-table-tool .layui-inline[lay-event]{color:var(--lay-color-text-3);border:1px solid var(--lay-color-border-2)}
|
||||
.layui-table-tool .layui-inline[lay-event]:hover{border:1px solid var(--lay-color-border-3)}
|
||||
.layui-table-tool-panel{color: var(--lay-color-text-1); border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-5);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-table-tool-panel li:hover{background-color:var(--lay-color-active)}
|
||||
.layui-table-col-set{background-color: var(--lay-color-white)}
|
||||
.layui-table-sort .layui-table-sort-asc{border-bottom-color:var(--lay-color-gray-8)}
|
||||
.layui-table-sort .layui-table-sort-asc:hover{border-bottom-color:var(--lay-color-gray-11)}
|
||||
.layui-table-sort .layui-table-sort-desc{border-top-color:var(--lay-color-gray-8)}
|
||||
.layui-table-sort .layui-table-sort-desc:hover{border-top-color:var(--lay-color-gray-11)}
|
||||
.layui-table-sort[lay-sort=asc] .layui-table-sort-asc{border-bottom-color:var(--lay-color-gray-13)}
|
||||
.layui-table-sort[lay-sort=desc] .layui-table-sort-desc{border-top-color:var(--lay-color-gray-13)}
|
||||
.layui-table-cell .layui-table-link{color: var(--lay-color-lightblue-5)}
|
||||
.layui-table-body .layui-none{color:var(--lay-color-gray-8)}
|
||||
.layui-table-fixed-l{box-shadow:1px 0 8px rgba(0,0,0,1)}
|
||||
.layui-table-fixed-r{box-shadow:-1px 0 8px rgba(0,0,0,1)}
|
||||
.layui-table-edit{box-shadow:var(--lay-shadow-1);background-color: var(--lay-color-bg-2)}
|
||||
.layui-table-edit:focus{border-color:var(--lay-color-secondary)!important}
|
||||
select.layui-table-edit{border-color:var(--lay-color-border-2)}
|
||||
.layui-table-grid-down{background-color: var(--lay-color-bg-5);color:var(--lay-color-gray-8)}
|
||||
.layui-table-grid-down:hover{background-color:var(--lay-color-bg-5)}
|
||||
/* 单元格多行展开风格 */
|
||||
.layui-table-cell-c{background-color: var(--lay-color-gray-13);color: var(--lay-color-text-1); border-color: var(--lay-color-border-3);}
|
||||
.layui-table-cell-c:hover{border-color: var(--lay-color-secondary-hover);}
|
||||
/* 单元格 TIPS 展开风格 */
|
||||
body .layui-table-tips .layui-layer-content{box-shadow:var(--lay-shadow-3)}
|
||||
.layui-table-tips-main{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-3)}
|
||||
.layui-table-tips-c{background-color:var(--lay-color-gray-13);color: var(--lay-color-text-1)}
|
||||
.layui-table-tips-c:hover{background-color:var(--lay-color-gray-10)}
|
||||
/** 文件上传 **/
|
||||
.layui-upload-choose{color:var(--lay-color-gray-8)}
|
||||
.layui-upload-drag{border:1px dashed var( --lay-color-border-2);background-color: var(--lay-color-bg-4);color: var(--lay-color-text-2)}
|
||||
.layui-upload-drag .layui-icon{color: var(--lay-color-primary)}
|
||||
.layui-upload-drag[lay-over]{border-color: var(--lay-color-primary)}
|
||||
/* 基础菜单元素 */
|
||||
.layui-menu{background-color: var(--lay-color-bg-2)}
|
||||
.layui-menu li{color: var(--lay-color-text-1)}
|
||||
.layui-menu li:hover{background-color: var(--lay-color-bg-5)}
|
||||
.layui-menu li.layui-disabled,.layui-menu li.layui-disabled *{color:var(--lay-color-text-4)!important}
|
||||
.layui-menu .layui-menu-item-group>.layui-menu-body-title{color: var(--lay-color-text-3)}
|
||||
.layui-menu .layui-menu-item-none{color: var(--lay-color-text-3);}
|
||||
.layui-menu .layui-menu-item-divider{border-bottom:1px solid var(--lay-color-border-2)}
|
||||
.layui-menu .layui-menu-item-group:hover,
|
||||
.layui-menu .layui-menu-item-none:hover,
|
||||
.layui-menu .layui-menu-item-divider:hover{background: none;}
|
||||
.layui-menu .layui-menu-item-up>.layui-menu-body-title{color: var(--lay-color-text-1)}
|
||||
.layui-menu .layui-menu-item-down:hover>.layui-menu-body-title>.layui-icon,.layui-menu .layui-menu-item-up>.layui-menu-body-title:hover>.layui-icon{color: var(--lay-color-text-1)}
|
||||
.layui-menu .layui-menu-item-checked,.layui-menu .layui-menu-item-checked2{background-color:var(--lay-color-active)!important;color:var(--lay-color-secondary)}
|
||||
.layui-menu .layui-menu-item-checked a,.layui-menu .layui-menu-item-checked2 a{color:var(--lay-color-secondary)}
|
||||
.layui-menu .layui-menu-item-checked:after{border-right:3px solid var(--lay-color-secondary)}
|
||||
.layui-menu-body-title a{color: var(--lay-color-text-1)}
|
||||
.layui-menu-lg .layui-menu-body-title a:hover,.layui-menu-lg li:hover{color:var(--lay-color-secondary)}
|
||||
/* 下拉菜单 */
|
||||
.layui-dropdown{background-color: var(--lay-color-bg-5)}
|
||||
.layui-dropdown.layui-panel,.layui-dropdown .layui-panel{background-color: var(--lay-color-bg-5);box-shadow: var(--lay-shadow-2)}
|
||||
.layui-dropdown.layui-panel .layui-menu{background-color: var(--lay-color-bg-5)}
|
||||
/** 导航菜单 **/
|
||||
.layui-nav{background-color:var(--lay-color-black-6);color: var(--lay-color-white)}
|
||||
.layui-nav .layui-nav-item a{color: var(--lay-color-text-1);}
|
||||
.layui-nav .layui-this:after,.layui-nav-bar{background-color:var(--lay-color-secondary)}
|
||||
.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{color: var(--lay-color-text-1)}
|
||||
.layui-nav-child{box-shadow:var(--lay-shadow-2);border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-5)}
|
||||
.layui-nav .layui-nav-child a{color: var(--lay-color-text-1)}
|
||||
.layui-nav .layui-nav-child a:hover{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-nav-child dd.layui-this{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-nav-tree .layui-nav-child dd.layui-this,.layui-nav-tree .layui-nav-child dd.layui-this a,.layui-nav-tree .layui-this,.layui-nav-tree .layui-this>a,.layui-nav-tree .layui-this>a:hover{background-color: var(--lay-color-primary);color: var(--lay-color-white)}
|
||||
.layui-nav-itemed>a,.layui-nav-tree .layui-nav-title a,.layui-nav-tree .layui-nav-title a:hover{color: var(--lay-color-white)!important}
|
||||
.layui-nav-tree .layui-nav-bar{background-color:var(--lay-color-primary)}
|
||||
.layui-nav-tree .layui-nav-child{background: none; background-color:rgba(0, 0, 0, .3); border: none; box-shadow: none;}
|
||||
.layui-nav-tree .layui-nav-child a{color: var(--lay-color-white);color: var(--lay-color-text-1)}
|
||||
.layui-nav-tree .layui-nav-child a:hover{background: none; color: var(--lay-color-white)}
|
||||
.layui-nav.layui-bg-gray,.layui-nav-tree.layui-bg-gray{background-color: var(--lay-color-bg-2) !important;color: var(--lay-color-text-1);}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-child{background-color: rgba(0, 0, 0, .3) !important;}
|
||||
.layui-nav-tree.layui-bg-gray a,.layui-nav.layui-bg-gray .layui-nav-item a{color: var(--lay-color-text-1)}
|
||||
.layui-nav.layui-bg-gray .layui-nav-child{background-color: var(--lay-color-bg-5);}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-itemed>a{color: var(--lay-color-text-1)!important}
|
||||
.layui-nav.layui-bg-gray .layui-this a{color:var(--lay-color-secondary)}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-child dd.layui-this,.layui-nav-tree.layui-bg-gray .layui-nav-child dd.layui-this a,.layui-nav-tree.layui-bg-gray .layui-this,.layui-nav-tree.layui-bg-gray .layui-this>a{color:var(--lay-color-secondary)!important}
|
||||
.layui-nav-tree.layui-bg-gray .layui-nav-bar{background-color:var(--lay-color-secondary)}
|
||||
/** 面包屑 **/
|
||||
.layui-breadcrumb a{color:var(--lay-color-gray-7)!important}
|
||||
.layui-breadcrumb a:hover{color:var(--lay-color-secondary)!important}
|
||||
.layui-breadcrumb a cite{color:var(--lay-color-gray-8)}
|
||||
.layui-breadcrumb span[lay-separator]{color:var(--lay-color-gray-7)}
|
||||
/** Tab 选项卡 **/
|
||||
.layui-tab .layui-tab-title:after{border-bottom-color: var(--lay-color-border-1);}
|
||||
.layui-tab-title .layui-this{color: var(--lay-color-text-2)}
|
||||
.layui-tab-title .layui-this:after{border-bottom-color: var(--lay-color-bg-1)}
|
||||
.layui-tab-bar{background-color: var(--lay-color-bg-3)}
|
||||
.layui-tab-more li.layui-this:after{border-bottom-color:var(--lay-color-border-1)}
|
||||
.layui-tab-title li .layui-tab-close{color:var(--lay-color-gray-8)}
|
||||
.layui-tab-title li .layui-tab-close:hover{background-color:var(--lay-color-danger);color: var(--lay-color-white)}
|
||||
.layui-tab-brief>.layui-tab-title .layui-this{color:var( --lay-color-primary)}
|
||||
.layui-tab-brief>.layui-tab-more li.layui-this:after,.layui-tab-brief>.layui-tab-title .layui-this:after{border-bottom:2px solid var(--lay-color-secondary)}
|
||||
.layui-tab-card{box-shadow: var(--lay-shadow-1)}
|
||||
.layui-tab-card>.layui-tab-title{background-color: var(--lay-color-bg-2)}
|
||||
.layui-tab-card>.layui-tab-title .layui-this{background-color: var(--lay-color-bg-1)}
|
||||
.layui-tab-card>.layui-tab-title .layui-this:after{border-bottom-color: var(--lay-color-bg-1)}
|
||||
.layui-tab-card>.layui-tab-more .layui-this{color:var(--lay-color-secondary)}
|
||||
|
||||
/** tabs 标签页 **/
|
||||
.layui-tabs-header:after,
|
||||
.layui-tabs-scroll:after{border-bottom-color: var(--lay-color-border-1);}
|
||||
.layui-tabs-card>.layui-tabs-header .layui-this{background-color: transparent;}
|
||||
.layui-tabs-card>.layui-tabs-header .layui-this:after{border-color: var(--lay-color-border-1); border-bottom-color: var(--lay-color-bg-1);}
|
||||
.layui-tabs-card.layui-panel>.layui-tabs-header .layui-this:after{border-bottom-color: var(--lay-color-bg-2);}
|
||||
.layui-tabs-bar .layui-icon{background-color: var(--lay-color-bg-1); color: var(--lay-color-text-2); border-color: var(--lay-color-border-1); box-shadow: 2px 0 5px 0 rgb(0 0 0 / 32%);}
|
||||
.layui-tabs-bar .layui-icon-next{box-shadow: -2px 0 5px 0 rgb(0 0 0 / 32%);}
|
||||
|
||||
/*时间线*/
|
||||
.layui-timeline-axis{background-color: var(--lay-color-bg-4);color:var(--lay-color-secondary)}
|
||||
.layui-timeline-axis:hover{color:var(--lay-color-red-6)}
|
||||
.layui-timeline-item:before{background-color: var(--lay-color-bg-3)}
|
||||
/*徽章*/
|
||||
.layui-badge,.layui-badge-dot,.layui-badge-rim{background-color:var(--lay-color-red-6);color: var(--lay-color-white)}
|
||||
.layui-badge-rim{background-color: var(--lay-color-white);color:var(--lay-color-black-6)}
|
||||
/* carousel 轮播 */
|
||||
.layui-carousel{background-color:var(--lay-color-gray-2)}
|
||||
.layui-carousel>[carousel-item]:before{color:var(--lay-color-gray-8);-moz-osx-font-smoothing:grayscale}
|
||||
.layui-carousel>[carousel-item]>*{background-color:var(--lay-color-gray-2)}
|
||||
.layui-carousel-arrow{background-color:rgba(0,0,0,.2);color: var(--lay-color-white)}
|
||||
.layui-carousel-arrow:hover,.layui-carousel-ind ul:hover{background-color:var(--lay-color-black)}
|
||||
.layui-carousel[lay-indicator=outside] .layui-carousel-ind ul{background-color:var(--lay-color-black)}
|
||||
.layui-carousel-ind ul{background-color:rgba(0,0,0,.2)}
|
||||
.layui-carousel-ind ul li{background-color:var(--lay-color-gray-3);background-color: var(--lay-color-text-3)}
|
||||
.layui-carousel-ind ul li:hover{background-color: var(--lay-color-white)}
|
||||
.layui-carousel-ind ul li.layui-this{background-color: var(--lay-color-white)}
|
||||
/** fixbar **/
|
||||
.layui-fixbar li{background-color:var(--lay-color-black-5);color: var(--lay-color-text-1)}
|
||||
/** 表情面板 **/
|
||||
body .layui-util-face .layui-layer-content{background-color: var(--lay-color-bg-5);color:var(--lay-color-text-2)}
|
||||
.layui-util-face ul{border:1px solid var(--lay-color-border-3);background-color: var(--lay-color-bg-5);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-util-face ul li{border:1px solid var(--lay-color-border-2)}
|
||||
.layui-util-face ul li:hover{border:1px solid var(--lay-color-red-7);background: var(--lay-color-text-1)}
|
||||
/** 代码文本修饰 **/
|
||||
.layui-code{border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-white);color: var(--lay-color-text-2)}
|
||||
/** 穿梭框 **/
|
||||
.layui-transfer-box,.layui-transfer-header,.layui-transfer-search{border-color: var(--lay-color-border-2)}
|
||||
.layui-transfer-box{background-color: var(--lay-color-bg-2)}
|
||||
.layui-transfer-search .layui-icon-search{color:var(--lay-color-gray-8)}
|
||||
.layui-transfer-active .layui-btn{background-color:var( --lay-color-secondary);border-color:var( --lay-color-secondary);color: var(--lay-color-white)}
|
||||
.layui-transfer-active .layui-btn-disabled{background-color:var(--lay-color-gray-2);border-color:var(--lay-color-gray-3);color:var(--lay-color-gray-8)}
|
||||
.layui-transfer-data li:hover{background-color:var(--lay-color-active)}
|
||||
/* chrome 105 */
|
||||
.layui-transfer-data li:hover:has([lay-filter="layTransferCheckbox"][disabled]){background-color:var(--lay-color-bg-2)}
|
||||
.layui-transfer-data .layui-none{color:var(--lay-color-gray-7)}
|
||||
/** 评分组件 **/
|
||||
.layui-rate li i.layui-icon{color:var(--lay-color-orange-6)}
|
||||
/** 颜色选择器 **/
|
||||
.layui-colorpicker{border:1px solid var(--lay-color-border-1)}
|
||||
.layui-colorpicker:hover{border-color: var(--lay-color-border-2)}
|
||||
.layui-colorpicker-trigger-span{border:1px solid var(--lay-color-border-1)}
|
||||
.layui-colorpicker-trigger-i{color: var(--lay-color-white)}
|
||||
.layui-colorpicker-trigger-i.layui-icon-close{color:var(--lay-color-black-7)}
|
||||
.layui-colorpicker-main{background: var(--lay-color-bg-2);border:1px solid var( --lay-color-border-2);box-shadow:var(--lay-shadow-2)}
|
||||
.layui-colorpicker-basis-white{background:linear-gradient(90deg, #fff,hsla(0,0%,100%,0))} /* danger: 勿改*/
|
||||
.layui-colorpicker-basis-black{background:linear-gradient(0deg,#000,transparent)} /* danger: 勿改*/
|
||||
.layui-colorpicker-basis-cursor{border:1px solid var(--lay-color-white)}
|
||||
.layui-colorpicker-side{background:linear-gradient(linear-gradient(#F00, #FF0, #0F0, #0FF, #00F, #F0F, #F00))} /* danger: 勿改*/
|
||||
.layui-colorpicker-side-slider{box-shadow:var(--lay-shadow-1);background: var(--lay-color-white);border:1px solid var(--lay-color-gray-2)}
|
||||
.layui-colorpicker-alpha-slider{box-shadow:var(--lay-shadow-1);background: var(--lay-color-white);border:1px solid var(--lay-color-gray-2)}
|
||||
.layui-colorpicker-pre.layui-this{box-shadow:var(--lay-shadow-1)}
|
||||
.layui-colorpicker-pre.selected{box-shadow:var(--lay-shadow-1)}
|
||||
.layui-colorpicker-main-input input.layui-input{color: var(--lay-color-text-2)}
|
||||
/** 滑块 **/
|
||||
.layui-slider{background: var( --lay-color-bg-5)}
|
||||
.layui-slider-step{background: var(--lay-color-fill-4)}
|
||||
.layui-slider-wrap-btn{background: var(--lay-color-bg-4)}
|
||||
.layui-slider-tips{color: var(--lay-color-text-1);background:var(--lay-color-black);box-shadow: var(--lay-shadow-3)}
|
||||
.layui-slider-tips:after{border-color:var(--lay-color-black) transparent transparent transparent}
|
||||
.layui-slider-input{border:1px solid var(--lay-color-border-1)}
|
||||
.layui-slider-input-btn{border-left:1px solid var(--lay-color-border-1)}
|
||||
.layui-slider-input-btn i{color:var(--lay-color-gray-9)}
|
||||
.layui-slider-input-btn i:first-child{border-bottom:1px solid var(--lay-color-border-1)}
|
||||
.layui-slider-input-btn i:hover{color:var(--lay-color-primary)}
|
||||
/** 树组件 **/
|
||||
.layui-tree-line .layui-tree-set .layui-tree-set:after{border-top:1px dotted var(--lay-color-gray-7)}
|
||||
.layui-tree-entry:hover{background-color: var(--lay-color-bg-4)}
|
||||
.layui-tree-line .layui-tree-entry:hover{background-color:var(--lay-color-black)}
|
||||
.layui-tree-line .layui-tree-entry:hover .layui-tree-txt{color:var(--lay-color-text-3)}
|
||||
.layui-tree-entry:hover:has(span.layui-tree-txt.layui-disabled){background-color: transparent !important}
|
||||
.layui-tree-line .layui-tree-set:before{border-left:1px dotted var(--lay-color-gray-7)}
|
||||
.layui-tree-iconClick{color:var(--lay-color-gray-7)}
|
||||
.layui-tree-icon{border:1px solid var(--lay-color-gray-8)}
|
||||
.layui-tree-icon .layui-icon{color:var(--lay-color-text-1)}
|
||||
.layui-tree-iconArrow:after{border-color:transparent transparent transparent var(--lay-color-gray-7)}
|
||||
.layui-tree-txt{color:var(--lay-color-text-2)}
|
||||
.layui-tree-search{color:var(--lay-color-black-7)}
|
||||
.layui-tree-btnGroup .layui-icon:hover{color:var(--lay-color-text-2)}
|
||||
.layui-tree-editInput{background-color:var(--lay-color-fill-2)}
|
||||
.layui-tree-emptyText{color:var(--lay-color-text-2)}
|
||||
/*code 不处理*/
|
||||
.layui-code-view{border:1px solid var(--lay-color-border-1);}
|
||||
.layui-code-view:not(.layui-code-hl){background-color: var(--lay-color-bg-2);color: var(--lay-color-text-2);}
|
||||
.layui-code-header{border-bottom: 1px solid var(--lay-color-border-1); background-color: var(--lay-color-bg-2)}
|
||||
.layui-code-header > .layui-code-header-about{color: var(--lay-color-text-2);}
|
||||
.layui-code-view:not(.layui-code-hl) .layui-code-ln-side{border-color: var(--lay-color-border-1); background-color: var(--lay-color-bg-2);}
|
||||
.layui-code-nowrap > .layui-code-ln-side{background: none !important;}
|
||||
.layui-code-fixbar > span{color: var(--lay-color-text-3);}
|
||||
.layui-code-fixbar > span:hover{color: var(--lay-color-secondary-hover);}
|
||||
|
||||
.layui-code-theme-dark,
|
||||
.layui-code-theme-dark > .layui-code-header{border-color: rgb(126 122 122 / 15%); background-color: #1f1f1f;}
|
||||
.layui-code-theme-dark{border-width: 1px; color: #ccc;}
|
||||
.layui-code-theme-dark > .layui-code-ln-side{border-right-color: #2a2a2a; background: none; color: #6e7681;}
|
||||
|
||||
.layui-code-view.layui-code-hl > .layui-code-ln-side{background-color: transparent;}
|
||||
.layui-code-theme-dark.layui-code-hl,
|
||||
.layui-code-theme-dark.layui-code-hl > .layui-code-ln-side{border-color: rgb(126 122 122 / 15%);}
|
||||
|
||||
.layui-code-full{background-color: var(--lay-color-bg-1)}
|
||||
/*日期选择器*/
|
||||
.layui-laydate-header i{color:var(--lay-color-gray-8)}
|
||||
.laydate-day-holidays:before{color:var(--lay-color-red-6)}
|
||||
.layui-laydate .layui-this .laydate-day-holidays:before{color: var(--lay-color-white)}
|
||||
.layui-laydate-footer span{border:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-5)}
|
||||
.layui-laydate-footer span:hover{color:var(--lay-color-secondary)}
|
||||
.layui-laydate-footer span.layui-laydate-preview{border-color:transparent!important;}
|
||||
.layui-laydate-footer span.layui-laydate-preview:hover{color:var(--lay-color-text-1) !important}
|
||||
.layui-laydate-shortcut+.layui-laydate-main{border-left:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate .layui-laydate-list{background-color: var(--lay-color-bg-5)}
|
||||
.layui-laydate-hint{color:var(--lay-color-danger)}
|
||||
.layui-laydate-range .laydate-main-list-1 .layui-laydate-content,.layui-laydate-range .laydate-main-list-1 .layui-laydate-header{border-left:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate,.layui-laydate-hint{border-color: var(--lay-color-border-2);box-shadow:var(--lay-shadow-3);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-laydate{box-shadow: var(--lay-shadow-2)}
|
||||
.layui-laydate-hint{border-color:var(--lay-color-border-1)}
|
||||
.layui-laydate-header{border-bottom:1px solid var( --lay-color-border-2)}
|
||||
.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:var(--lay-color-secondary)}
|
||||
.layui-laydate-content th{color: var(--lay-color-text-1)}
|
||||
.layui-laydate-content td{color: var(--lay-color-text-1)}
|
||||
.layui-laydate-content td.laydate-day-now{color:var(--lay-color-secondary)}
|
||||
.layui-laydate-content td.laydate-day-now:after{border:1px solid var(--lay-color-secondary)}
|
||||
.layui-laydate-linkage .layui-laydate-content td.laydate-selected>div{background-color:var(--lay-color-green-8);}
|
||||
.layui-laydate-linkage .laydate-selected:hover>div{background-color:var(--lay-color-green-8)!important}
|
||||
.layui-laydate-content td>div:hover,.layui-laydate-list li:hover,.layui-laydate-shortcut>li:hover{background-color: var(--lay-color-fill-2);color: var(--lay-color-text-2)}
|
||||
.layui-laydate-content td.laydate-disabled>div:hover{background-color: var(--lay-color-bg-5);color: var(--lay-color-text-4)}
|
||||
.laydate-time-list li ol{border:1px solid var(--lay-color-border-2)}
|
||||
.laydate-time-list>li:hover{background: 0 0;}
|
||||
.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color: var(--lay-color-text-3)}
|
||||
.layui-laydate-linkage .laydate-selected.laydate-day-next>div,.layui-laydate-linkage .laydate-selected.laydate-day-prev>div{background: none!important}
|
||||
.layui-laydate-footer{border-top:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate-hint{color:var(--lay-color-danger)}
|
||||
.laydate-day-mark::after{background-color:var(--lay-color-secondary)}
|
||||
.layui-laydate-footer span[lay-type=date]{color:var(--lay-color-secondary)}
|
||||
.layui-laydate .layui-this,.layui-laydate .layui-this>div{background-color:var(--lay-color-secondary)!important;color: var(--lay-color-white)!important}
|
||||
.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{color: var(--lay-color-text-4)!important}
|
||||
.layui-laydate .layui-this.laydate-disabled,.layui-laydate .layui-this.laydate-disabled>div{background-color: var(--lay-color-fill-1) !important;color: var(--lay-color-text-4) !important;}
|
||||
.laydate-theme-molv .layui-laydate-header{background-color:var(--lay-color-primary)}
|
||||
.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:var(--lay-color-gray-2)}
|
||||
.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color: var(--lay-color-white)}
|
||||
.laydate-theme-molv .layui-laydate-content{border:1px solid var(--lay-color-border-2)}
|
||||
.laydate-theme-molv .layui-this, .laydate-theme-molv .layui-this>div{background-color: var(--lay-color-primary) !important;}
|
||||
.laydate-theme-molv .layui-laydate-footer{border:1px solid var(--lay-color-border-2)}
|
||||
.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead{border:1px solid var(--lay-color-border-2)}
|
||||
.layui-laydate-linkage.laydate-theme-grid .laydate-selected,.layui-laydate-linkage.laydate-theme-grid .laydate-selected:hover{background-color:var(--lay-color-gray-3)!important;color:var(--lay-color-primary)!important}
|
||||
.layui-laydate-linkage.laydate-theme-grid .laydate-selected.laydate-day-next,.layui-laydate-linkage.laydate-theme-grid .laydate-selected.laydate-day-prev{color:var(--lay-color-gray-6)!important}
|
||||
.layui-laydate.laydate-theme-circle .layui-laydate-content table td.layui-this{background-color:transparent!important}
|
||||
/*layer*/
|
||||
.layui-layer{background-color: var(--lay-color-bg-3);box-shadow:var(--lay-shadow-3)}
|
||||
.layui-layer-border{border:1px solid var(--lay-color-border-2);box-shadow:var(--lay-shadow-3)}
|
||||
.layui-layer-move{background-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-title{border-bottom:1px solid var(--lay-color-border-2);color: var(--lay-color-text-1)}
|
||||
.layui-layer-setwin span{color: var(--lay-color-text-1)}
|
||||
.layui-layer-setwin .layui-layer-min:before{border-bottom-color:var(--lay-color-text-1)}
|
||||
.layui-layer-setwin .layui-layer-min:hover:before{border-bottom-color:var(--lay-color-info-hover)}
|
||||
.layui-layer-setwin .layui-layer-max:after,.layui-layer-setwin .layui-layer-max:before{border:1px solid var(--lay-color-text-3)}
|
||||
.layui-layer-setwin .layui-layer-max:hover:after,.layui-layer-setwin .layui-layer-max:hover:before{border-color:var(--lay-color-info-hover)}
|
||||
.layui-layer-setwin .layui-layer-maxmin:after,.layui-layer-setwin .layui-layer-maxmin:before{background-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-setwin .layui-layer-close2{color:var(--lay-color-text-1);background-color:var(--lay-color-gray-10)}
|
||||
.layui-layer-setwin .layui-layer-close2:hover{background-color:var(--lay-color-normal)}
|
||||
.layui-layer-btn a{border:1px solid var(--lay-color-border-2);background-color: var( --lay-color-bg-3);color: var(--lay-color-text-2)}
|
||||
.layui-layer-btn .layui-layer-btn0{border-color: transparent;background-color: var(--lay-color-normal);color: var(--lay-color-text-1)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-layer-face{color:var(--lay-color-gray-9)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-tips{color:var(--lay-color-warning)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-success{color: var(--lay-color-success)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-error{top: 19px; color: var(--lay-color-danger)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-question{color: var(--lay-color-warning);}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-lock{color: var(--lay-color-gray-10)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-face-cry{color:var(--lay-color-danger)}
|
||||
.layui-layer-dialog .layui-layer-content .layui-icon-face-smile{color:var(--lay-color-success)}
|
||||
.layui-layer-rim{border:6px solid var(--lay-color-gray-8);border:6px solid var(--lay-color-border-2)}
|
||||
.layui-layer-msg{border:1px solid var( --lay-color-border-1)}
|
||||
.layui-layer-hui{background-color: var(--lay-color-bg-3);color: var(--lay-color-text-1)}
|
||||
.layui-layer-hui .layui-layer-close{color: var(--lay-color-white)}
|
||||
.layui-layer-loading-icon{color:var(--lay-color-gray-9)}
|
||||
.layui-layer-loading-2:after,.layui-layer-loading-2:before{border:3px solid var(--lay-color-gray-6)}
|
||||
.layui-layer-loading-2:after{border-color:transparent;border-left-color: var(--lay-color-normal)}
|
||||
.layui-layer-tips .layui-layer-content{box-shadow: var(--lay-shadow-3);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-tips i.layui-layer-TipsG{border-color:transparent}
|
||||
.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{border-right-color:var(--lay-color-black)}
|
||||
.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{border-bottom-color:var(--lay-color-black)}
|
||||
.layui-layer-lan .layui-layer-title{background:var(--lay-color-blue-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-lan .layui-layer-btn{border-top:1px solid var(--lay-color-border-3)}
|
||||
.layui-layer-lan .layui-layer-btn a{background: var(--lay-color-white);border-color:var(--lay-color-border-3);color: var(--lay-color-black-7)}
|
||||
.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background: var(--lay-color-gray-7)}
|
||||
.layui-layer-molv .layui-layer-title{background:var(--lay-color-layuigreen-6);color: var(--lay-color-text-1)}
|
||||
.layui-layer-molv .layui-layer-btn a{background:var(--lay-color-layuigreen-6);border-color:var(--lay-color-layuigreen-6)}
|
||||
.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:var(--lay-color-gray-7)}
|
||||
.layui-layer-win10{border-color: var(--lay-color-border-2)}
|
||||
.layui-layer-win10 .layui-layer-btn{background-color: var(--lay-color-bg-2);border-color: var(--lay-color-border-2)}
|
||||
.layui-layer-win10.layui-layer-dialog .layui-layer-content{color: var(--lay-color-blue-7)}
|
||||
.layui-layer-win10 .layui-layer-btn .layui-layer-btn0{border-color: var(--lay-color-blue-9);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-win10 .layui-layer-btn .layui-layer-btn1{border-color: var(--lay-color-border-2);background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-win10 .layui-layer-btn a:hover{background-color: var(--lay-color-blue-10);border-color: var(--lay-color-blue-8)}
|
||||
.layui-layer-prompt .layui-layer-input{border:1px solid var(--lay-color-border-2);color: var(--lay-color-text-2)}
|
||||
.layui-layer-tab{box-shadow:var(--lay-shadow-3)}
|
||||
.layui-layer-tab .layui-layer-title span.layui-this{border-left:1px solid var(--lay-color-border-2);border-right:1px solid var(--lay-color-border-2);background-color: var(--lay-color-bg-3)}
|
||||
.layui-layer-photos{background: none; box-shadow: none;}
|
||||
.layui-layer-photos-prev,.layui-layer-photos-next{color:var(--lay-color-gray-9)}
|
||||
.layui-layer-photos-prev:hover,.layui-layer-photos-next:hover{color:var(--lay-color-text-1)}
|
||||
.layui-layer-photos-toolbar{background-color:#333;background-color: var(--lay-color-bg-5);color: var(--lay-color-text-1)}
|
||||
.layui-layer-photos-toolbar *{color: var(--lay-color-text-1)}
|
||||
.layui-layer-photos-toolbar a:hover{color: var(--lay-color-text-2)}
|
||||
.layui-layer-photos-header > span:hover{background-color: var(--lay-color-fill-2)}
|
||||
.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{border-right-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{border-bottom-color: var(--lay-color-bg-5)}
|
||||
.layui-layer-prompt .layui-layer-input{border:1px solid var(--lay-color-border-2);color:var(--lay-color-text-1);background-color:var(--lay-color-black)}
|
||||
.layui-layer-prompt .layui-layer-input:focus{outline:0}
|
||||
|
||||
/*fix style*/
|
||||
.layui-layer-loading{background:0 0;box-shadow:0 0}
|
||||
.layui-btn-primary{border-color:transparent}
|
||||
.layui-btn-group .layui-btn:first-child{border-left:none}
|
||||
.layui-btn-group .layui-btn-primary:hover{border-top-color:transparent; border-bottom-color: transparent;}
|
||||
.layui-menu li:hover{background-color:var(--lay-color-fill-2)}
|
||||
.layui-nav-child dd.layui-this{background-color:var(--lay-color-fill-2)}
|
||||
.layui-nav .layui-nav-child a:hover{background-color:var(--lay-color-fill-2)}
|
||||
.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{background-color: var(--lay-color-fill-2)}
|
||||
.layui-nav-child dd.layui-this{background-color: var(--lay-color-fill-2)}
|
||||
.layui-tab-card>.layui-tab-title .layui-this:after{border-bottom-color:var(--lay-color-bg-1)}
|
||||
.layui-form-select dl dd:hover{background-color:var(--lay-color-fill-2)}
|
||||
.layui-form-select dl dd.layui-this{background-color:var(--lay-color-fill-2)}
|
||||
.layui-laypage button{color:var(--lay-color-text-1)}
|
||||
.layui-table[lay-even] tbody tr:nth-child(even){background-color:var(--lay-color-fill-4)}
|
||||
.layui-menu .layui-menu-item-checked,.layui-menu .layui-menu-item-checked2{background-color:var(--lay-color-fill-2)!important}
|
||||
.layui-input-split{background-color: var(--lay-color-bg-2);}
|
||||
.layui-input-wrap .layui-input-prefix.layui-input-split{border-width: 1px;}
|
||||
.layui-input-wrap .layui-input-split:has(+.layui-input:hover) {border-color: var(--lay-color-border-2);}
|
||||
.layui-input-wrap .layui-input-split:has(+.layui-input:focus) {border-color: var(--lay-color-secondary-hover);}
|
||||
.layui-layer-tab .layui-layer-title span:first-child{border-left: none !important;}
|
||||
.layui-slider-input.layui-input,
|
||||
.layui-slider-input .layui-input {background-color: var(--lay-color-bg-2);}
|
||||
240
web/static/tpl/theme.html
Normal file
240
web/static/tpl/theme.html
Normal file
@@ -0,0 +1,240 @@
|
||||
<style>
|
||||
:root{
|
||||
--ms-color-bg: #FFF;
|
||||
}
|
||||
:root.dark{
|
||||
--ms-color-bg: #000;
|
||||
}
|
||||
.ms-color-palette{
|
||||
font-size:0;
|
||||
}
|
||||
.ms-color-palette .ms-color-gradient:nth-child(10n){
|
||||
margin-right: 20px;
|
||||
}
|
||||
.ms-color-palette .ms-color-gradient:nth-child(20n){
|
||||
display: table-column;
|
||||
}
|
||||
.ms-color-gradient{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height:20px;
|
||||
margin-bottom: 15px;
|
||||
margin-right: 5px;
|
||||
transition-duration:0.1s;
|
||||
}
|
||||
.ms-color-gradient:hover{
|
||||
transform:scale(1.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.ms-color-edit-picker>div{
|
||||
border-color: transparent !important;
|
||||
}
|
||||
.ms-color-edit-picker i{
|
||||
display: none;
|
||||
}
|
||||
.ms-color-edit-picker span{
|
||||
border-color: #5f5f60;
|
||||
}
|
||||
.ms-color-edit-picker .layui-colorpicker-trigger-bgcolor{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div style="padding: 20px;background-color: var(--ms-color-bg);">
|
||||
<div style="display: flex;justify-content: space-between;align-items: center;">
|
||||
<div style="display: flex;" >
|
||||
<label style="display: flex; position: relative; top: 1px; margin-right: 10px;" title="降低饱和度,提高亮度,暗色下更舒适">
|
||||
深色色板
|
||||
<input name="colorpicker" type="checkbox" style="height: 20px; width: 20px;">
|
||||
</label>
|
||||
<label title="类名/属性名例如 .dark,[theme-mode='dark']">
|
||||
自定义主题类/属性选择器
|
||||
<input name="theme-prefix" type="input" style="height: 18px; width: 150px;">
|
||||
</label>
|
||||
<i id="theme-prefix-tips" class="layui-icon layui-icon-tips" style="position: relative; top: 3.5px;margin-left: 2px;"></i>
|
||||
</div>
|
||||
<div style="display: flex;" >
|
||||
<span title="重置"><i id="resetTheme" style="font-size: 23px;" class="layui-icon layui-icon-refresh"></i></span>
|
||||
<span title="下载"><i id="downloadCSS" style="font-size: 23px;margin-left: 10px;" class="layui-icon layui-icon-download-circle"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="ms-color-palette">
|
||||
<!-- 色板 tpl 生成 -->
|
||||
</div>
|
||||
<div class="ms-color-edit">
|
||||
<!-- 编辑区 tpl 生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="tpl-color-palette">
|
||||
{{# layui.each(d.ColorPaletteLight, function(key, val){ }}
|
||||
<div class="ms-color-gradient" style="background-color: var({{- key }});" title="{{- key.replace('--lay-color-','') }}"></div>
|
||||
{{# }) }}
|
||||
</template>
|
||||
|
||||
<template id="tpl-color-editable">
|
||||
{{# layui.each(d.editable, function(key, val){ }}
|
||||
<div style="display: flex; align-items: center; height:30px">
|
||||
<div>{{= key }}</div>
|
||||
<div style="flex: 1 1 auto;"></div>
|
||||
<input type="text" name="color" value="{{= val }}" placeholder=""
|
||||
style="text-align: right;height:28px;width: 150px;background-color: transparent;border-color: transparent;">
|
||||
<div class="ms-color-edit-picker" lay-options="{color: '{{= val }}', format: '{{- /^rgb/.test(val) ? 'rgb':'hex' }}' }" data-key="{{= key}}"></div>
|
||||
</div>
|
||||
{{# }) }}
|
||||
</template>
|
||||
<template id="tpl-theme-prefix-example">
|
||||
<pre class="layui-code code-demo" lay-options="{}" style="margin: 0; padding: 0;">
|
||||
/** .dark,通过改变 html 标签的类名切换主题*/
|
||||
:root{ :root.dark{
|
||||
--color-bg: #000; --color-bg: #000;
|
||||
} ==> }
|
||||
.lay-card{ .dark .lay-card{}
|
||||
color: #FFF; color: #FFF;
|
||||
} }
|
||||
/** js */
|
||||
// 设置为暗色主题
|
||||
document.documentElement.classList.add('dark')
|
||||
// 恢复亮色主题
|
||||
document.documentElement.classList.remove('dark')
|
||||
// 切换亮/暗主题
|
||||
document.documentElement.classList.toggle('dark')
|
||||
----------------------------------------------------------
|
||||
/** [theme-mode='dark'],通过改变 html 标签上 theme-mode 的属性切换主题*/
|
||||
:root{ :root[theme-mode='dark']{
|
||||
--color-bg: #000; --color-bg: #000;
|
||||
} ==> }
|
||||
.lay-card{ [theme-mode='dark'] .lay-card{}
|
||||
color: #FFF; color: #FFF;
|
||||
} }
|
||||
/** js */
|
||||
// 设置为暗色主题
|
||||
document.documentElement.setAttribute('theme-mode', 'dark')
|
||||
// 恢复亮色主题
|
||||
document.documentElement.removeAttribute('theme-mode');
|
||||
</pre>
|
||||
</template>
|
||||
<script src="static/lib/less.js"></script>
|
||||
<script>
|
||||
layui.use(async ()=> {
|
||||
const {jquery:$,laytpl,colorpicker,layer} = layui;
|
||||
|
||||
const originalData=await (await fetch('./assets/themes.json')).json()
|
||||
let customTheme = {};
|
||||
|
||||
laytpl($('#tpl-color-palette').html()).render(originalData,function(str) {
|
||||
$('.ms-color-palette').html(str);
|
||||
});
|
||||
|
||||
laytpl($('#tpl-color-editable').html()).render(originalData,function(str) {
|
||||
$('.ms-color-edit').html(str);
|
||||
renderColorPicker();
|
||||
});
|
||||
|
||||
$('input[name=colorpicker]').on('click',function(){
|
||||
applyColorPalette(this.checked)
|
||||
})
|
||||
$('#resetTheme').on('click',function(){
|
||||
resetTheme()
|
||||
$('input[name=colorpicker]').prop('checked',false)
|
||||
})
|
||||
$('#downloadCSS').on('click',function(){
|
||||
dropdownCSS()
|
||||
})
|
||||
$('#theme-prefix-tips').hover(function(){
|
||||
layer.tips($('#tpl-theme-prefix-example').html(),this,{
|
||||
tips: 3,
|
||||
time: false,
|
||||
area: ['700px','auto'],
|
||||
success: function(layero){
|
||||
layui.code({elem: '.code-demo'});
|
||||
layero.find('.layui-layer-content').css({padding:0,margin:0})
|
||||
layero.css('top','50px') //阻止反转
|
||||
}
|
||||
});
|
||||
},function(){
|
||||
layer.closeLast('tips');
|
||||
})
|
||||
|
||||
function renderColorPicker(){
|
||||
colorpicker.render({
|
||||
elem: '.ms-color-edit-picker',
|
||||
alpha: true,
|
||||
change: function(color) {
|
||||
const elem=this.elem
|
||||
elem.prev().val(color)
|
||||
applyTheme(elem.data('key'),color)
|
||||
},
|
||||
done: function(color) {
|
||||
const elem=this.elem
|
||||
elem.prev().val(color)
|
||||
applyTheme(elem.data('key'),color)
|
||||
},
|
||||
close: function(color){
|
||||
const elem=this.elem
|
||||
elem.prev().val(color)
|
||||
applyTheme(elem.data('key'),color)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetTheme() {
|
||||
customTheme={}
|
||||
addStyle('demo-customTheme',getCSS(customTheme))
|
||||
renderColorPicker()
|
||||
}
|
||||
|
||||
function applyTheme(key,val){
|
||||
customTheme = {...customTheme,...{[key]:val} }
|
||||
addStyle('demo-customTheme',getCSS(customTheme))
|
||||
}
|
||||
|
||||
function applyColorPalette(isDark=false){
|
||||
customTheme={...customTheme, ...originalData[isDark ? 'ColorPaletteDark' : 'ColorPaletteLight']}
|
||||
addStyle('demo-customTheme',getCSS(customTheme))
|
||||
}
|
||||
|
||||
function getCSS(cssVarsObj){
|
||||
return `:root {\n ${
|
||||
Object.entries(cssVarsObj)
|
||||
.map(([key,val])=> ` ${key}: ${val};`)
|
||||
.join('\n')
|
||||
}\n}`
|
||||
}
|
||||
|
||||
async function dropdownCSS(){
|
||||
const hasPrefix = $('input[name="theme-prefix"]').val()
|
||||
const overrideCSS = await(await fetch('./static/src/override.css')).text()
|
||||
const varsCSS = getCSS({...originalData.Default, ...customTheme})
|
||||
let finalCSS=`
|
||||
${hasPrefix
|
||||
? varsCSS.replace(':root',`:root${hasPrefix}`)
|
||||
: varsCSS}\n
|
||||
${hasPrefix
|
||||
? `${hasPrefix}{${overrideCSS}}`
|
||||
: overrideCSS}`;
|
||||
|
||||
// css-next 插件太大,暂时用 less
|
||||
finalCSS = (await window.less.render(finalCSS)).css
|
||||
const alink=document.createElement("a")
|
||||
alink.download='layui-theme-dark-custom.css'
|
||||
alink.href=URL.createObjectURL(new Blob([finalCSS]))
|
||||
document.body.appendChild(alink)
|
||||
alink.click()
|
||||
document.body.removeChild(alink)
|
||||
}
|
||||
|
||||
function addStyle(id,cssStr) {
|
||||
var el=document.getElementById(id)||document.createElement('style')
|
||||
if(!el.isConnected) {
|
||||
el.type='text/css';
|
||||
el.id=id;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.textContent=cssStr;
|
||||
}
|
||||
})
|
||||
</script>
|
||||
412
web/template/admin/apps.html
Normal file
412
web/template/admin/apps.html
Normal file
@@ -0,0 +1,412 @@
|
||||
{{ define "apps.html" }}
|
||||
<section>
|
||||
<h2>应用管理</h2>
|
||||
<div class="layui-btn-container" style="margin:12px 0">
|
||||
<button class="layui-btn" id="btnAddApp"><i class="layui-icon layui-icon-add-1"></i> 新增应用</button>
|
||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteApps"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
||||
<button class="layui-btn layui-btn-normal" id="btnBatchEnableApps"><i class="layui-icon layui-icon-ok-circle"></i> 批量启用</button>
|
||||
<button class="layui-btn layui-btn-warm" id="btnBatchDisableApps"><i class="layui-icon layui-icon-close-fill"></i> 批量禁用</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">筛选</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form layui-form-pane" id="appFilterForm" lay-filter="appFilterForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">搜索</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="search" placeholder="应用名称/UUID" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button type="button" class="layui-btn" id="btnSearchApps">查询</button>
|
||||
<button type="button" class="layui-btn layui-btn-primary" id="btnResetApps">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">应用列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="appsTable" lay-filter="appsTableFilter"></table>
|
||||
<script type="text/html" id="tpl-apps-ops">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
<script type="text/html" id="tpl-apps-status">
|
||||
{{`{{# if(d.status === 1) { }}`}}
|
||||
<span class="layui-badge layui-bg-green">启用</span>
|
||||
{{`{{# } else { }}`}}
|
||||
<span class="layui-badge">禁用</span>
|
||||
{{`{{# } }}`}}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的表单弹层内容:新增/编辑应用 -->
|
||||
<div id="appFormModal" style="display:none;padding:16px">
|
||||
<form class="layui-form layui-form-pane" id="appForm">
|
||||
<input type="hidden" name="id" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">应用名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" placeholder="请输入应用名称" autocomplete="off" class="layui-input" lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">版本号</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="version" placeholder="请输入版本号,默认1.0.0" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="status">
|
||||
<option value="1" selected>启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">强制更新</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="force_update">
|
||||
<option value="0" selected>不开启</option>
|
||||
<option value="1">开启</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">更新方式</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="download_type" lay-filter="downloadTypeChange">
|
||||
<option value="0" selected>不启用更新</option>
|
||||
<option value="1">自动更新</option>
|
||||
<option value="2">手动下载</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item" id="downloadUrlItem">
|
||||
<label class="layui-form-label">下载地址</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="download_url" placeholder="请输入下载地址" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button type="submit" class="layui-btn" lay-submit lay-filter="appFormSubmit">提交</button>
|
||||
<button type="button" class="layui-btn layui-btn-primary" id="btnCancelApp">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'form', 'layer', 'element'], function() {
|
||||
const table = layui.table;
|
||||
const form = layui.form;
|
||||
const layer = layui.layer;
|
||||
const $ = layui.$;
|
||||
|
||||
// 格式化时间函数
|
||||
function formatDateTime(dateStr) {
|
||||
if (!dateStr) return '-';
|
||||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
|
||||
// 渲染表格
|
||||
const appsTable = table.render({
|
||||
elem: '#appsTable',
|
||||
id: 'appsTable',
|
||||
url: '/admin/api/apps/list',
|
||||
parseData: function(res) {
|
||||
// 后端返回的数据结构处理
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.msg || '',
|
||||
count: res.count || 0,
|
||||
data: res.data || []
|
||||
};
|
||||
},
|
||||
request: {
|
||||
pageName: 'page', // 页码的参数名称,默认:page
|
||||
limitName: 'page_size' // 每页数据量的参数名称,默认:limit
|
||||
},
|
||||
method: 'GET',
|
||||
page: true,
|
||||
limit: 20,
|
||||
limits: [10, 20, 50, 100],
|
||||
loading: true,
|
||||
done: function(res, curr, count) {
|
||||
// 表格渲染完成后的回调
|
||||
},
|
||||
cols: [[
|
||||
{ type: 'checkbox', width: 50 },
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'name', title: '应用名称', minWidth: 180 },
|
||||
{ field: 'uuid', title: 'UUID', minWidth: 320 },
|
||||
{ field: 'version', title: '版本', width: 100 },
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
templet: (d) => {
|
||||
if (d.status === 1) return '<span style="color: #5FB878;">启用</span>';
|
||||
return '<span style="color: #FF5722;">禁用</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'secret',
|
||||
title: '密钥',
|
||||
minWidth: 320,
|
||||
templet: (d) => '<span style="font-family: monospace;">' + d.secret + '</span>'
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
templet: (d) => formatDateTime(d.created_at)
|
||||
},
|
||||
{ fixed: 'right', title: '操作', toolbar: '#tpl-apps-ops', width: 120 }
|
||||
]]
|
||||
});
|
||||
|
||||
// 搜索功能
|
||||
$('#btnSearchApps').on('click', function() {
|
||||
const search = $('input[name="search"]').val();
|
||||
appsTable.reload({
|
||||
where: {
|
||||
search: search
|
||||
},
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 重置搜索
|
||||
$('#btnResetApps').on('click', function() {
|
||||
$('#appFilterForm')[0].reset();
|
||||
appsTable.reload({
|
||||
where: {},
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 新增应用
|
||||
$('#btnAddApp').on('click', function() {
|
||||
$('#appForm')[0].reset();
|
||||
$('input[name="id"]').val('');
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '新增应用',
|
||||
content: $('#appFormModal'),
|
||||
area: ['500px', '460px'],
|
||||
btn: false,
|
||||
shadeClose: false
|
||||
});
|
||||
form.render();
|
||||
});
|
||||
|
||||
// 监听更新方式切换(保留事件监听器以备将来扩展)
|
||||
form.on('select(downloadTypeChange)', function(data) {
|
||||
// 下载地址字段现在始终显示,无需切换显示状态
|
||||
});
|
||||
|
||||
// 表单提交
|
||||
form.on('submit(appFormSubmit)', function(data) {
|
||||
const isEdit = data.field.id !== '';
|
||||
const url = isEdit ? '/admin/api/apps/update' : '/admin/api/apps/create';
|
||||
|
||||
// 转换字段类型为正确的数据类型
|
||||
const formData = {
|
||||
...data.field,
|
||||
status: parseInt(data.field.status) || 0,
|
||||
download_type: parseInt(data.field.download_type) || 0,
|
||||
force_update: parseInt(data.field.force_update) || 0
|
||||
};
|
||||
|
||||
// 如果是编辑模式,确保id也是整数
|
||||
if (isEdit) {
|
||||
formData.id = parseInt(data.field.id);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: JSON.stringify(formData),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
layer.closeAll();
|
||||
appsTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// 取消按钮
|
||||
$('#btnCancelApp').on('click', function() {
|
||||
layer.closeAll();
|
||||
});
|
||||
|
||||
// 表格工具栏事件
|
||||
table.on('tool(appsTableFilter)', function(obj) {
|
||||
const data = obj.data;
|
||||
|
||||
if (obj.event === 'edit') {
|
||||
// 编辑
|
||||
$('#appForm')[0].reset();
|
||||
$('input[name="id"]').val(data.id);
|
||||
$('input[name="name"]').val(data.name);
|
||||
$('input[name="version"]').val(data.version);
|
||||
$('select[name="status"]').val(data.status);
|
||||
$('select[name="download_type"]').val(data.download_type || 0);
|
||||
$('input[name="download_url"]').val(data.download_url || '');
|
||||
$('select[name="force_update"]').val(data.force_update || 0);
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '编辑应用',
|
||||
content: $('#appFormModal'),
|
||||
area: ['500px', '460px'],
|
||||
btn: false,
|
||||
shadeClose: false
|
||||
});
|
||||
form.render();
|
||||
|
||||
} else if (obj.event === 'del') {
|
||||
// 删除
|
||||
layer.confirm('确定删除该应用吗?', {icon: 3, title: '提示'}, function(index) {
|
||||
$.ajax({
|
||||
url: '/admin/api/apps/delete',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({id: data.id}),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
appsTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '删除失败', {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '删除失败', {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 批量删除
|
||||
$('#btnBatchDeleteApps').on('click', function() {
|
||||
const checkStatus = table.checkStatus('appsTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要删除的应用', {icon: 2});
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm('确定删除选中的 ' + data.length + ' 个应用吗?', {icon: 3, title: '提示'}, function(index) {
|
||||
const ids = data.map(item => item.id);
|
||||
$.ajax({
|
||||
url: '/admin/api/apps/batch_delete',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({ids: ids}),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
appsTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '批量删除失败', {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '批量删除失败', {icon: 2});
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
});
|
||||
|
||||
// 批量启用
|
||||
$('#btnBatchEnableApps').on('click', function() {
|
||||
const checkStatus = table.checkStatus('appsTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要启用的应用', {icon: 2});
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = data.map(item => item.id);
|
||||
$.ajax({
|
||||
url: '/admin/api/apps/batch_update_status',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({ids: ids, status: 1}),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
appsTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '批量启用失败', {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '批量启用失败', {icon: 2});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 批量禁用
|
||||
$('#btnBatchDisableApps').on('click', function() {
|
||||
const checkStatus = table.checkStatus('appsTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要禁用的应用', {icon: 2});
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = data.map(item => item.id);
|
||||
$.ajax({
|
||||
url: '/admin/api/apps/batch_update_status',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({ids: ids, status: 0}),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
appsTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '批量禁用失败', {icon: 2});
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '批量禁用失败', {icon: 2});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
{{ end }}
|
||||
415
web/template/admin/card_types.html
Normal file
415
web/template/admin/card_types.html
Normal file
@@ -0,0 +1,415 @@
|
||||
{{ define "card_types.html" }}
|
||||
<section>
|
||||
<h2>卡密类型管理</h2>
|
||||
<div class="layui-btn-container" style="margin:12px 0">
|
||||
<button class="layui-btn" id="btnAddCardType"><i class="layui-icon layui-icon-add-1"></i> 新增类型</button>
|
||||
<button class="layui-btn layui-btn-normal" id="btnBatchEnableCardTypes"><i class="layui-icon layui-icon-ok-circle"></i> 批量启用</button>
|
||||
<button class="layui-btn layui-btn-warm" id="btnBatchDisableCardTypes"><i class="layui-icon layui-icon-close-fill"></i> 批量禁用</button>
|
||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteCardTypes"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">筛选</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form layui-form-pane" id="cardTypeFilterForm" lay-filter="cardTypeFilterForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="keyword" placeholder="卡密类型名称" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button type="button" class="layui-btn" id="btnSearchCardTypes">查询</button>
|
||||
<button type="button" class="layui-btn layui-btn-primary" id="btnResetCardTypes">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">卡密类型列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="cardTypesTable" lay-filter="cardTypesTableFilter"></table>
|
||||
<script type="text/html" id="tpl-cardtypes-ops">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的表单弹层内容 -->
|
||||
<div id="cardTypeFormModal" style="display:none;padding:16px">
|
||||
<!-- 参考demo表单2样式,添加layui-form-pane类实现方框风格 -->
|
||||
<form class="layui-form layui-form-pane" id="cardTypeForm">
|
||||
<input type="hidden" name="id" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" required lay-verify="required" placeholder="请输入类型名称" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="status">
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">登录</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="login_types" placeholder="请输入登录方式名称,多个用逗号分隔" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 操作按钮已移除,统一由 layer.open 的 btn 控制“提交/取消” -->
|
||||
<!-- 操作按钮移除:统一由 layer.open 的 btn 控制“提交/取消” -->
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'form', 'layer'], () => {
|
||||
const { table, form, layer, $ } = layui;
|
||||
let currentFormLayerIndex; // 保存当前表单弹窗的索引
|
||||
|
||||
// 渲染表格
|
||||
const cardTypesTable = table.render({
|
||||
elem: '#cardTypesTable',
|
||||
url: '/admin/api/card_types/list',
|
||||
method: 'GET',
|
||||
page: true,
|
||||
limit: 20,
|
||||
limits: [10, 20, 50, 100],
|
||||
loading: true,
|
||||
cols: [[
|
||||
{ type: 'checkbox' },
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'name', title: '名称' },
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
templet: (d) => {
|
||||
return d.status === 1
|
||||
? '<span class="layui-badge layui-bg-green">启用</span>'
|
||||
: '<span class="layui-badge">禁用</span>';
|
||||
}
|
||||
},
|
||||
{ field: 'login_types', title: '登录方式' },
|
||||
{
|
||||
field: 'created_at',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
templet: (d) => {
|
||||
return formatDateTime(d.created_at);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'updated_at',
|
||||
title: '更新时间',
|
||||
width: 180,
|
||||
templet: (d) => {
|
||||
return formatDateTime(d.updated_at);
|
||||
}
|
||||
},
|
||||
{ title: '操作', toolbar: '#tpl-cardtypes-ops', width: 150, fixed: 'right' }
|
||||
]],
|
||||
parseData: (res) => {
|
||||
// 后端已返回正确格式,直接使用
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.msg || '',
|
||||
"count": res.data ? res.data.total : 0,
|
||||
"data": res.data ? res.data.items : []
|
||||
};
|
||||
},
|
||||
request: {
|
||||
pageName: 'page',
|
||||
limitName: 'page_size'
|
||||
},
|
||||
where: {}
|
||||
});
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0') + ' ' +
|
||||
String(date.getHours()).padStart(2, '0') + ':' +
|
||||
String(date.getMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getSeconds()).padStart(2, '0');
|
||||
};
|
||||
|
||||
// 监听表格工具条
|
||||
table.on('tool(cardTypesTableFilter)', (obj) => {
|
||||
const { data, event } = obj;
|
||||
if (event === 'edit') {
|
||||
editCardType(data);
|
||||
} else if (event === 'del') {
|
||||
deleteCardType(data.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 新增卡密类型
|
||||
$('#btnAddCardType').on('click', () => {
|
||||
showCardTypeForm();
|
||||
});
|
||||
|
||||
// 显示表单弹窗(统一使用 layer.open 的按钮作为确认/取消)
|
||||
const showCardTypeForm = (data = null) => {
|
||||
const title = data ? '编辑卡密类型' : '新增卡密类型';
|
||||
currentFormLayerIndex = layer.open({
|
||||
type: 1,
|
||||
title: title,
|
||||
content: $('#cardTypeFormModal'),
|
||||
area: ['500px', '300px'],
|
||||
btn: ['提交', '取消'],
|
||||
btnAlign: 'c',
|
||||
yes: () => {
|
||||
// 点击“提交”时,执行统一的提交方法
|
||||
doCardTypeSubmit();
|
||||
},
|
||||
btn2: (index) => {
|
||||
// 点击“取消”时,关闭当前弹窗
|
||||
layer.close(index);
|
||||
},
|
||||
success: () => {
|
||||
// 成功打开弹窗后渲染表单,并根据是否为编辑模式进行回填
|
||||
form.render();
|
||||
if (data) {
|
||||
// 编辑模式,填充数据
|
||||
$('#cardTypeForm input[name="id"]').val(data.id);
|
||||
$('#cardTypeForm input[name="name"]').val(data.name);
|
||||
$('#cardTypeForm select[name="status"]').val(data.status);
|
||||
$('#cardTypeForm input[name="login_types"]').val(data.login_types);
|
||||
form.render('select');
|
||||
} else {
|
||||
// 新增模式,清空表单
|
||||
$('#cardTypeForm')[0].reset();
|
||||
$('#cardTypeForm input[name="id"]').val('');
|
||||
form.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑卡密类型
|
||||
const editCardType = (data) => {
|
||||
showCardTypeForm(data);
|
||||
};
|
||||
|
||||
// 提交表单(通过 layer.open 的“提交”按钮触发)
|
||||
const doCardTypeSubmit = () => {
|
||||
// 读取表单数据
|
||||
const idValue = $('#cardTypeForm input[name="id"]').val();
|
||||
const formData = {
|
||||
id: idValue ? parseInt(idValue) : 0,
|
||||
name: $('#cardTypeForm input[name="name"]').val().trim(),
|
||||
status: parseInt($('#cardTypeForm select[name="status"]').val()),
|
||||
login_types: $('#cardTypeForm input[name="login_types"]').val().trim()
|
||||
};
|
||||
|
||||
// 校验必填项
|
||||
if (!formData.name) {
|
||||
layer.msg('请输入类型名称', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据是否存在 id 判断是创建还是更新
|
||||
const url = formData.id ? '/admin/api/card_types/update' : '/admin/api/card_types/create';
|
||||
const loadIndex = layer.load(2);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
layer.close(loadIndex);
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg || (formData.id ? '更新成功' : '创建成功'), { icon: 1 });
|
||||
layer.close(currentFormLayerIndex);
|
||||
cardTypesTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
layer.close(loadIndex);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
};
|
||||
|
||||
// 取消按钮已移除,统一由 layer.open 的“取消”按钮处理
|
||||
// 取消按钮已移除:统一由 layer.open 的“取消”按钮处理
|
||||
|
||||
// 删除卡密类型
|
||||
const deleteCardType = (id) => {
|
||||
layer.confirm('确定要删除这个卡密类型吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const loadIndex = layer.load(2);
|
||||
|
||||
fetch('/admin/api/card_types/delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: id })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
cardTypesTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
$('#btnBatchDeleteCardTypes').on('click', () => {
|
||||
const checkStatus = table.checkStatus('cardTypesTable');
|
||||
if (checkStatus.data.length === 0) {
|
||||
layer.msg('请选择要删除的数据', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm(`确定要删除选中的 ${checkStatus.data.length} 条数据吗?`, {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const ids = checkStatus.data.map(item => item.id);
|
||||
const loadIndex = layer.load(2);
|
||||
|
||||
fetch('/admin/api/card_types/batch_delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ids: ids })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
cardTypesTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 批量启用
|
||||
$('#btnBatchEnableCardTypes').on('click', () => {
|
||||
batchUpdateStatus('/admin/api/card_types/batch_enable', '启用');
|
||||
});
|
||||
|
||||
// 批量禁用
|
||||
$('#btnBatchDisableCardTypes').on('click', () => {
|
||||
batchUpdateStatus('/admin/api/card_types/batch_disable', '禁用');
|
||||
});
|
||||
|
||||
// 批量更新状态的通用函数
|
||||
const batchUpdateStatus = (url, action) => {
|
||||
const checkStatus = table.checkStatus('cardTypesTable');
|
||||
if (checkStatus.data.length === 0) {
|
||||
layer.msg('请选择要操作的数据', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm(`确定要${action}选中的 ${checkStatus.data.length} 条数据吗?`, {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const ids = checkStatus.data.map(item => item.id);
|
||||
const loadIndex = layer.load(2);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ids: ids })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
cardTypesTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 搜索功能
|
||||
$('#btnSearchCardTypes').on('click', () => {
|
||||
const keyword = $('#cardTypeFilterForm input[name="keyword"]').val();
|
||||
const status = $('#cardTypeFilterForm select[name="status"]').val();
|
||||
|
||||
cardTypesTable.reload({
|
||||
where: {
|
||||
keyword: keyword,
|
||||
status: status
|
||||
},
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 重置搜索
|
||||
$('#btnResetCardTypes').on('click', () => {
|
||||
$('#cardTypeFilterForm')[0].reset();
|
||||
form.render();
|
||||
cardTypesTable.reload({
|
||||
where: {},
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
771
web/template/admin/cards.html
Normal file
771
web/template/admin/cards.html
Normal file
@@ -0,0 +1,771 @@
|
||||
{{ define "cards.html" }}
|
||||
<section>
|
||||
<h2>卡密管理</h2>
|
||||
<div class="layui-btn-container" style="margin:12px 0">
|
||||
<button class="layui-btn" id="btnAddCard"><i class="layui-icon layui-icon-add-1"></i> 新增卡密</button>
|
||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteCards"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
||||
<button class="layui-btn layui-btn-normal" id="btnBatchEnableCards"><i class="layui-icon layui-icon-ok-circle"></i> 设为未用</button>
|
||||
<button class="layui-btn layui-btn-warm" id="btnBatchDisableCards"><i class="layui-icon layui-icon-close-fill"></i> 设为已用</button>
|
||||
<!-- 新增:导出卡密按钮 -->
|
||||
<button class="layui-btn layui-btn-primary" id="btnExportCards"><i class="layui-icon layui-icon-export"></i> 导出卡密</button>
|
||||
<!-- 新增:导出选中卡密按钮 -->
|
||||
<button class="layui-btn layui-btn-primary" id="btnExportSelectedCards"><i class="layui-icon layui-icon-export"></i> 导出选中</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">筛选</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form layui-form-pane" id="cardFilterForm" lay-filter="cardFilterForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">卡密</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="keyword" placeholder="卡号/批次/备注/任务号" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">类型</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="card_type" id="filterCardTypeSelect">
|
||||
<option value="">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="0">未使用</option>
|
||||
<option value="1">已使用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button type="button" class="layui-btn" id="btnSearchCards">查询</button>
|
||||
<button type="button" class="layui-btn layui-btn-primary" id="btnResetCards">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">卡密列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="cardsTable" lay-filter="cardsTableFilter"></table>
|
||||
<script type="text/html" id="tpl-cards-ops">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的表单弹层内容:新增卡密 -->
|
||||
<div id="cardFormModal" style="display:none;padding:16px">
|
||||
<form class="layui-form layui-form-pane" id="cardForm">
|
||||
<input type="hidden" name="id" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">前缀</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="prefix" placeholder="可选,生成卡号时使用的前缀" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 新增:生成数量(默认1,最大500),位置在前缀之后、大小写之前 -->
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">数量</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="count" min="1" max="500" value="1" placeholder="一次生成的数量(默认1,最大500)" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 新增:生成大小写选项(默认小写),位置在长度之前 -->
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">规则</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="uppercase">
|
||||
<option value="lower" selected>小写</option>
|
||||
<option value="upper">大写</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">长度</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="number" name="length" min="1" max="64" value="18" placeholder="生成卡号的总长度(包含前缀),默认18" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">类型</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="card_type" id="cardTypeSelect">
|
||||
<option value="">请选择类型</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="status">
|
||||
<option value="0">未使用</option>
|
||||
<option value="1">已使用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">备注</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="remark" placeholder="可填写备注信息" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移除:内置“操作/提交/取消”按钮,统一由 layer.open 的 btn 控制 -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的表单弹层内容:编辑卡密 -->
|
||||
<div id="cardEditFormModal" style="display:none;padding:16px">
|
||||
<form class="layui-form layui-form-pane" id="cardEditForm">
|
||||
<input type="hidden" name="id" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">类型</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="card_type" id="cardEditTypeSelect">
|
||||
<option value="">请选择类型</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="status">
|
||||
<option value="0">未使用</option>
|
||||
<option value="1">已使用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">任务号</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="task_no" placeholder="可选,支持填写/清空任务号" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">备注</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="remark" placeholder="可填写备注信息" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移除:内置“操作/提交/取消”按钮,统一由 layer.open 的 btn 控制 -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 新增:导出条件弹窗(隐藏) -->
|
||||
<div id="cardExportModal" style="display:none;padding:16px">
|
||||
<form class="layui-form layui-form-pane" id="cardExportForm" lay-filter="cardExportForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="0">未使用</option>
|
||||
<option value="1">已使用</option>
|
||||
<option value="2">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">类型</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="card_type" id="exportCardTypeSelect">
|
||||
<option value="">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">批次</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="batch" placeholder="按批次模糊匹配" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">备注</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="remark" placeholder="按备注模糊匹配" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
layui.use(['table', 'form', 'layer'], () => {
|
||||
const { table, form, layer, $ } = layui;
|
||||
let currentFormLayerIndex; // 保存当前表单弹窗的索引
|
||||
let cardTypes = []; // 存储卡密类型数据
|
||||
// 中文注释:以下三个标志用于协调类型列表和表格渲染的先后关系,避免出现“未知类型”
|
||||
let cardTypesLoaded = false; // 类型列表是否已加载完成
|
||||
let tableFirstRendered = false; // 表格是否已完成首次渲染
|
||||
let tableReloadedAfterTypes = false; // 类型加载后是否已触发表格的二次渲染
|
||||
|
||||
// 格式化时间的辅助函数
|
||||
const formatDateTime = (dateStr) => {
|
||||
if (!dateStr) return '-';
|
||||
const date = new Date(dateStr);
|
||||
return String(date.getFullYear()) + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0') + ' ' +
|
||||
String(date.getHours()).padStart(2, '0') + ':' +
|
||||
String(date.getMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getSeconds()).padStart(2, '0');
|
||||
};
|
||||
|
||||
// 获取卡密类型名称
|
||||
// 中文注释:根据 card_type_id 在缓存的 cardTypes 中查找对应的类型名称;
|
||||
// 为避免后端返回的 id 与前端数据在类型上不一致(字符串/数字)导致匹配失败,这里统一转换为数字再比较
|
||||
const getCardTypeName = (cardTypeId) => {
|
||||
const idNum = Number(cardTypeId);
|
||||
const cardType = cardTypes.find(type => Number(type.id) === idNum);
|
||||
return cardType ? cardType.name : '未知类型';
|
||||
};
|
||||
|
||||
// 渲染表格
|
||||
const cardsTable = table.render({
|
||||
elem: '#cardsTable',
|
||||
id: 'cardsTable',
|
||||
url: '/admin/api/cards/list',
|
||||
parseData: function(res) {
|
||||
// 后端返回的数据结构:{items, total, page, page_size, pages}
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.msg || '',
|
||||
count: res.data ? res.data.total : 0,
|
||||
data: res.data ? res.data.items : []
|
||||
};
|
||||
},
|
||||
request: {
|
||||
pageName: 'page', // 页码的参数名称,默认:page
|
||||
limitName: 'page_size' // 每页数据量的参数名称,默认:limit
|
||||
},
|
||||
method: 'GET',
|
||||
page: true,
|
||||
limit: 20,
|
||||
limits: [10, 20, 50, 100],
|
||||
loading: true,
|
||||
// 中文注释:表格首次渲染完成后,如果类型已经加载,则进行一次刷新以正确显示类型名称
|
||||
done: function() {
|
||||
if (!tableFirstRendered) {
|
||||
tableFirstRendered = true;
|
||||
if (cardTypesLoaded && !tableReloadedAfterTypes) {
|
||||
tableReloadedAfterTypes = true;
|
||||
cardsTable.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
cols: [[
|
||||
{ type: 'checkbox', width: 50 },
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'card_number', title: '卡号', minWidth: 150 },
|
||||
{
|
||||
field: 'card_type_id',
|
||||
title: '类型',
|
||||
width: 100,
|
||||
templet: (d) => d.card_type_name || getCardTypeName(d.card_type_id)
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 80,
|
||||
templet: (d) => {
|
||||
if (d.status === 0) return '<span style="color: #5FB878;">未使用</span>';
|
||||
if (d.status === 1) return '<span style="color: #FF5722;">已使用</span>';
|
||||
return '<span style="color: #999;">禁用</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'task_no',
|
||||
title: '任务号',
|
||||
minWidth: 140,
|
||||
templet: (d) => d.task_no || '-'
|
||||
},
|
||||
{ field: 'batch', title: '批次', minWidth: 60 },
|
||||
{ field: 'remark', title: '备注', minWidth: 100 },
|
||||
{
|
||||
field: 'used_at',
|
||||
title: '使用时间',
|
||||
width: 180,
|
||||
templet: (d) => formatDateTime(d.used_at)
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
templet: (d) => formatDateTime(d.created_at)
|
||||
},
|
||||
{ fixed: 'right', title: '操作', toolbar: '#tpl-cards-ops', width: 120 }
|
||||
]]
|
||||
});
|
||||
|
||||
// 加载卡密类型数据
|
||||
const loadCardTypes = () => {
|
||||
fetch('/admin/api/cards/types?all=1')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.code === 0) {
|
||||
cardTypes = data.data || [];
|
||||
// 填充筛选下拉框
|
||||
const filterSelect = $('#filterCardTypeSelect');
|
||||
filterSelect.empty().append('<option value="">全部</option>');
|
||||
cardTypes.forEach(type => {
|
||||
if (type.status === 1) { // 只显示启用的类型
|
||||
filterSelect.append(`<option value="${type.id}">${type.name}</option>`);
|
||||
}
|
||||
});
|
||||
// 填充新增表单下拉框
|
||||
const cardTypeSelect = $('#cardTypeSelect');
|
||||
cardTypeSelect.empty().append('<option value="">请选择类型</option>');
|
||||
cardTypes.forEach(type => {
|
||||
if (type.status === 1) { // 只显示启用的类型
|
||||
cardTypeSelect.append(`<option value="${type.id}">${type.name}</option>`);
|
||||
}
|
||||
});
|
||||
// 填充编辑表单下拉框
|
||||
const cardEditTypeSelect = $('#cardEditTypeSelect');
|
||||
cardEditTypeSelect.empty().append('<option value="">请选择类型</option>');
|
||||
cardTypes.forEach(type => {
|
||||
if (type.status === 1) { // 只显示启用的类型
|
||||
cardEditTypeSelect.append(`<option value="${type.id}">${type.name}</option>`);
|
||||
}
|
||||
});
|
||||
// 新增:填充导出弹窗下拉框(显示全部状态的类型,方便条件筛选)
|
||||
const exportTypeSelect = $('#exportCardTypeSelect');
|
||||
exportTypeSelect.empty().append('<option value="">全部</option>');
|
||||
cardTypes.forEach(type => {
|
||||
exportTypeSelect.append(`<option value="${type.id}">${type.name}</option>`);
|
||||
});
|
||||
|
||||
form.render('select');
|
||||
|
||||
// 中文注释:标记类型加载完成;如表格已首次渲染,则进行一次性刷新以正确显示类型名称
|
||||
cardTypesLoaded = true;
|
||||
if (tableFirstRendered && !tableReloadedAfterTypes) {
|
||||
tableReloadedAfterTypes = true;
|
||||
cardsTable.reload();
|
||||
}
|
||||
// 卡密类型加载完成,表格会根据需要自动进行一次刷新
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('加载卡密类型失败:', error);
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化加载卡密类型
|
||||
loadCardTypes();
|
||||
|
||||
// 监听表格工具条
|
||||
table.on('tool(cardsTableFilter)', (obj) => {
|
||||
const { data, event } = obj;
|
||||
if (event === 'edit') {
|
||||
editCard(data);
|
||||
} else if (event === 'del') {
|
||||
deleteCard(data.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 新增卡密
|
||||
$('#btnAddCard').on('click', () => {
|
||||
showCardForm();
|
||||
});
|
||||
|
||||
// 显示新增卡密表单弹窗
|
||||
// 中文注释:弹出新增/编辑表单的公共方法,采用 layer.open + btn/yes/btn2 的“确认框风格”
|
||||
// data 为空表示新增;存在表示编辑。通过 yes 回调直接调用提交流程函数。
|
||||
const showCardForm = (data = null) => {
|
||||
const title = data ? '编辑卡密' : '新增卡密';
|
||||
const modalId = data ? '#cardEditFormModal' : '#cardFormModal';
|
||||
const formId = data ? '#cardEditForm' : '#cardForm';
|
||||
|
||||
const areaHeight = data ? '420px' : '600px';
|
||||
|
||||
currentFormLayerIndex = layer.open({
|
||||
type: 1,
|
||||
title: title,
|
||||
content: $(modalId),
|
||||
area: ['500px', areaHeight],
|
||||
btn: ['提交', '取消'],
|
||||
btnAlign: 'c',
|
||||
yes: () => {
|
||||
if (data) {
|
||||
doEditCardSubmit();
|
||||
} else {
|
||||
doCreateCardSubmit();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
btn2: (index) => {
|
||||
layer.close(index);
|
||||
},
|
||||
success: () => {
|
||||
form.render();
|
||||
if (data) {
|
||||
$(formId + ' input[name="id"]').val(data.id);
|
||||
$(formId + ' select[name="card_type"]').val(data.card_type_id);
|
||||
$(formId + ' select[name="status"]').val(data.status);
|
||||
$(formId + ' textarea[name="remark"]').val(data.remark || '');
|
||||
// 中文注释:编辑模式下,回填已有的任务号(若无则为空字符串)
|
||||
$(formId + ' input[name="task_no"]').val(data.task_no || '');
|
||||
form.render('select');
|
||||
} else {
|
||||
$(formId)[0].reset();
|
||||
$(formId + ' input[name="id"]').val('');
|
||||
// 中文注释:新增模式下显式清空任务号,避免出现上一次编辑残留
|
||||
$(formId + ' input[name="task_no"]').val('');
|
||||
form.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑卡密
|
||||
const editCard = (data) => {
|
||||
showCardForm(data);
|
||||
};
|
||||
|
||||
// 提交新增卡密表单
|
||||
// 提交逻辑函数化,供弹窗按钮直接调用,避免依赖模板内按钮
|
||||
// 中文注释:提交“新增卡密”表单,完成校验、请求与反馈
|
||||
const doCreateCardSubmit = () => {
|
||||
const uppercaseValue = $('#cardForm select[name="uppercase"]').val();
|
||||
const formData = {
|
||||
prefix: $('#cardForm input[name="prefix"]').val() || '',
|
||||
count: parseInt($('#cardForm input[name="count"]').val()) || 1,
|
||||
uppercase: uppercaseValue === 'upper',
|
||||
length: parseInt($('#cardForm input[name="length"]').val()) || 18,
|
||||
card_type_id: parseInt($('#cardForm select[name="card_type"]').val()),
|
||||
status: parseInt($('#cardForm select[name="status"]').val()),
|
||||
remark: $('#cardForm textarea[name="remark"]').val() || ''
|
||||
};
|
||||
|
||||
// 校验
|
||||
if (!formData.card_type_id) {
|
||||
layer.msg('请选择卡密类型', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (formData.count < 1 || formData.count > 500) {
|
||||
layer.msg('生成数量必须在1-500之间', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (formData.length < 1 || formData.length > 64) {
|
||||
layer.msg('卡号长度必须在1-64之间', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
const loadIndex = layer.load(2);
|
||||
fetch('/admin/api/cards/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
layer.close(currentFormLayerIndex);
|
||||
cardsTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('新增卡密失败:', error);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
};
|
||||
|
||||
// 中文注释:提交“编辑卡密”表单,完成校验、请求与反馈
|
||||
const doEditCardSubmit = () => {
|
||||
const idValue = $('#cardEditForm input[name="id"]').val();
|
||||
const taskNoRaw = $('#cardEditForm input[name="task_no"]').val();
|
||||
const hasTaskNoField = true; // 中文注释:该字段始终存在,通过值是否为空决定清空或设置
|
||||
const formData = {
|
||||
id: idValue ? parseInt(idValue) : 0,
|
||||
card_type_id: parseInt($('#cardEditForm select[name="card_type"]').val()),
|
||||
status: parseInt($('#cardEditForm select[name="status"]').val()),
|
||||
remark: $('#cardEditForm textarea[name="remark"]').val() || ''
|
||||
};
|
||||
|
||||
// 当任务号输入框有值或被清空时,也要传递 task_no 字段(允许清空)
|
||||
if (hasTaskNoField) {
|
||||
formData.task_no = (taskNoRaw || '').trim();
|
||||
}
|
||||
|
||||
// 校验
|
||||
if (!formData.id) {
|
||||
layer.msg('卡密ID不能为空', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (!formData.card_type_id) {
|
||||
layer.msg('请选择卡密类型', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
const loadIndex = layer.load(2);
|
||||
fetch('/admin/api/cards/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
layer.close(currentFormLayerIndex);
|
||||
cardsTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('编辑卡密失败:', error);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
};
|
||||
|
||||
// 删除卡密
|
||||
const deleteCard = (id) => {
|
||||
layer.confirm('确定要删除这个卡密吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const loadIndex = layer.load(2);
|
||||
|
||||
fetch('/admin/api/cards/delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: parseInt(id) })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
cardsTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('删除卡密失败:', error);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 批量删除卡密
|
||||
$('#btnBatchDeleteCards').on('click', () => {
|
||||
const checkStatus = table.checkStatus('cardsTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要删除的卡密', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm(`确定要删除选中的 ${data.length} 个卡密吗?`, {
|
||||
icon: 3,
|
||||
title: '批量删除确认'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const loadIndex = layer.load(2);
|
||||
const ids = data.map(item => item.id);
|
||||
|
||||
fetch('/admin/api/cards/batch_delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ids: ids })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
cardsTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('批量删除卡密失败:', error);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 批量启用卡密
|
||||
$('#btnBatchEnableCards').on('click', () => {
|
||||
batchUpdateStatus(0, '设为未用');
|
||||
});
|
||||
|
||||
// 批量禁用卡密
|
||||
$('#btnBatchDisableCards').on('click', () => {
|
||||
batchUpdateStatus(1, '设为已用');
|
||||
});
|
||||
|
||||
// 批量更新状态
|
||||
const batchUpdateStatus = (status, statusText) => {
|
||||
const checkStatus = table.checkStatus('cardsTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要操作的卡密', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm(`确定要${statusText}选中的 ${data.length} 个卡密吗?`, {
|
||||
icon: 3,
|
||||
title: `批量${statusText}确认`
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const loadIndex = layer.load(2);
|
||||
const ids = data.map(item => item.id);
|
||||
|
||||
fetch('/admin/api/cards/batch_update_status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ids: ids, status: status })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
layer.close(loadIndex);
|
||||
if (data.code === 0) {
|
||||
layer.msg(data.msg, { icon: 1 });
|
||||
cardsTable.reload();
|
||||
} else {
|
||||
layer.msg(data.msg, { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error(`批量${statusText}卡密失败:`, error);
|
||||
layer.msg('网络错误,请重试', { icon: 2 });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 搜索功能
|
||||
$('#btnSearchCards').on('click', () => {
|
||||
const formData = form.val('cardFilterForm');
|
||||
const where = {};
|
||||
if (formData.card_type) {
|
||||
where.card_type_id = formData.card_type;
|
||||
}
|
||||
if (formData.status !== '') {
|
||||
where.status = formData.status;
|
||||
}
|
||||
if (formData.keyword && formData.keyword.trim() !== '') {
|
||||
// 中文注释:将关键字作为 keyword 传给后端,由后端在 card_number、remark、batch 三个字段中进行模糊匹配
|
||||
where.keyword = formData.keyword.trim();
|
||||
}
|
||||
table.reload('cardsTable', { where, page: { curr: 1 } });
|
||||
});
|
||||
|
||||
$('#btnResetCards').on('click', () => {
|
||||
$('#cardFilterForm')[0].reset();
|
||||
form.render();
|
||||
table.reload('cardsTable', { where: {}, page: { curr: 1 } });
|
||||
});
|
||||
|
||||
// =============== 导出卡密逻辑 ===============
|
||||
// 显示“导出卡密”弹窗
|
||||
// 中文注释:弹出导出条件选择弹窗,允许管理员按状态/类型/批次/备注筛选导出
|
||||
const showExportDialog = () => {
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '导出卡密',
|
||||
content: $('#cardExportModal'),
|
||||
area: ['520px', '360px'],
|
||||
btn: ['导出', '取消'],
|
||||
btnAlign: 'c',
|
||||
yes: (index) => {
|
||||
doExportCards();
|
||||
layer.close(index); // 关闭导出弹窗
|
||||
layer.msg('卡密导出中...', { icon: 1 });
|
||||
return false;
|
||||
},
|
||||
success: () => {
|
||||
form.render();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 绑定按钮事件
|
||||
$('#btnExportCards').on('click', () => {
|
||||
showExportDialog();
|
||||
});
|
||||
|
||||
// 导出选中卡密
|
||||
// 中文注释:导出当前表格中选中的卡密,无需弹窗筛选,直接根据选中的卡密ID进行导出
|
||||
$('#btnExportSelectedCards').on('click', () => {
|
||||
const checkStatus = table.checkStatus('cardsTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要导出的卡密', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm(`确定要导出选中的 ${data.length} 个卡密吗?`, {
|
||||
icon: 3,
|
||||
title: '导出选中确认'
|
||||
}, (index) => {
|
||||
layer.close(index);
|
||||
const ids = data.map(item => item.id);
|
||||
const params = new URLSearchParams();
|
||||
params.set('ids', ids.join(','));
|
||||
|
||||
const url = '/admin/api/cards/export_selected?' + params.toString();
|
||||
triggerDownload(url);
|
||||
|
||||
layer.msg(`正在导出 ${data.length} 个卡密...`, { icon: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
// 执行导出
|
||||
// 中文注释:根据表单条件拼接导出URL,并以下载方式触发导出(CSV 文件)
|
||||
const doExportCards = () => {
|
||||
const formData = form.val('cardExportForm');
|
||||
const params = new URLSearchParams();
|
||||
if (formData.status !== '') params.set('status', formData.status);
|
||||
if (formData.card_type) params.set('card_type_id', formData.card_type);
|
||||
if (formData.batch && formData.batch.trim() !== '') params.set('batch', formData.batch.trim());
|
||||
if (formData.remark && formData.remark.trim() !== '') params.set('remark', formData.remark.trim());
|
||||
|
||||
const url = '/admin/api/cards/export' + (params.toString() ? ('?' + params.toString()) : '');
|
||||
triggerDownload(url);
|
||||
};
|
||||
|
||||
// 触发下载
|
||||
// 中文注释:通过创建临时 <a> 元素点击,保持当前页面不跳转,触发后端附件下载
|
||||
const triggerDownload = (url) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.target = '_blank';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
236
web/template/admin/dashboard.html
Normal file
236
web/template/admin/dashboard.html
Normal file
@@ -0,0 +1,236 @@
|
||||
{{ define "dashboard.html" }}
|
||||
<section>
|
||||
<h2>系统信息</h2>
|
||||
<div class="layui-row layui-col-space15" style="margin-top:12px">
|
||||
<div class="layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">基本信息</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="system-info-grid">
|
||||
<div class="system-info-item">
|
||||
<div class="system-info-label">版本</div>
|
||||
<div class="system-info-value">{{ .Version }}</div>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<div class="system-info-label">运行模式</div>
|
||||
<div class="system-info-value">{{ .Mode }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">运行状态</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="system-info-grid">
|
||||
<div class="system-info-item">
|
||||
<div class="system-info-label">数据库</div>
|
||||
<div class="system-info-value">{{ .DBType }}</div>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<div class="system-info-label">运行时长</div>
|
||||
<div class="system-info-value">{{ .Uptime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 卡密统计区域 -->
|
||||
<section style="margin-top:16px">
|
||||
<div class="layui-row layui-col-space15">
|
||||
<!-- 当日卡密统计 -->
|
||||
<div class="layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">当日卡密统计 <span class="layui-badge layui-bg-blue" style="margin-left:8px">总数:<span id="today-total">-</span></span></div>
|
||||
<div class="layui-card-body">
|
||||
<div id="chart-today-by-status" style="width:100%;height:320px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 所有卡密统计 -->
|
||||
<div class="layui-col-md6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">所有卡密统计 <span class="layui-badge layui-bg-blue" style="margin-left:8px">总数:<span id="all-total">-</span></span></div>
|
||||
<div class="layui-card-body">
|
||||
<div id="chart-all-by-status" style="width:100%;height:320px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 30天走势图 -->
|
||||
<div class="layui-row layui-col-space15" style="margin-top:16px">
|
||||
<div class="layui-col-md12">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">近30天卡密走势</div>
|
||||
<div class="layui-card-body">
|
||||
<div id="chart-trend-30days" style="width:100%;height:360px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// 仪表盘统计脚本(采用箭头函数与中文注释)
|
||||
layui.use(['layer', 'util'], function(){
|
||||
const layer = layui.layer;
|
||||
const util = layui.util;
|
||||
const $ = layui.$;
|
||||
|
||||
// 全局引用:ECharts CDN 地址
|
||||
const echartsCdn = 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js';
|
||||
|
||||
// 工具函数:加载 ECharts 库(若已加载则直接回调)
|
||||
// 功能:通过全局的 loadScript 方法按需加载图表库,避免重复加载
|
||||
const ensureECharts = (cb) => {
|
||||
if (window.echarts) { cb && cb(); return; }
|
||||
if (typeof loadScript === 'function') {
|
||||
loadScript(echartsCdn, () => cb && cb());
|
||||
} else {
|
||||
// 兜底:直接插入 <script>
|
||||
const s = document.createElement('script');
|
||||
s.src = echartsCdn;
|
||||
s.onload = () => cb && cb();
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
};
|
||||
|
||||
// 工具函数:状态码 -> 名称 映射
|
||||
// 说明:卡密状态映射,0=未使用,1=已使用,2=禁用
|
||||
const getStatusText = (s) => {
|
||||
const map = {0:'未使用',1:'已使用',2:'禁用'};
|
||||
const k = Number(s);
|
||||
return map[k] ?? String(s);
|
||||
};
|
||||
|
||||
// 工具函数:状态码 -> 颜色 映射(与徽章风格一致,尽量贴近 Layui 配色)
|
||||
const getStatusColor = (s) => {
|
||||
switch (Number(s)) {
|
||||
case 0: return '#1E9FFF'; // 蓝色 - 未使用
|
||||
case 1: return '#5FB878'; // 绿色 - 已使用
|
||||
case 2: return '#FF5722'; // 红色 - 禁用
|
||||
default: return '#909399'; // 灰色 - 默认
|
||||
}
|
||||
};
|
||||
|
||||
// 函数:渲染饼图
|
||||
// 说明:接收状态分布对象(键为状态码,值为数量),绘制环形图
|
||||
const renderPie = (domId, byStatus) => {
|
||||
const el = document.getElementById(domId);
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
|
||||
const codes = [0,1,2]; // 卡密状态:0=未使用,1=已使用,2=禁用
|
||||
const data = codes.map(code => ({
|
||||
name: getStatusText(code),
|
||||
value: Number((byStatus && byStatus[code]) || 0),
|
||||
itemStyle: { color: getStatusColor(code) }
|
||||
}));
|
||||
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { top: 'bottom' },
|
||||
series: [{
|
||||
name: '按状态分布',
|
||||
type: 'pie',
|
||||
radius: ['38%', '68%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: { formatter: '{b}: {c} ({d}%)' },
|
||||
data
|
||||
}]
|
||||
});
|
||||
|
||||
// 自适应
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
return chart;
|
||||
};
|
||||
|
||||
// 函数:渲染 30 天折线图
|
||||
// 说明:三条序列:total/used/unused,对应后台返回的数组
|
||||
const renderTrend = (domId, trend) => {
|
||||
const el = document.getElementById(domId);
|
||||
if (!el) return;
|
||||
const chart = echarts.init(el);
|
||||
|
||||
const dates = (trend && trend.dates) || [];
|
||||
const total = (trend && trend.total) || [];
|
||||
const used = (trend && trend.used) || [];
|
||||
const unused = (trend && trend.unused) || [];
|
||||
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['总数', '已使用', '未使用'] },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', boundaryGap: false, data: dates },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{ name: '总数', type: 'line', smooth: true, data: total, itemStyle: { color: '#909399' } },
|
||||
{ name: '已使用', type: 'line', smooth: true, data: used, itemStyle: { color: getStatusColor(1) } },
|
||||
{ name: '未使用', type: 'line', smooth: true, data: unused, itemStyle: { color: getStatusColor(0) } }
|
||||
]
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => chart.resize());
|
||||
return chart;
|
||||
};
|
||||
|
||||
// 函数:拉取概览并渲染
|
||||
// 说明:请求 /admin/api/cards/stats_overview,更新总数文本并渲染两个饼图
|
||||
const loadAndRenderOverview = () => {
|
||||
$.get('/admin/api/cards/stats_overview', (res) => {
|
||||
if (!res || res.code !== 0) { layer.msg(res && res.msg ? res.msg : '获取统计概览失败'); return; }
|
||||
const data = res.data || {};
|
||||
$('#today-total').text((data.today && data.today.total) ?? '-');
|
||||
$('#all-total').text((data.all && data.all.total) ?? '-');
|
||||
// 渲染饼图
|
||||
renderPie('chart-today-by-status', data.today ? data.today.by_status : {});
|
||||
renderPie('chart-all-by-status', data.all ? data.all.by_status : {});
|
||||
});
|
||||
};
|
||||
|
||||
// 函数:拉取 30 天数据并渲染折线图
|
||||
// 说明:请求 /admin/api/cards/trend_30days,渲染趋势图
|
||||
const loadAndRenderTrend = () => {
|
||||
$.get('/admin/api/cards/trend_30days', (res) => {
|
||||
if (!res || res.code !== 0) { layer.msg(res && res.msg ? res.msg : '获取30天趋势失败'); return; }
|
||||
renderTrend('chart-trend-30days', res.data || {});
|
||||
});
|
||||
};
|
||||
|
||||
// 函数:刷新基本信息和运行状态
|
||||
// 说明:请求后台获取最新的系统信息并更新页面显示
|
||||
const refreshSystemInfo = () => {
|
||||
$.get('/admin/api/system/info', (res) => {
|
||||
if (res && res.code === 0 && res.data) {
|
||||
const data = res.data;
|
||||
// 更新运行时长
|
||||
if (data.uptime) {
|
||||
$('.system-info-item').each(function() {
|
||||
const label = $(this).find('.system-info-label').text();
|
||||
if (label === '运行时长') {
|
||||
$(this).find('.system-info-value').text(data.uptime);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).fail(() => {
|
||||
console.log('获取系统信息失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 入口:确保 ECharts 已加载后开始渲染
|
||||
ensureECharts(() => {
|
||||
loadAndRenderOverview();
|
||||
loadAndRenderTrend();
|
||||
|
||||
// 立即刷新一次系统信息
|
||||
refreshSystemInfo();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
76
web/template/admin/layout.html
Normal file
76
web/template/admin/layout.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{{ .Title }} - {{ .SystemName }}</title>
|
||||
<link rel="stylesheet" href="/static/css/admin.css" />
|
||||
<script type="module" src="./static/lib/include.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-layout layui-layout-admin" id="app">
|
||||
<div class="layui-header">
|
||||
<!-- 头部区域(可配合layui 已有的水平导航) -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<!-- 移动端显示 -->
|
||||
<li class="layui-nav-item layui-show-xs-inline-block" lay-header-event="menuLeft">
|
||||
<i class="layui-icon layui-icon-spread-left"></i>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<!-- 刷新页面按钮 -->
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;" id="refresh-btn" style="background-color: unset" title="刷新页面">
|
||||
<i class="layui-icon layui-icon-refresh-3" style="font-size: 20px"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="layui-nav-item">
|
||||
<i id="change-theme" class="layui-icon layui-icon-theme" style="font-size: 20px"></i>
|
||||
</li>
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;" id="logout-btn" style="background-color: unset" title="退出登录">
|
||||
<i class="layui-icon layui-icon-logout" style="font-size: 20px"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="layui-side layui-bg-black">
|
||||
<div class="layui-side-scroll">
|
||||
<!-- 左侧导航区域 -->
|
||||
<div class="layui-logo layui-bg-black logo-enhanced">{{ .SystemName }}</div>
|
||||
<ul class="layui-nav layui-nav-tree" lay-shrink="all" lay-unselect lay-filter="nav-side" id="ws-nav-side">
|
||||
<li class="layui-nav-item">
|
||||
<a class="" href="javascript:;">系统管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="dashboard" href="javascript:;">仪表盘</a></dd>
|
||||
<dd><a data-path="user" href="javascript:;">个人资料</a></dd>
|
||||
<dd><a data-path="settings" href="javascript:;">系统设置</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item">
|
||||
<a href="javascript:;">应用管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="apps" href="javascript:;">应用列表</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item">
|
||||
<a href="javascript:;">卡密管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="logintypes" href="javascript:;">登录类型</a></dd>
|
||||
<dd><a data-path="cardtypes" href="javascript:;">卡密类型</a></dd>
|
||||
<dd><a data-path="cards" href="javascript:;">卡密列表</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-body">
|
||||
<!-- 内容主体区域 -->
|
||||
<wc-include id="router-view" allow-scripts></wc-include>
|
||||
</div>
|
||||
<div class="layui-footer">{{ .FooterText }}</div>
|
||||
</div>
|
||||
<script type="module" src="./static/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
259
web/template/admin/login.html
Normal file
259
web/template/admin/login.html
Normal file
@@ -0,0 +1,259 @@
|
||||
{{/* 管理员登录页面模板:使用layui构建的登录界面 */}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>{{ .Title }}</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/layui@2.10.1/dist/css/layui.css">
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.login-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
.login-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
}
|
||||
.login-header p {
|
||||
margin: 8px 0 0;
|
||||
opacity: 0.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
.login-form {
|
||||
padding: 40px 30px;
|
||||
}
|
||||
.layui-form-item {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.layui-input {
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 4px;
|
||||
padding: 12px 15px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.layui-input:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
.layui-btn-fluid {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.layui-btn-fluid:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
/* 修复登录按钮文字垂直位置偏下:使用flex进行垂直水平居中,并设置固定高度 */
|
||||
.login-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
line-height: normal; /* 避免与高度不一致导致的文字偏移 */
|
||||
font-weight: 500;
|
||||
}
|
||||
.error-msg {
|
||||
color: #ff5722;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 响应式设计 - 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.login-container {
|
||||
margin: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.login-header {
|
||||
padding: 25px 15px;
|
||||
}
|
||||
.login-header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
.login-form {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
.layui-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-container {
|
||||
margin: 5px;
|
||||
border-radius: 0;
|
||||
min-height: calc(100vh - 10px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.login-header {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
.login-header h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.login-form {
|
||||
padding: 25px 15px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.layui-input {
|
||||
padding: 15px;
|
||||
font-size: 16px; /* 防止iOS缩放 */
|
||||
}
|
||||
.login-btn {
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕适配 */
|
||||
@media (max-width: 320px) {
|
||||
.login-form {
|
||||
padding: 20px 10px;
|
||||
}
|
||||
.login-header {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1>{{ .SystemName }}</h1>
|
||||
<p>管理员登录</p>
|
||||
</div>
|
||||
|
||||
<div class="login-form">
|
||||
<form class="layui-form" id="loginForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-username"></i>
|
||||
</div>
|
||||
<input type="text" name="username" placeholder="请输入用户名" lay-verify="required" lay-reqtext="请输入用户名" class="layui-input" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-password"></i>
|
||||
</div>
|
||||
<input type="password" name="password" placeholder="请输入密码" lay-verify="required" lay-reqtext="请输入密码" class="layui-input" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<input type="checkbox" name="remember" title="记住登录状态" lay-skin="primary">
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<button class="layui-btn layui-btn-fluid login-btn" lay-submit lay-filter="login">立即登录</button>
|
||||
</div>
|
||||
|
||||
<div class="error-msg" id="errorMsg"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>{{ .FooterText }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/layui@2.10.1/dist/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['form', 'layer'], function(){
|
||||
var form = layui.form;
|
||||
var layer = layui.layer;
|
||||
|
||||
// 登录提交回调:向 /admin/login 发送请求,并依据 code===0 判断成功与否
|
||||
form.on('submit(login)', function(data){
|
||||
var loadIndex = layer.load(1, {
|
||||
shade: [0.1, '#fff']
|
||||
});
|
||||
|
||||
// 发送登录请求
|
||||
fetch('/admin/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data.field)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
layer.close(loadIndex);
|
||||
|
||||
// 根据统一接口:code === 0 表示成功
|
||||
const isOk = result && result.code === 0;
|
||||
if (isOk) {
|
||||
layer.msg('登录成功', {
|
||||
icon: 1,
|
||||
time: 1500
|
||||
}, function(){
|
||||
const redirect = (result.data && result.data.redirect) || '/admin';
|
||||
window.location.href = redirect;
|
||||
});
|
||||
} else {
|
||||
const msg = (result && (result.msg || result.message)) || '登录失败,请检查用户名和密码';
|
||||
document.getElementById('errorMsg').style.display = 'block';
|
||||
document.getElementById('errorMsg').textContent = msg;
|
||||
layer.msg(msg, {icon: 2});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('登录错误:', error);
|
||||
document.getElementById('errorMsg').style.display = 'block';
|
||||
document.getElementById('errorMsg').textContent = '网络错误,请稍后重试';
|
||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
||||
});
|
||||
|
||||
return false; // 阻止表单跳转
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
422
web/template/admin/login_types.html
Normal file
422
web/template/admin/login_types.html
Normal file
@@ -0,0 +1,422 @@
|
||||
{{ define "login_types.html" }}
|
||||
<section>
|
||||
<h2>登录方式管理</h2>
|
||||
<div class="layui-btn-container" style="margin:12px 0">
|
||||
<button class="layui-btn" id="btnAddLoginType"><i class="layui-icon layui-icon-add-1"></i> 新增方式</button>
|
||||
<button class="layui-btn layui-btn-normal" id="btnBatchEnableLoginTypes"><i class="layui-icon layui-icon-ok-circle"></i> 批量启用</button>
|
||||
<button class="layui-btn layui-btn-warm" id="btnBatchDisableLoginTypes"><i class="layui-icon layui-icon-close-fill"></i> 批量禁用</button>
|
||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteLoginTypes"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">筛选</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form layui-form-pane" id="loginTypeFilterForm" lay-filter="loginTypeFilterForm">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="keyword" placeholder="登录方式名称" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="status">
|
||||
<option value="">全部</option>
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button type="button" class="layui-btn" id="btnSearchLoginTypes">查询</button>
|
||||
<button type="button" class="layui-btn layui-btn-primary" id="btnResetLoginTypes">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
<div class="layui-card-header">登录方式列表</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="loginTypesTable" lay-filter="loginTypesTableFilter"></table>
|
||||
<script type="text/html" id="tpl-logintypes-ops">
|
||||
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
|
||||
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的表单弹层内容 -->
|
||||
<div id="loginTypeFormModal" style="display:none;padding:16px">
|
||||
<!-- 参考demo表单2样式,添加layui-form-pane类实现方框风格 -->
|
||||
<form class="layui-form layui-form-pane" id="loginTypeForm" lay-filter="loginTypeForm">
|
||||
<input type="hidden" name="id" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">名称</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" required lay-verify="required" placeholder="请输入登录方式名称" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="status">
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">验证类型</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="verify_types" placeholder="请输入验证类型,多个用逗号分隔" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 操作按钮移除:统一由 layer.open 的 btn 控制“提交/取消” -->
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// 登录类型管理页面的JavaScript脚本
|
||||
layui.use(['table', 'form', 'layer'], function(){
|
||||
const table = layui.table;
|
||||
const form = layui.form;
|
||||
const layer = layui.layer;
|
||||
const $ = layui.$;
|
||||
|
||||
// 表格实例
|
||||
let tableIns;
|
||||
|
||||
// 初始化表格
|
||||
const initTable = () => {
|
||||
tableIns = table.render({
|
||||
elem: '#loginTypesTable',
|
||||
url: '/admin/api/login_types/list',
|
||||
method: 'GET',
|
||||
page: true,
|
||||
limit: 20,
|
||||
limits: [10, 20, 50, 100],
|
||||
loading: true,
|
||||
cols: [[
|
||||
{type: 'checkbox'},
|
||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
||||
{field: 'name', title: '名称'},
|
||||
{field: 'status', title: '状态', width: 100, templet: function(d){
|
||||
return d.status === 1 ? '<span class="layui-badge layui-bg-green">启用</span>' : '<span class="layui-badge">禁用</span>';
|
||||
}},
|
||||
{field: 'verify_types', title: '验证类型'},
|
||||
{field: 'created_at', title: '创建时间', width: 180, templet: function(d){
|
||||
return formatDateTime(d.created_at);
|
||||
}},
|
||||
{field: 'updated_at', title: '更新时间', width: 180, templet: function(d){
|
||||
return formatDateTime(d.updated_at);
|
||||
}},
|
||||
{title: '操作', toolbar: '#tpl-logintypes-ops', width: 150, fixed: 'right'}
|
||||
]],
|
||||
parseData: function(res){
|
||||
// 后端已返回正确格式,直接使用
|
||||
return {
|
||||
"code": res.code,
|
||||
"msg": res.msg || '',
|
||||
"count": res.data ? res.data.total : 0,
|
||||
"data": res.data ? res.data.items : []
|
||||
};
|
||||
},
|
||||
request: {
|
||||
pageName: 'page',
|
||||
limitName: 'page_size'
|
||||
},
|
||||
where: {}
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0') + ' ' +
|
||||
String(date.getHours()).padStart(2, '0') + ':' +
|
||||
String(date.getMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getSeconds()).padStart(2, '0');
|
||||
};
|
||||
|
||||
// 重载表格数据
|
||||
const reloadTable = (where = {}) => {
|
||||
tableIns.reload({
|
||||
where: where,
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取选中的行数据
|
||||
const getCheckData = () => {
|
||||
const checkStatus = table.checkStatus('loginTypesTable');
|
||||
return checkStatus.data;
|
||||
};
|
||||
|
||||
// 当前表单弹窗索引
|
||||
let currentFormLayerIndex = null;
|
||||
|
||||
// 显示表单弹层(统一使用 layer.open 的按钮作为确认/取消)
|
||||
const showFormModal = (title, data = {}) => {
|
||||
// 重置表单
|
||||
$('#loginTypeForm')[0].reset();
|
||||
|
||||
// 填充表单数据
|
||||
if (data.id) {
|
||||
$('input[name="id"]').val(data.id);
|
||||
$('input[name="name"]').val(data.name);
|
||||
$('select[name="status"]').val(data.status);
|
||||
$('input[name="verify_types"]').val(data.verify_types);
|
||||
}
|
||||
|
||||
// 刷新表单渲染
|
||||
form.render();
|
||||
|
||||
// 显示弹层并保存索引
|
||||
currentFormLayerIndex = layer.open({
|
||||
type: 1,
|
||||
title: title,
|
||||
content: $('#loginTypeFormModal'),
|
||||
area: ['500px', '300px'],
|
||||
btn: ['提交', '取消'],
|
||||
btnAlign: 'c',
|
||||
yes: () => {
|
||||
// 点击“提交”时执行提交
|
||||
doLoginTypeSubmit();
|
||||
},
|
||||
btn2: (index) => {
|
||||
// 点击“取消”时关闭弹层
|
||||
layer.close(index);
|
||||
},
|
||||
closeBtn: 1
|
||||
});
|
||||
};
|
||||
|
||||
// 提交表单(通过 layer.open 的“提交”按钮触发)
|
||||
const doLoginTypeSubmit = () => {
|
||||
// 读取表单数据并校验
|
||||
const idVal = $('input[name="id"]').val();
|
||||
const isEdit = idVal && idVal !== '';
|
||||
const name = $('input[name="name"]').val().trim();
|
||||
const status = parseInt($('select[name="status"]').val() || '0');
|
||||
const verifyTypes = $('input[name="verify_types"]').val().trim();
|
||||
|
||||
if (!name) {
|
||||
layer.msg('请输入登录方式名称', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
const url = isEdit ? '/admin/api/login_types/update' : '/admin/api/login_types/create';
|
||||
const requestData = {
|
||||
name: name,
|
||||
status: status,
|
||||
verify_types: verifyTypes
|
||||
};
|
||||
if (isEdit) requestData.id = parseInt(idVal);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(requestData),
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(isEdit ? '更新成功' : '创建成功', { icon: 1, time: 1000 });
|
||||
layer.close(currentFormLayerIndex);
|
||||
reloadTable();
|
||||
} else {
|
||||
// 失败时对提示信息做截断,避免过长
|
||||
const raw = res.msg || '操作失败';
|
||||
const shortMsg = raw.length > 100 ? (raw.slice(0, 100) + '...') : raw;
|
||||
layer.msg(shortMsg, { icon: 2 });
|
||||
}
|
||||
},
|
||||
// 优先展示后端返回的业务错误信息,避免统一显示“网络错误”
|
||||
error: (xhr) => {
|
||||
// 失败时对提示信息做截断,避免过长
|
||||
const raw = (xhr.responseJSON && xhr.responseJSON.msg) ? xhr.responseJSON.msg : '网络错误,请重试';
|
||||
const shortMsg = raw.length > 100 ? (raw.slice(0, 100) + '...') : raw;
|
||||
layer.msg(shortMsg, { icon: 2 });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除单个记录
|
||||
// 说明:删除前二次确认;后端返回400/500也能显示具体错误信息
|
||||
const deleteItem = (id) => {
|
||||
layer.confirm('确定要删除这条记录吗?', {icon: 3, title: '提示'}, function(index){
|
||||
$.ajax({
|
||||
url: '/admin/api/login_types/delete',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({id: id}),
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg('删除成功', { icon: 1, time: 3000 });
|
||||
reloadTable();
|
||||
} else {
|
||||
// 删除失败:使用折行展示错误信息,便于阅读(不再截断)
|
||||
const raw = res.msg || '删除失败';
|
||||
const safe = String(raw)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
// 将常见分隔符替换为换行,结合 white-space: pre-wrap 生效
|
||||
const content = `<div style="white-space:pre-wrap;word-break:break-word;">${safe.replace(/[,,;;]/g, '\n')}</div>`;
|
||||
layer.msg(content, { icon: 2 });
|
||||
}
|
||||
},
|
||||
// 解析后端JSON错误响应,展示msg内容(支持折行)
|
||||
error: (xhr) => {
|
||||
const raw = (xhr.responseJSON && xhr.responseJSON.msg) ? xhr.responseJSON.msg : '网络错误,请重试';
|
||||
const safe = String(raw)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
const content = `<div style="white-space:pre-wrap;word-break:break-word;">${safe.replace(/[,,;;]/g, '\n')}</div>`;
|
||||
layer.msg(content, { icon: 2 });
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
};
|
||||
|
||||
// 批量操作
|
||||
// 参数:operation 用于提示文案,url 为接口地址,confirmMsg 为确认提示语
|
||||
const batchOperation = (operation, url, confirmMsg) => {
|
||||
const checkData = getCheckData();
|
||||
if (checkData.length === 0) {
|
||||
layer.msg('请选择要操作的数据', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = checkData.map(item => item.id);
|
||||
|
||||
layer.confirm(confirmMsg, {icon: 3, title: '提示'}, function(index){
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ids: ids}),
|
||||
// 统一成功/失败提示,移除残留的diff标记
|
||||
success: function(res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(operation + '成功', { icon: 1 });
|
||||
reloadTable();
|
||||
} else {
|
||||
// 批量失败:使用折行展示长信息(例如占用明细),便于阅读
|
||||
const raw = res.msg || operation + '失败';
|
||||
const safe = String(raw)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
const content = `<div style="white-space:pre-wrap;word-break:break-word;">${safe.replace(/[,,;;]/g, '\n')}</div>`;
|
||||
layer.msg(content, { icon: 2 });
|
||||
}
|
||||
},
|
||||
// 出错时同样尝试展示后端返回的msg(支持折行)
|
||||
error: (xhr) => {
|
||||
const raw = (xhr.responseJSON && xhr.responseJSON.msg) ? xhr.responseJSON.msg : '网络错误,请重试';
|
||||
const safe = String(raw)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
const content = `<div style="white-space:pre-wrap;word-break:break-word;">${safe.replace(/[,,;;]/g, '\n')}</div>`;
|
||||
layer.msg(content, { icon: 2 });
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
});
|
||||
};
|
||||
|
||||
// 事件绑定
|
||||
|
||||
// 新增按钮
|
||||
$('#btnAddLoginType').on('click', function(){
|
||||
showFormModal('新增登录方式');
|
||||
});
|
||||
|
||||
// 批量启用按钮
|
||||
$('#btnBatchEnableLoginTypes').on('click', function(){
|
||||
batchOperation('批量启用', '/admin/api/login_types/batch_enable', '确定要启用选中的登录方式吗?');
|
||||
});
|
||||
|
||||
// 批量禁用按钮
|
||||
$('#btnBatchDisableLoginTypes').on('click', function(){
|
||||
batchOperation('批量禁用', '/admin/api/login_types/batch_disable', '确定要禁用选中的登录方式吗?');
|
||||
});
|
||||
|
||||
// 批量删除按钮
|
||||
$('#btnBatchDeleteLoginTypes').on('click', function(){
|
||||
batchOperation('批量删除', '/admin/api/login_types/batch_delete', '确定要删除选中的登录方式吗?删除后不可恢复!');
|
||||
});
|
||||
|
||||
// 查询按钮
|
||||
$('#btnSearchLoginTypes').on('click', function(){
|
||||
const formData = form.val('loginTypeFilterForm');
|
||||
const where = {};
|
||||
|
||||
if (formData.keyword && formData.keyword.trim() !== '') {
|
||||
where.keyword = formData.keyword.trim();
|
||||
}
|
||||
if (formData.status && formData.status !== '') {
|
||||
where.status = formData.status;
|
||||
}
|
||||
|
||||
reloadTable(where);
|
||||
});
|
||||
|
||||
// 重置按钮
|
||||
$('#btnResetLoginTypes').on('click', function(){
|
||||
$('#loginTypeFilterForm')[0].reset();
|
||||
form.render();
|
||||
reloadTable();
|
||||
});
|
||||
|
||||
// Layui表单提交事件
|
||||
// 删除 Layui 表单提交监听(由 layer.open 的“提交”按钮统一触发)
|
||||
// form.on('submit(loginTypeSubmit)', function(data){
|
||||
// submitForm();
|
||||
// return false; // 阻止表单跳转
|
||||
// });
|
||||
|
||||
// 删除表单取消按钮事件(由 layer.open 的“取消”按钮统一处理)
|
||||
// $('#btnCancelLoginType').on('click', function(){
|
||||
// layer.close(currentFormLayerIndex);
|
||||
// });
|
||||
|
||||
// 表格工具栏事件
|
||||
table.on('tool(loginTypesTableFilter)', function(obj){
|
||||
const data = obj.data;
|
||||
const layEvent = obj.event;
|
||||
|
||||
if (layEvent === 'edit') {
|
||||
showFormModal('编辑登录方式', data);
|
||||
} else if (layEvent === 'del') {
|
||||
deleteItem(data.id);
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化页面
|
||||
initTable();
|
||||
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
277
web/template/admin/settings.html
Normal file
277
web/template/admin/settings.html
Normal file
@@ -0,0 +1,277 @@
|
||||
{{ define "settings.html" }}
|
||||
<section>
|
||||
<h2>系统设置</h2>
|
||||
|
||||
<!-- 基本信息设置 -->
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">基本信息设置</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="basicForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">站点标题</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="site_title" lay-verify="required" placeholder="请输入站点标题" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">关键词</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="site_keywords" placeholder="请输入站点关键词,多个关键词用逗号分隔" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<label class="layui-form-label">站点描述</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="site_description" placeholder="请输入站点描述" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">站点Logo</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="site_logo" placeholder="/assets/logo.svg" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统配置设置 -->
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">系统配置</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="systemForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">关闭系统</label>
|
||||
<div class="layui-input-block">
|
||||
<div style="display: flex; align-items: center; justify-content: flex-start; gap: 10px;">
|
||||
<input type="checkbox" name="maintenance_mode" lay-skin="switch" lay-text="关闭系统|开启系统" title="关闭系统|开启系统">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">默认角色</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="default_user_role">
|
||||
<option value="0">管理员</option>
|
||||
<option value="1">普通用户</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">会话超时</label>
|
||||
<div class="layui-input-block">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<input type="number" name="session_timeout" placeholder="3600" min="300" max="86400" class="layui-input" style="width: 120px;" />
|
||||
<span class="layui-form-mid">秒(300-86400秒)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页脚与备案信息 -->
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">页脚与备案</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="footerForm">
|
||||
<div class="layui-form-item layui-form-text">
|
||||
<label class="layui-form-label">页脚文本</label>
|
||||
<div class="layui-input-block">
|
||||
<textarea name="footer_text" placeholder="© 2025 凌动技术 保留所有权利" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">ICP备案</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="icp_record" placeholder="京ICP备12345678号" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">备案链接</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="url" name="icp_record_link" placeholder="https://beian.miit.gov.cn" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">公安备案</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="psb_record" placeholder="京公网安备11010802012345号" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">备案链接</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="url" name="psb_record_link" placeholder="http://www.beian.gov.cn/portal/registerSystemInfo" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="layui-form-item" style="margin-top: 24px;">
|
||||
<div class="layui-input-block">
|
||||
<button type="button" class="layui-btn" id="saveAllBtn" lay-submit lay-filter="saveAll">保存所有设置</button>
|
||||
<button type="button" class="layui-btn layui-btn-primary" id="resetBtn">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
layui.use(['jquery', 'form', 'layer'], function() {
|
||||
const { $, form, layer } = layui;
|
||||
|
||||
// 缓存上次加载的设置值,用于“重置”恢复
|
||||
let originalSettings = {};
|
||||
|
||||
/**
|
||||
* 加载后台所有设置并回填到三个表单
|
||||
* - 从 /admin/api/settings 获取 name:value 映射
|
||||
* - 处理开关型字段(maintenance_mode)
|
||||
* - 渲染 layui 组件
|
||||
*/
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const res = await fetch('/admin/api/settings', {
|
||||
method: 'GET',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.code !== 0) {
|
||||
layer.msg(data.msg || '加载设置失败', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
originalSettings = data.data || {};
|
||||
fillForms(originalSettings);
|
||||
} catch (err) {
|
||||
console.error('获取设置失败:', err);
|
||||
layer.msg('网络错误,无法加载设置', { icon: 2 });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将 settings 数据回填到各表单控件
|
||||
* - 文本/文本域/下拉:直接赋值
|
||||
* - 开关:根据 "1"/"0" 置为选中/未选中
|
||||
*/
|
||||
const fillForms = (settings = {}) => {
|
||||
// 基本信息
|
||||
$('[name="site_title"]').val(settings.site_title || '');
|
||||
$('[name="site_keywords"]').val(settings.site_keywords || '');
|
||||
$('[name="site_description"]').val(settings.site_description || '');
|
||||
$('[name="site_logo"]').val(settings.site_logo || '');
|
||||
|
||||
// 系统配置
|
||||
const maintenanceChecked = (settings.maintenance_mode || '0') === '1';
|
||||
$('[name="maintenance_mode"]').prop('checked', maintenanceChecked);
|
||||
$('[name="default_user_role"]').val(settings.default_user_role || '1');
|
||||
$('[name="session_timeout"]').val(settings.session_timeout || '3600');
|
||||
|
||||
// 页脚与备案
|
||||
$('[name="footer_text"]').val(settings.footer_text || '');
|
||||
$('[name="icp_record"]').val(settings.icp_record || '');
|
||||
$('[name="icp_record_link"]').val(settings.icp_record_link || '');
|
||||
$('[name="psb_record"]').val(settings.psb_record || '');
|
||||
$('[name="psb_record_link"]').val(settings.psb_record_link || '');
|
||||
|
||||
// 渲染 layui 组件
|
||||
form.render();
|
||||
};
|
||||
|
||||
/**
|
||||
* 收集某个表单下所有可用控件的值
|
||||
* - 统一将 checkbox 转为 "1"/"0"
|
||||
* - 其他控件转为字符串,避免后端类型不一致
|
||||
*/
|
||||
const collectForm = (selector) => {
|
||||
const obj = {};
|
||||
const $form = $(selector);
|
||||
$form.find('input, textarea, select').each(function() {
|
||||
const $el = $(this);
|
||||
const name = $el.attr('name');
|
||||
if (!name) return; // 无 name 不纳入
|
||||
const type = ($el.attr('type') || '').toLowerCase();
|
||||
let value = '';
|
||||
if (type === 'checkbox') {
|
||||
value = $el.prop('checked') ? '1' : '0';
|
||||
} else {
|
||||
value = ($el.val() ?? '').toString();
|
||||
}
|
||||
obj[name] = value;
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* 汇总三个表单的字段为一个扁平对象
|
||||
*/
|
||||
const collectAllSettings = () => {
|
||||
return {
|
||||
...collectForm('#basicForm'),
|
||||
...collectForm('#systemForm'),
|
||||
...collectForm('#footerForm'),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理“保存所有设置”点击
|
||||
* - 二次确认后提交
|
||||
* - 显示加载中,防重复提交
|
||||
* - 成功后提示并刷新缓存的 originalSettings
|
||||
*/
|
||||
const handleSaveAll = () => {
|
||||
const payload = collectAllSettings();
|
||||
layer.confirm('确认保存所有设置?', { icon: 3, title: '提示' }, (idx) => {
|
||||
layer.close(idx);
|
||||
const btn = $('#saveAllBtn');
|
||||
btn.prop('disabled', true).addClass('layui-btn-disabled');
|
||||
const loadIdx = layer.load(2, { content: '正在保存...' });
|
||||
|
||||
fetch('/admin/api/settings/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(resp => resp.json())
|
||||
.then(res => {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg || '保存成功', { icon: 1, time: 1000 });
|
||||
originalSettings = { ...payload };
|
||||
} else {
|
||||
layer.msg(res.msg || '保存失败', { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('保存设置失败:', err);
|
||||
layer.msg('网络错误,保存失败', { icon: 2 });
|
||||
})
|
||||
.finally(() => {
|
||||
layer.close(loadIdx);
|
||||
btn.prop('disabled', false).removeClass('layui-btn-disabled');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理“重置”点击
|
||||
* - 恢复为上次加载的 originalSettings
|
||||
*/
|
||||
const handleReset = () => {
|
||||
fillForms(originalSettings);
|
||||
layer.msg('已恢复到上次加载的值', { icon: 1, time: 800 });
|
||||
};
|
||||
|
||||
// 事件绑定
|
||||
$('#saveAllBtn').off('click').on('click', handleSaveAll);
|
||||
$('#resetBtn').off('click').on('click', handleReset);
|
||||
|
||||
// 初始化:加载设置
|
||||
loadSettings();
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
256
web/template/admin/user.html
Normal file
256
web/template/admin/user.html
Normal file
@@ -0,0 +1,256 @@
|
||||
{{ define "user.html" }}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">个人资料</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="accountForm" lay-filter="accountForm" onsubmit="return false">
|
||||
<!-- 按照要求纵向排序:ID、角色、用户名、旧密码、新密码、确认新密码 -->
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">ID</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="id" disabled readonly class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">角色</label>
|
||||
<div class="layui-input-block">
|
||||
<!-- 角色禁用与只读,仅作展示用途,显示中文标签“管理员/普通成员” -->
|
||||
<input type="text" name="role" disabled readonly class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="username" placeholder="请输入用户名(不修改可留空或保持不变)" autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">旧密码</label>
|
||||
<div class="layui-input-block">
|
||||
<!-- 不修改密码时可留空 -->
|
||||
<input type="password" name="old_password" placeholder="不修改可留空" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">新密码</label>
|
||||
<div class="layui-input-block">
|
||||
<!-- 不修改密码时可留空 -->
|
||||
<input type="password" name="new_password" placeholder="不修改可留空(至少6位)" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">确认密码</label>
|
||||
<div class="layui-input-block">
|
||||
<!-- 不修改密码时可留空 -->
|
||||
<input type="password" name="confirm_password" placeholder="不修改可留空" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="submitAccount">保存更改</button>
|
||||
<!-- 将原先 type="reset" 改为自定义按钮,避免浏览器重置成初始空值 -->
|
||||
<button type="button" id="btnReset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||
(() => {
|
||||
// 工具方法:将数值角色转为中文标签
|
||||
// 0 => 管理员,1 => 普通成员
|
||||
const roleToText = (role) => {
|
||||
// 将可能的字符串数值转为数字
|
||||
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
||||
return r === 0 ? '管理员' : '普通成员'
|
||||
}
|
||||
|
||||
// 如果未加载 layui,则按需加载(兼容用户直接访问片段页 /admin/user)
|
||||
// 说明:当 window.layui 不存在时,动态引入 Layui 的 CSS 和 JS,加载完成后再执行页面逻辑
|
||||
const ensureLayui = () => new Promise((resolve) => {
|
||||
if (window.layui) return resolve(window.layui)
|
||||
const css = document.createElement('link')
|
||||
css.rel = 'stylesheet'
|
||||
css.href = 'https://unpkg.com/layui@2.10.1/dist/css/layui.css'
|
||||
document.head.appendChild(css)
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://unpkg.com/layui@2.10.1/dist/layui.js'
|
||||
script.onload = () => resolve(window.layui)
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
// 在确保 Layui 可用后再执行页面逻辑
|
||||
ensureLayui().then(() => {
|
||||
layui.use(['form', 'layer'], () => {
|
||||
const form = layui.form
|
||||
const layer = layui.layer
|
||||
|
||||
// 记录初始用户名,用于判断是否需要更新
|
||||
let initialUsername = ''
|
||||
// 缓存最近一次加载到表单中的资料,用于“重置”恢复
|
||||
let lastProfile = null
|
||||
|
||||
// 加载个人资料:填充ID/用户名/角色(角色显示中文标签并禁用)
|
||||
// 返回:无;副作用:设置 initialUsername、lastProfile 与表单值
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/profile')
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '加载失败')
|
||||
const payload = data.data || {}
|
||||
initialUsername = payload.username || ''
|
||||
// 将角色转换为中文展示,并缓存为最近一次加载的“默认值”
|
||||
const display = { ...payload, role: roleToText(payload.role) }
|
||||
lastProfile = display
|
||||
form.val('accountForm', display)
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '加载个人资料失败', { icon: 2 })
|
||||
}
|
||||
}
|
||||
|
||||
// 校验密码表单:当任一密码字段填写时,要求三个字段均填写且有效
|
||||
// 返回:{ ok: boolean, msg?: string }
|
||||
const validatePassword = (fields) => {
|
||||
const oldPwd = (fields.old_password || '').trim()
|
||||
const newPwd = (fields.new_password || '').trim()
|
||||
const confirmPwd = (fields.confirm_password || '').trim()
|
||||
const anyFilled = !!(oldPwd || newPwd || confirmPwd)
|
||||
if (!anyFilled) return { ok: true }
|
||||
if (!oldPwd || !newPwd || !confirmPwd) return { ok: false, msg: '请完整填写旧密码/新密码/确认新密码' }
|
||||
if (newPwd.length < 6) return { ok: false, msg: '新密码长度不能少于6位' }
|
||||
if (newPwd !== confirmPwd) return { ok: false, msg: '两次输入的新密码不一致' }
|
||||
if (oldPwd === newPwd) return { ok: false, msg: '新密码不能与旧密码相同' }
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
// 更新用户名:传输 username 与 old_password(当仅修改用户名时必须提供当前密码;同时修改密码时沿用同一 old_password)
|
||||
// 返回:Promise<void>
|
||||
const updateUsername = async (username, oldPassword) => {
|
||||
const payload = { username }
|
||||
if (oldPassword) payload.old_password = oldPassword
|
||||
const res = await fetch('/admin/api/user/profile/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '保存资料失败')
|
||||
}
|
||||
|
||||
// 更新密码:仅传输旧/新/确认三个字段
|
||||
// 返回:Promise<any> 后端响应数据,用于可能的重定向处理
|
||||
const updatePassword = async (fields) => {
|
||||
const payload = {
|
||||
old_password: fields.old_password,
|
||||
new_password: fields.new_password,
|
||||
confirm_password: fields.confirm_password
|
||||
}
|
||||
const res = await fetch('/admin/api/user/password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改密码失败')
|
||||
return data
|
||||
}
|
||||
|
||||
// 提交综合更新:
|
||||
// 规则:
|
||||
// - 用户名:仅当与 initialUsername 不同且非空时更新
|
||||
// - 密码:当任一密码字段填写时,要求完整校验并更新;若均未填则不更新
|
||||
// - 若两者均无改动,则提示“未修改任何内容”
|
||||
form.on('submit(submitAccount)', async (obj) => {
|
||||
const fields = obj.field
|
||||
const desiredUsername = (fields.username || '').trim()
|
||||
const needUpdateUsername = desiredUsername && desiredUsername !== initialUsername
|
||||
|
||||
// 判定密码相关输入:
|
||||
// - wantChangePassword:输入了新密码或确认密码,视为尝试修改密码(将要求三个字段都填写)
|
||||
// - onlyOldProvided:仅输入了旧密码,用于支持“仅修改用户名需要当前密码”的场景
|
||||
const hasOld = !!(fields.old_password && fields.old_password.trim())
|
||||
const hasNewOrConfirm = !!((fields.new_password && fields.new_password.trim()) || (fields.confirm_password && fields.confirm_password.trim()))
|
||||
const wantChangePassword = hasNewOrConfirm
|
||||
const onlyOldProvided = hasOld && !hasNewOrConfirm
|
||||
|
||||
if (!needUpdateUsername && !wantChangePassword) {
|
||||
layer.msg('未修改任何内容', { icon: 0 })
|
||||
return false
|
||||
}
|
||||
|
||||
// 修改密码场景:需进行严格校验(旧/新/确认均必填)
|
||||
if (wantChangePassword) {
|
||||
const pwdCheck = validatePassword(fields)
|
||||
if (!pwdCheck.ok) {
|
||||
layer.msg(pwdCheck.msg, { icon: 2 })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 仅修改用户名:要求输入当前密码
|
||||
if (needUpdateUsername && !wantChangePassword && !hasOld) {
|
||||
layer.msg('修改用户名需要输入当前密码', { icon: 2 })
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 始终先更新用户名,再更新密码(避免改密后跳转导致无法继续)
|
||||
if (needUpdateUsername) {
|
||||
await updateUsername(desiredUsername, hasOld ? fields.old_password : '')
|
||||
initialUsername = desiredUsername
|
||||
}
|
||||
|
||||
if (wantChangePassword) {
|
||||
const pwdResp = await updatePassword(fields)
|
||||
// 修改密码后通常需要重新登录,优先使用后端返回的 redirect,否则默认登录页
|
||||
const redirect = pwdResp && pwdResp.data && pwdResp.data.redirect ? pwdResp.data.redirect : '/admin/login'
|
||||
layer.msg('密码修改成功,即将跳转到登录页', { icon: 1, time: 1200 }, () => {
|
||||
window.location.href = redirect
|
||||
})
|
||||
} else {
|
||||
// 未修改密码,仅修改资料
|
||||
await loadProfile()
|
||||
layer.msg('保存成功', { icon: 1 })
|
||||
}
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '保存失败', { icon: 2 })
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// 绑定“重置”按钮:将表单恢复为最近一次加载到表单中的资料
|
||||
// 逻辑:
|
||||
// - 如有 lastProfile,直接回填;
|
||||
// - 回填时同时清空三个密码字段;
|
||||
// - 如暂无缓存(极小概率),则重新请求资料
|
||||
const bindReset = () => {
|
||||
const btn = document.getElementById('btnReset')
|
||||
if (!btn) return
|
||||
btn.addEventListener('click', () => {
|
||||
if (lastProfile) {
|
||||
form.val('accountForm', { ...lastProfile, old_password: '', new_password: '', confirm_password: '' })
|
||||
layer.msg('已恢复为当前资料', { icon: 1 })
|
||||
} else {
|
||||
loadProfile()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
bindReset()
|
||||
loadProfile()
|
||||
})
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
{{ end }}
|
||||
366
web/template/index.html
Normal file
366
web/template/index.html
Normal file
@@ -0,0 +1,366 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<title>{{.SystemName}} - 生活就像愤怒的小鸟,失败后总有几只猪在笑。</title>
|
||||
<!-- 站 点 协 议 -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta http-equiv="content-language" content="zh-cn">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<!-- 站 点 图 标 -->
|
||||
<link href='/favicon.ico' rel='icon' type='image/x-icon'>
|
||||
<link href="/favicon.ico" rel="shortcut icon">
|
||||
<link href="/favicon.ico" rel="bookmark">
|
||||
<!-- 样 式 文 件 -->
|
||||
<link rel="stylesheet" href="//lib.baomitu.com/layui/2.8.17/css/layui.css"/>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #000000 !important;
|
||||
}
|
||||
|
||||
.layui-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.body-background {
|
||||
width: 420px;
|
||||
min-height: 350px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.logo-title {
|
||||
text-align: center;
|
||||
letter-spacing: 3px;
|
||||
padding: 0 0 0 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.logo-title h1 {
|
||||
color: #2550dd;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
||||
animation: glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from {
|
||||
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
||||
}
|
||||
to {
|
||||
text-shadow: 0 0 30px rgba(0, 212, 255, 0.8), 0 0 40px rgba(0, 212, 255, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.box-form {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(240, 248, 255, 0.9));
|
||||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||||
border-radius: 15px;
|
||||
padding: 30px 25px;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.box-form::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.1), transparent);
|
||||
animation: shimmer 3s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
.box-form .layui-form-item {
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 24px;
|
||||
color: #ff4757;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 2px 4px rgba(255, 71, 87, 0.3);
|
||||
margin: 15px 0;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.info-text {
|
||||
color: #3742fa;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin: 15px 0;
|
||||
text-shadow: 0 1px 2px rgba(55, 66, 250, 0.2);
|
||||
}
|
||||
|
||||
.body_box {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body_footer {
|
||||
padding-top: 15px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 14px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.body_beian {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.body_beian a {
|
||||
color: rgba(0, 212, 255, 0.8);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.body_beian a:hover {
|
||||
color: #00d4ff;
|
||||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
|
||||
}
|
||||
|
||||
#canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #00d4ff, transparent);
|
||||
margin: 20px 0;
|
||||
border-radius: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 代 码 结 构 -->
|
||||
<div class="layui-container">
|
||||
<canvas id="canvas"></canvas>
|
||||
<div class="body-background body_box">
|
||||
<div class="layui-form box-form body_box">
|
||||
<div class="layui-form-item logo-title">
|
||||
<h1><strong>系统提醒</strong></h1>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="layui-form-item">
|
||||
<div class="warning-text">🚫 未授权,拒绝访问</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="info-text">💬 如有问题,请联系网站管理员</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body_footer">{{.FooterText}}</div>
|
||||
{{if or .ICPRecord .PSBRecord}}<div class="body_beian">{{if .ICPRecord}}<a href="{{.ICPRecordLink}}" target="_blank">{{.ICPRecord}}</a>{{end}}{{if and .ICPRecord .PSBRecord}} {{end}}{{if .PSBRecord}}<a href="{{.PSBRecordLink}}" target="_blank">{{.PSBRecord}}</a>{{end}}</div>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 资 源 引 入 -->
|
||||
<script src="//lib.baomitu.com/jquery/3.6.4/jquery.min.js" type="text/javascript"></script>
|
||||
|
||||
<script>
|
||||
// 获取canvas元素和绘图上下文
|
||||
const canvas = document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 设置canvas尺寸为全屏
|
||||
const resizeCanvas = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 粒子类
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// 重置粒子位置和属性
|
||||
reset() {
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = Math.random() * canvas.height;
|
||||
this.vx = (Math.random() - 0.5) * 2;
|
||||
this.vy = (Math.random() - 0.5) * 2;
|
||||
this.size = Math.random() * 3 + 1;
|
||||
this.opacity = Math.random() * 0.8 + 0.2;
|
||||
this.color = this.getRandomColor();
|
||||
}
|
||||
|
||||
// 获取随机颜色
|
||||
getRandomColor() {
|
||||
const colors = [
|
||||
'#00FF00', '#0080FF', '#FF0080', '#FFFF00',
|
||||
'#FF8000', '#8000FF', '#00FFFF', '#FF4000'
|
||||
];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
// 更新粒子位置
|
||||
update() {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// 边界检测,粒子超出边界时重置
|
||||
if (this.x < 0 || this.x > canvas.width ||
|
||||
this.y < 0 || this.y > canvas.height) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// 随机改变透明度
|
||||
this.opacity += (Math.random() - 0.5) * 0.02;
|
||||
this.opacity = Math.max(0.1, Math.min(1, this.opacity));
|
||||
}
|
||||
|
||||
// 绘制粒子
|
||||
draw() {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建粒子数组
|
||||
const particles = [];
|
||||
const particleCount = 150;
|
||||
|
||||
// 初始化粒子
|
||||
const initParticles = () => {
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
particles.push(new Particle());
|
||||
}
|
||||
};
|
||||
|
||||
// 绘制连线
|
||||
const drawConnections = () => {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 如果距离小于100像素,绘制连线
|
||||
if (distance < 100) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = (100 - distance) / 100 * 0.3;
|
||||
ctx.strokeStyle = '#00FF00';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 动画循环
|
||||
const animate = () => {
|
||||
// 清除画布,使用半透明黑色创建拖尾效果
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 更新和绘制所有粒子
|
||||
particles.forEach(particle => {
|
||||
particle.update();
|
||||
particle.draw();
|
||||
});
|
||||
|
||||
// 绘制粒子间的连线
|
||||
drawConnections();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// 鼠标交互效果
|
||||
const addMouseInteraction = () => {
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
mouseX = e.clientX;
|
||||
mouseY = e.clientY;
|
||||
|
||||
// 鼠标附近的粒子会被吸引
|
||||
particles.forEach(particle => {
|
||||
const dx = mouseX - particle.x;
|
||||
const dy = mouseY - particle.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 150) {
|
||||
particle.vx += dx * 0.0001;
|
||||
particle.vy += dy * 0.0001;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 点击时添加新粒子
|
||||
canvas.addEventListener('click', (e) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const newParticle = new Particle();
|
||||
newParticle.x = e.clientX + (Math.random() - 0.5) * 20;
|
||||
newParticle.y = e.clientY + (Math.random() - 0.5) * 20;
|
||||
particles.push(newParticle);
|
||||
}
|
||||
|
||||
// 限制粒子数量
|
||||
if (particles.length > particleCount + 50) {
|
||||
particles.splice(0, 5);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 启动粒子系统
|
||||
initParticles();
|
||||
addMouseInteraction();
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user