mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7fd8c0c822 | ||
![]() |
a9e9c81505 | ||
![]() |
e8cc68bdea | ||
![]() |
9e51a661a4 | ||
![]() |
a167aaf55f | ||
![]() |
a54ecbcaa0 | ||
![]() |
788462cdfa | ||
![]() |
45c5965b99 | ||
![]() |
ce7614de46 | ||
![]() |
9f78e1ce1e | ||
![]() |
2c7b0625e8 | ||
![]() |
c3a5da9be1 | ||
![]() |
ca796e1920 | ||
![]() |
7ce04cf781 | ||
![]() |
024a3eb760 | ||
![]() |
1702f429b4 | ||
![]() |
96d79cf495 | ||
![]() |
a6a11a7026 | ||
![]() |
970a49e2a5 | ||
![]() |
2e013ed4f5 | ||
![]() |
f8c396b1fe | ||
![]() |
b54870cb60 | ||
![]() |
84318acb18 | ||
![]() |
a11a042b93 | ||
![]() |
8a8aa8f62c | ||
![]() |
93f78f4db5 | ||
![]() |
404bfdd5e6 | ||
![]() |
e4577dc2f1 |
@@ -64,4 +64,4 @@ NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进
|
|||||||
|
|
||||||
## 开源附加
|
## 开源附加
|
||||||
|
|
||||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**本仓库仅用于提高IM易用性,实现类似Hook推送,此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
|
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.5.16",
|
"version": "4.5.18",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"@heroui/accordion": "^2.2.8",
|
||||||
"@heroui/avatar": "2.2.7",
|
"@heroui/avatar": "2.2.7",
|
||||||
"@heroui/breadcrumbs": "2.2.7",
|
"@heroui/breadcrumbs": "2.2.7",
|
||||||
"@heroui/button": "2.2.10",
|
"@heroui/button": "2.2.10",
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-dropzone": "^14.3.5",
|
"react-dropzone": "^14.3.5",
|
||||||
"react-error-boundary": "^5.0.0",
|
"react-error-boundary": "^5.0.0",
|
||||||
|
36
napcat.webui/src/components/ColorPicker.tsx
Normal file
36
napcat.webui/src/components/ColorPicker.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@heroui/popover'
|
||||||
|
import React from 'react'
|
||||||
|
import { ColorResult, SketchPicker } from 'react-color'
|
||||||
|
|
||||||
|
// 假定 heroui 提供的 Popover组件
|
||||||
|
|
||||||
|
interface ColorPickerProps {
|
||||||
|
color: string
|
||||||
|
onChange: (color: ColorResult) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColorPicker: React.FC<ColorPickerProps> = ({ color, onChange }) => {
|
||||||
|
const handleChange = (colorResult: ColorResult) => {
|
||||||
|
onChange(colorResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover triggerScaleOnOpen={false}>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<div
|
||||||
|
className="w-36 h-8 rounded-md cursor-pointer border border-content4"
|
||||||
|
style={{ background: color }}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<SketchPicker
|
||||||
|
color={color}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="!bg-transparent !shadow-none"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ColorPicker
|
@@ -1,4 +1,5 @@
|
|||||||
import { Button } from '@heroui/button'
|
import { Button } from '@heroui/button'
|
||||||
|
import clsx from 'clsx'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
|
|
||||||
@@ -7,15 +8,22 @@ export interface SaveButtonsProps {
|
|||||||
reset: () => void
|
reset: () => void
|
||||||
refresh?: () => void
|
refresh?: () => void
|
||||||
isSubmitting: boolean
|
isSubmitting: boolean
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
reset,
|
reset,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
refresh
|
refresh,
|
||||||
|
className
|
||||||
}) => (
|
}) => (
|
||||||
<div className="max-w-full mx-3 w-96 flex flex-col justify-center gap-3">
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'max-w-full mx-3 w-96 flex flex-col justify-center gap-3',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-center gap-2 mt-5">
|
<div className="flex items-center justify-center gap-2 mt-5">
|
||||||
<Button
|
<Button
|
||||||
color="default"
|
color="default"
|
||||||
|
@@ -58,14 +58,13 @@ const renderItems = (items: MenuItem[], children = false) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
endContent={
|
endContent={
|
||||||
canOpen ? (
|
canOpen ? (
|
||||||
// div实现箭头V效果
|
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'ml-auto relative w-3 h-3 transition-transform',
|
'ml-auto relative w-3 h-3 transition-transform',
|
||||||
open && 'transform rotate-180',
|
open && 'transform rotate-180',
|
||||||
isActive
|
isActive
|
||||||
? 'text-primary-500'
|
? 'text-primary-500'
|
||||||
: 'text-red-300 dark:text-white',
|
: 'text-primary-200 dark:text-white',
|
||||||
'before:rounded-full',
|
'before:rounded-full',
|
||||||
'before:content-[""]',
|
'before:content-[""]',
|
||||||
'before:block',
|
'before:block',
|
||||||
@@ -98,7 +97,7 @@ const renderItems = (items: MenuItem[], children = false) => {
|
|||||||
'w-3 h-1.5 rounded-full ml-auto shadow-lg',
|
'w-3 h-1.5 rounded-full ml-auto shadow-lg',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary-500 animate-spinner-ease-spin'
|
? 'bg-primary-500 animate-spinner-ease-spin'
|
||||||
: 'bg-red-300 dark:bg-white'
|
: 'bg-primary-200 dark:bg-white'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@@ -34,7 +34,7 @@ const SystemInfoItem: React.FC<SystemInfoItemProps> = ({
|
|||||||
endContent
|
endContent
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex text-sm gap-1 p-2 items-center shadow-sm shadow-primary-50 dark:shadow-primary-100 rounded text-primary-400">
|
<div className="flex text-sm gap-1 p-2 items-center shadow-sm shadow-primary-100 dark:shadow-primary-100 rounded text-primary-400">
|
||||||
{icon}
|
{icon}
|
||||||
<div className="w-24">{title}</div>
|
<div className="w-24">{title}</div>
|
||||||
<div className="text-primary-200">{value}</div>
|
<div className="text-primary-200">{value}</div>
|
||||||
@@ -234,7 +234,7 @@ const SystemInfo: React.FC<SystemInfoProps> = (props) => {
|
|||||||
error: qqVersionError
|
error: qqVersionError
|
||||||
} = useRequest(WebUIManager.getQQVersion)
|
} = useRequest(WebUIManager.getQQVersion)
|
||||||
return (
|
return (
|
||||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-50 dark:shadow-primary-100 overflow-visible flex-1">
|
<Card className="bg-opacity-60 shadow-sm shadow-primary-100 dark:shadow-primary-100 overflow-visible flex-1">
|
||||||
<CardHeader className="pb-0 items-center gap-1 text-primary-500 font-extrabold">
|
<CardHeader className="pb-0 items-center gap-1 text-primary-500 font-extrabold">
|
||||||
<FaCircleInfo className="text-lg" />
|
<FaCircleInfo className="text-lg" />
|
||||||
<span>系统信息</span>
|
<span>系统信息</span>
|
||||||
|
@@ -24,7 +24,7 @@ const SystemStatusItem: React.FC<SystemStatusItemProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'shadow-sm p-2 rounded-md text-sm bg-content1 bg-opacity-30',
|
'shadow-sm shadow-primary-100 p-2 rounded-md text-sm bg-content1 bg-opacity-30',
|
||||||
size === 'lg' ? 'col-span-2' : 'col-span-1 flex justify-between'
|
size === 'lg' ? 'col-span-2' : 'col-span-1 flex justify-between'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -55,7 +55,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-50 dark:shadow-primary-100 col-span-1 lg:col-span-2 relative overflow-hidden">
|
<Card className="bg-opacity-60 shadow-sm shadow-primary-100 col-span-1 lg:col-span-2 relative overflow-hidden">
|
||||||
<div className="absolute h-full right-0 top-0">
|
<div className="absolute h-full right-0 top-0">
|
||||||
<Image
|
<Image
|
||||||
src={bkg}
|
src={bkg}
|
||||||
|
6
napcat.webui/src/const/themes.ts
Normal file
6
napcat.webui/src/const/themes.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import heroui from './themes/heroui'
|
||||||
|
import nc_pink from './themes/nc_pink'
|
||||||
|
|
||||||
|
const themes: ThemeInfo[] = [nc_pink, heroui]
|
||||||
|
|
||||||
|
export default themes
|
256
napcat.webui/src/const/themes/heroui.ts
Normal file
256
napcat.webui/src/const/themes/heroui.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
const theme: ThemeConfig = {
|
||||||
|
dark: {
|
||||||
|
'--heroui-background': '0 0% 0%',
|
||||||
|
'--heroui-foreground-50': '240 5.88% 10%',
|
||||||
|
'--heroui-foreground-100': '240 3.7% 15.88%',
|
||||||
|
'--heroui-foreground-200': '240 5.26% 26.08%',
|
||||||
|
'--heroui-foreground-300': '240 5.2% 33.92%',
|
||||||
|
'--heroui-foreground-400': '240 3.83% 46.08%',
|
||||||
|
'--heroui-foreground-500': '240 5.03% 64.9%',
|
||||||
|
'--heroui-foreground-600': '240 4.88% 83.92%',
|
||||||
|
'--heroui-foreground-700': '240 5.88% 90%',
|
||||||
|
'--heroui-foreground-800': '240 4.76% 95.88%',
|
||||||
|
'--heroui-foreground-900': '0 0% 98.04%',
|
||||||
|
'--heroui-foreground': '210 5.56% 92.94%',
|
||||||
|
'--heroui-focus': '212.01999999999998 100% 46.67%',
|
||||||
|
'--heroui-overlay': '0 0% 0%',
|
||||||
|
'--heroui-divider': '0 0% 100%',
|
||||||
|
'--heroui-divider-opacity': '0.15',
|
||||||
|
'--heroui-content1': '240 5.88% 10%',
|
||||||
|
'--heroui-content1-foreground': '0 0% 98.04%',
|
||||||
|
'--heroui-content2': '240 3.7% 15.88%',
|
||||||
|
'--heroui-content2-foreground': '240 4.76% 95.88%',
|
||||||
|
'--heroui-content3': '240 5.26% 26.08%',
|
||||||
|
'--heroui-content3-foreground': '240 5.88% 90%',
|
||||||
|
'--heroui-content4': '240 5.2% 33.92%',
|
||||||
|
'--heroui-content4-foreground': '240 4.88% 83.92%',
|
||||||
|
'--heroui-default-50': '240 5.88% 10%',
|
||||||
|
'--heroui-default-100': '240 3.7% 15.88%',
|
||||||
|
'--heroui-default-200': '240 5.26% 26.08%',
|
||||||
|
'--heroui-default-300': '240 5.2% 33.92%',
|
||||||
|
'--heroui-default-400': '240 3.83% 46.08%',
|
||||||
|
'--heroui-default-500': '240 5.03% 64.9%',
|
||||||
|
'--heroui-default-600': '240 4.88% 83.92%',
|
||||||
|
'--heroui-default-700': '240 5.88% 90%',
|
||||||
|
'--heroui-default-800': '240 4.76% 95.88%',
|
||||||
|
'--heroui-default-900': '0 0% 98.04%',
|
||||||
|
'--heroui-default-foreground': '0 0% 100%',
|
||||||
|
'--heroui-default': '240 5.26% 26.08%',
|
||||||
|
'--heroui-danger-50': '340 84.91% 10.39%',
|
||||||
|
'--heroui-danger-100': '339.33 86.54% 20.39%',
|
||||||
|
'--heroui-danger-200': '339.11 85.99% 30.78%',
|
||||||
|
'--heroui-danger-300': '339 86.54% 40.78%',
|
||||||
|
'--heroui-danger-400': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-danger-500': '339 90% 60.78%',
|
||||||
|
'--heroui-danger-600': '339.11 90.6% 70.78%',
|
||||||
|
'--heroui-danger-700': '339.33 90% 80.39%',
|
||||||
|
'--heroui-danger-800': '340 91.84% 90.39%',
|
||||||
|
'--heroui-danger-900': '339.13 92% 95.1%',
|
||||||
|
'--heroui-danger-foreground': '0 0% 100%',
|
||||||
|
'--heroui-danger': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-primary-50': '211.84 100% 9.61%',
|
||||||
|
'--heroui-primary-100': '211.84 100% 19.22%',
|
||||||
|
'--heroui-primary-200': '212.24 100% 28.82%',
|
||||||
|
'--heroui-primary-300': '212.14 100% 38.43%',
|
||||||
|
'--heroui-primary-400': '212.02 100% 46.67%',
|
||||||
|
'--heroui-primary-500': '212.14 92.45% 58.43%',
|
||||||
|
'--heroui-primary-600': '212.24 92.45% 68.82%',
|
||||||
|
'--heroui-primary-700': '211.84 92.45% 79.22%',
|
||||||
|
'--heroui-primary-800': '211.84 92.45% 89.61%',
|
||||||
|
'--heroui-primary-900': '212.5 92.31% 94.9%',
|
||||||
|
'--heroui-primary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-primary': '212.02 100% 46.67%',
|
||||||
|
'--heroui-secondary-50': '270 66.67% 9.41%',
|
||||||
|
'--heroui-secondary-100': '270 66.67% 18.82%',
|
||||||
|
'--heroui-secondary-200': '270 66.67% 28.24%',
|
||||||
|
'--heroui-secondary-300': '270 66.67% 37.65%',
|
||||||
|
'--heroui-secondary-400': '270 66.67% 47.06%',
|
||||||
|
'--heroui-secondary-500': '270 59.26% 57.65%',
|
||||||
|
'--heroui-secondary-600': '270 59.26% 68.24%',
|
||||||
|
'--heroui-secondary-700': '270 59.26% 78.82%',
|
||||||
|
'--heroui-secondary-800': '270 59.26% 89.41%',
|
||||||
|
'--heroui-secondary-900': '270 61.54% 94.9%',
|
||||||
|
'--heroui-secondary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-secondary': '270 59.26% 57.65%',
|
||||||
|
'--heroui-success-50': '145.71 77.78% 8.82%',
|
||||||
|
'--heroui-success-100': '146.2 79.78% 17.45%',
|
||||||
|
'--heroui-success-200': '145.79 79.26% 26.47%',
|
||||||
|
'--heroui-success-300': '146.01 79.89% 35.1%',
|
||||||
|
'--heroui-success-400': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-success-500': '146.01 62.45% 55.1%',
|
||||||
|
'--heroui-success-600': '145.79 62.57% 66.47%',
|
||||||
|
'--heroui-success-700': '146.2 61.74% 77.45%',
|
||||||
|
'--heroui-success-800': '145.71 61.4% 88.82%',
|
||||||
|
'--heroui-success-900': '146.67 64.29% 94.51%',
|
||||||
|
'--heroui-success-foreground': '0 0% 0%',
|
||||||
|
'--heroui-success': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-warning-50': '37.14 75% 10.98%',
|
||||||
|
'--heroui-warning-100': '37.14 75% 21.96%',
|
||||||
|
'--heroui-warning-200': '36.96 73.96% 33.14%',
|
||||||
|
'--heroui-warning-300': '37.01 74.22% 44.12%',
|
||||||
|
'--heroui-warning-400': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-warning-500': '37.01 91.26% 64.12%',
|
||||||
|
'--heroui-warning-600': '36.96 91.24% 73.14%',
|
||||||
|
'--heroui-warning-700': '37.14 91.3% 81.96%',
|
||||||
|
'--heroui-warning-800': '37.14 91.3% 90.98%',
|
||||||
|
'--heroui-warning-900': '54.55 91.67% 95.29%',
|
||||||
|
'--heroui-warning-foreground': '0 0% 0%',
|
||||||
|
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-code-background': '240 5.56% 7.06%',
|
||||||
|
'--heroui-strong': '190.14 94.67% 44.12%',
|
||||||
|
'--heroui-code-mdx': '190.14 94.67% 44.12%',
|
||||||
|
'--heroui-divider-weight': '1px',
|
||||||
|
'--heroui-disabled-opacity': '.5',
|
||||||
|
'--heroui-font-size-tiny': '0.75rem',
|
||||||
|
'--heroui-font-size-small': '0.875rem',
|
||||||
|
'--heroui-font-size-medium': '1rem',
|
||||||
|
'--heroui-font-size-large': '1.125rem',
|
||||||
|
'--heroui-line-height-tiny': '1rem',
|
||||||
|
'--heroui-line-height-small': '1.25rem',
|
||||||
|
'--heroui-line-height-medium': '1.5rem',
|
||||||
|
'--heroui-line-height-large': '1.75rem',
|
||||||
|
'--heroui-radius-small': '8px',
|
||||||
|
'--heroui-radius-medium': '12px',
|
||||||
|
'--heroui-radius-large': '14px',
|
||||||
|
'--heroui-border-width-small': '1px',
|
||||||
|
'--heroui-border-width-medium': '2px',
|
||||||
|
'--heroui-border-width-large': '3px',
|
||||||
|
'--heroui-box-shadow-small':
|
||||||
|
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-box-shadow-medium':
|
||||||
|
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-box-shadow-large':
|
||||||
|
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-hover-opacity': '.9'
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
'--heroui-background': '0 0% 100%',
|
||||||
|
'--heroui-foreground-50': '240 5.88% 95%',
|
||||||
|
'--heroui-foreground-100': '240 3.7% 90%',
|
||||||
|
'--heroui-foreground-200': '240 5.26% 80%',
|
||||||
|
'--heroui-foreground-300': '240 5.2% 70%',
|
||||||
|
'--heroui-foreground-400': '240 3.83% 60%',
|
||||||
|
'--heroui-foreground-500': '240 5.03% 50%',
|
||||||
|
'--heroui-foreground-600': '240 4.88% 40%',
|
||||||
|
'--heroui-foreground-700': '240 5.88% 30%',
|
||||||
|
'--heroui-foreground-800': '240 4.76% 20%',
|
||||||
|
'--heroui-foreground-900': '0 0% 10%',
|
||||||
|
'--heroui-foreground': '210 5.56% 7.06%',
|
||||||
|
'--heroui-focus': '212.01999999999998 100% 53.33%',
|
||||||
|
'--heroui-overlay': '0 0% 100%',
|
||||||
|
'--heroui-divider': '0 0% 0%',
|
||||||
|
'--heroui-divider-opacity': '0.85',
|
||||||
|
'--heroui-content1': '240 5.88% 95%',
|
||||||
|
'--heroui-content1-foreground': '0 0% 10%',
|
||||||
|
'--heroui-content2': '240 3.7% 90%',
|
||||||
|
'--heroui-content2-foreground': '240 4.76% 20%',
|
||||||
|
'--heroui-content3': '240 5.26% 80%',
|
||||||
|
'--heroui-content3-foreground': '240 5.88% 30%',
|
||||||
|
'--heroui-content4': '240 5.2% 70%',
|
||||||
|
'--heroui-content4-foreground': '240 4.88% 40%',
|
||||||
|
'--heroui-default-50': '240 5.88% 95%',
|
||||||
|
'--heroui-default-100': '240 3.7% 90%',
|
||||||
|
'--heroui-default-200': '240 5.26% 80%',
|
||||||
|
'--heroui-default-300': '240 5.2% 70%',
|
||||||
|
'--heroui-default-400': '240 3.83% 60%',
|
||||||
|
'--heroui-default-500': '240 5.03% 50%',
|
||||||
|
'--heroui-default-600': '240 4.88% 40%',
|
||||||
|
'--heroui-default-700': '240 5.88% 30%',
|
||||||
|
'--heroui-default-800': '240 4.76% 20%',
|
||||||
|
'--heroui-default-900': '0 0% 10%',
|
||||||
|
'--heroui-default-foreground': '0 0% 0%',
|
||||||
|
'--heroui-default': '240 5.26% 80%',
|
||||||
|
'--heroui-danger-50': '339.13 92% 95.1%',
|
||||||
|
'--heroui-danger-100': '340 91.84% 90.39%',
|
||||||
|
'--heroui-danger-200': '339.33 90% 80.39%',
|
||||||
|
'--heroui-danger-300': '339.11 90.6% 70.78%',
|
||||||
|
'--heroui-danger-400': '339 90% 60.78%',
|
||||||
|
'--heroui-danger-500': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-danger-600': '339 86.54% 40.78%',
|
||||||
|
'--heroui-danger-700': '339.11 85.99% 30.78%',
|
||||||
|
'--heroui-danger-800': '339.33 86.54% 20.39%',
|
||||||
|
'--heroui-danger-900': '340 84.91% 10.39%',
|
||||||
|
'--heroui-danger-foreground': '0 0% 100%',
|
||||||
|
'--heroui-danger': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-primary-50': '212.5 92.31% 94.9%',
|
||||||
|
'--heroui-primary-100': '211.84 92.45% 89.61%',
|
||||||
|
'--heroui-primary-200': '211.84 92.45% 79.22%',
|
||||||
|
'--heroui-primary-300': '212.24 92.45% 68.82%',
|
||||||
|
'--heroui-primary-400': '212.14 92.45% 58.43%',
|
||||||
|
'--heroui-primary-500': '212.02 100% 46.67%',
|
||||||
|
'--heroui-primary-600': '212.14 100% 38.43%',
|
||||||
|
'--heroui-primary-700': '212.24 100% 28.82%',
|
||||||
|
'--heroui-primary-800': '211.84 100% 19.22%',
|
||||||
|
'--heroui-primary-900': '211.84 100% 9.61%',
|
||||||
|
'--heroui-primary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-primary': '212.02 100% 46.67%',
|
||||||
|
'--heroui-secondary-50': '270 61.54% 94.9%',
|
||||||
|
'--heroui-secondary-100': '270 59.26% 89.41%',
|
||||||
|
'--heroui-secondary-200': '270 59.26% 78.82%',
|
||||||
|
'--heroui-secondary-300': '270 59.26% 68.24%',
|
||||||
|
'--heroui-secondary-400': '270 59.26% 57.65%',
|
||||||
|
'--heroui-secondary-500': '270 66.67% 47.06%',
|
||||||
|
'--heroui-secondary-600': '270 66.67% 37.65%',
|
||||||
|
'--heroui-secondary-700': '270 66.67% 28.24%',
|
||||||
|
'--heroui-secondary-800': '270 66.67% 18.82%',
|
||||||
|
'--heroui-secondary-900': '270 66.67% 9.41%',
|
||||||
|
'--heroui-secondary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-secondary': '270 66.67% 47.06%',
|
||||||
|
'--heroui-success-50': '146.67 64.29% 94.51%',
|
||||||
|
'--heroui-success-100': '145.71 61.4% 88.82%',
|
||||||
|
'--heroui-success-200': '146.2 61.74% 77.45%',
|
||||||
|
'--heroui-success-300': '145.79 62.57% 66.47%',
|
||||||
|
'--heroui-success-400': '146.01 62.45% 55.1%',
|
||||||
|
'--heroui-success-500': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-success-600': '146.01 79.89% 35.1%',
|
||||||
|
'--heroui-success-700': '145.79 79.26% 26.47%',
|
||||||
|
'--heroui-success-800': '146.2 79.78% 17.45%',
|
||||||
|
'--heroui-success-900': '145.71 77.78% 8.82%',
|
||||||
|
'--heroui-success-foreground': '0 0% 0%',
|
||||||
|
'--heroui-success': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-warning-50': '54.55 91.67% 95.29%',
|
||||||
|
'--heroui-warning-100': '37.14 91.3% 90.98%',
|
||||||
|
'--heroui-warning-200': '37.14 91.3% 81.96%',
|
||||||
|
'--heroui-warning-300': '36.96 91.24% 73.14%',
|
||||||
|
'--heroui-warning-400': '37.01 91.26% 64.12%',
|
||||||
|
'--heroui-warning-500': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-warning-600': '37.01 74.22% 44.12%',
|
||||||
|
'--heroui-warning-700': '36.96 73.96% 33.14%',
|
||||||
|
'--heroui-warning-800': '37.14 75% 21.96%',
|
||||||
|
'--heroui-warning-900': '37.14 75% 10.98%',
|
||||||
|
'--heroui-warning-foreground': '0 0% 0%',
|
||||||
|
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-code-background': '221.25 17.39% 18.04%',
|
||||||
|
'--heroui-strong': '316.95 100% 65.29%',
|
||||||
|
'--heroui-code-mdx': '316.95 100% 65.29%',
|
||||||
|
'--heroui-divider-weight': '1px',
|
||||||
|
'--heroui-disabled-opacity': '.5',
|
||||||
|
'--heroui-font-size-tiny': '0.75rem',
|
||||||
|
'--heroui-font-size-small': '0.875rem',
|
||||||
|
'--heroui-font-size-medium': '1rem',
|
||||||
|
'--heroui-font-size-large': '1.125rem',
|
||||||
|
'--heroui-line-height-tiny': '1rem',
|
||||||
|
'--heroui-line-height-small': '1.25rem',
|
||||||
|
'--heroui-line-height-medium': '1.5rem',
|
||||||
|
'--heroui-line-height-large': '1.75rem',
|
||||||
|
'--heroui-radius-small': '8px',
|
||||||
|
'--heroui-radius-medium': '12px',
|
||||||
|
'--heroui-radius-large': '14px',
|
||||||
|
'--heroui-border-width-small': '1px',
|
||||||
|
'--heroui-border-width-medium': '2px',
|
||||||
|
'--heroui-border-width-large': '3px',
|
||||||
|
'--heroui-box-shadow-small':
|
||||||
|
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-box-shadow-medium':
|
||||||
|
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-box-shadow-large':
|
||||||
|
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-hover-opacity': '.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
theme,
|
||||||
|
author: 'HeroUI',
|
||||||
|
name: 'heroui',
|
||||||
|
description: 'HeroUI Default Theme'
|
||||||
|
} satisfies ThemeInfo
|
256
napcat.webui/src/const/themes/nc_pink.ts
Normal file
256
napcat.webui/src/const/themes/nc_pink.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
const theme: ThemeConfig = {
|
||||||
|
dark: {
|
||||||
|
'--heroui-background': '0 0% 0%',
|
||||||
|
'--heroui-foreground-50': '240 5.88% 10%',
|
||||||
|
'--heroui-foreground-100': '240 3.7% 15.88%',
|
||||||
|
'--heroui-foreground-200': '240 5.26% 26.08%',
|
||||||
|
'--heroui-foreground-300': '240 5.2% 33.92%',
|
||||||
|
'--heroui-foreground-400': '240 3.83% 46.08%',
|
||||||
|
'--heroui-foreground-500': '240 5.03% 64.9%',
|
||||||
|
'--heroui-foreground-600': '240 4.88% 83.92%',
|
||||||
|
'--heroui-foreground-700': '240 5.88% 90%',
|
||||||
|
'--heroui-foreground-800': '240 4.76% 95.88%',
|
||||||
|
'--heroui-foreground-900': '0 0% 98.04%',
|
||||||
|
'--heroui-foreground': '210 5.56% 92.94%',
|
||||||
|
'--heroui-focus': '212.01999999999998 100% 46.67%',
|
||||||
|
'--heroui-overlay': '0 0% 0%',
|
||||||
|
'--heroui-divider': '0 0% 100%',
|
||||||
|
'--heroui-divider-opacity': '0.15',
|
||||||
|
'--heroui-content1': '240 5.88% 10%',
|
||||||
|
'--heroui-content1-foreground': '0 0% 98.04%',
|
||||||
|
'--heroui-content2': '240 3.7% 15.88%',
|
||||||
|
'--heroui-content2-foreground': '240 4.76% 95.88%',
|
||||||
|
'--heroui-content3': '240 5.26% 26.08%',
|
||||||
|
'--heroui-content3-foreground': '240 5.88% 90%',
|
||||||
|
'--heroui-content4': '240 5.2% 33.92%',
|
||||||
|
'--heroui-content4-foreground': '240 4.88% 83.92%',
|
||||||
|
'--heroui-default-50': '240 5.88% 10%',
|
||||||
|
'--heroui-default-100': '240 3.7% 15.88%',
|
||||||
|
'--heroui-default-200': '240 5.26% 26.08%',
|
||||||
|
'--heroui-default-300': '240 5.2% 33.92%',
|
||||||
|
'--heroui-default-400': '240 3.83% 46.08%',
|
||||||
|
'--heroui-default-500': '240 5.03% 64.9%',
|
||||||
|
'--heroui-default-600': '240 4.88% 83.92%',
|
||||||
|
'--heroui-default-700': '240 5.88% 90%',
|
||||||
|
'--heroui-default-800': '240 4.76% 95.88%',
|
||||||
|
'--heroui-default-900': '0 0% 98.04%',
|
||||||
|
'--heroui-default-foreground': '0 0% 100%',
|
||||||
|
'--heroui-default': '240 5.26% 26.08%',
|
||||||
|
'--heroui-danger-50': '301.89 82.61% 22.55%',
|
||||||
|
'--heroui-danger-100': '308.18 76.39% 28.24%',
|
||||||
|
'--heroui-danger-200': '313.85 70.65% 36.08%',
|
||||||
|
'--heroui-danger-300': '319.73 65.64% 44.51%',
|
||||||
|
'--heroui-danger-400': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-danger-500': '331.82 75% 65.49%',
|
||||||
|
'--heroui-danger-600': '337.84 83.46% 73.92%',
|
||||||
|
'--heroui-danger-700': '343.42 90.48% 83.53%',
|
||||||
|
'--heroui-danger-800': '350.53 90.48% 91.76%',
|
||||||
|
'--heroui-danger-900': '324 90.91% 95.69%',
|
||||||
|
'--heroui-danger-foreground': '0 0% 100%',
|
||||||
|
'--heroui-danger': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-primary-50': '340 84.91% 10.39%',
|
||||||
|
'--heroui-primary-100': '339.33 86.54% 20.39%',
|
||||||
|
'--heroui-primary-200': '339.11 85.99% 30.78%',
|
||||||
|
'--heroui-primary-300': '339 86.54% 40.78%',
|
||||||
|
'--heroui-primary-400': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-primary-500': '339 90% 60.78%',
|
||||||
|
'--heroui-primary-600': '339.11 90.6% 70.78%',
|
||||||
|
'--heroui-primary-700': '339.33 90% 80.39%',
|
||||||
|
'--heroui-primary-800': '340 91.84% 90.39%',
|
||||||
|
'--heroui-primary-900': '339.13 92% 95.1%',
|
||||||
|
'--heroui-primary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-primary': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-secondary-50': '270 66.67% 9.41%',
|
||||||
|
'--heroui-secondary-100': '270 66.67% 18.82%',
|
||||||
|
'--heroui-secondary-200': '270 66.67% 28.24%',
|
||||||
|
'--heroui-secondary-300': '270 66.67% 37.65%',
|
||||||
|
'--heroui-secondary-400': '270 66.67% 47.06%',
|
||||||
|
'--heroui-secondary-500': '270 59.26% 57.65%',
|
||||||
|
'--heroui-secondary-600': '270 59.26% 68.24%',
|
||||||
|
'--heroui-secondary-700': '270 59.26% 78.82%',
|
||||||
|
'--heroui-secondary-800': '270 59.26% 89.41%',
|
||||||
|
'--heroui-secondary-900': '270 61.54% 94.9%',
|
||||||
|
'--heroui-secondary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-secondary': '270 59.26% 57.65%',
|
||||||
|
'--heroui-success-50': '145.71 77.78% 8.82%',
|
||||||
|
'--heroui-success-100': '146.2 79.78% 17.45%',
|
||||||
|
'--heroui-success-200': '145.79 79.26% 26.47%',
|
||||||
|
'--heroui-success-300': '146.01 79.89% 35.1%',
|
||||||
|
'--heroui-success-400': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-success-500': '146.01 62.45% 55.1%',
|
||||||
|
'--heroui-success-600': '145.79 62.57% 66.47%',
|
||||||
|
'--heroui-success-700': '146.2 61.74% 77.45%',
|
||||||
|
'--heroui-success-800': '145.71 61.4% 88.82%',
|
||||||
|
'--heroui-success-900': '146.67 64.29% 94.51%',
|
||||||
|
'--heroui-success-foreground': '0 0% 0%',
|
||||||
|
'--heroui-success': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-warning-50': '37.14 75% 10.98%',
|
||||||
|
'--heroui-warning-100': '37.14 75% 21.96%',
|
||||||
|
'--heroui-warning-200': '36.96 73.96% 33.14%',
|
||||||
|
'--heroui-warning-300': '37.01 74.22% 44.12%',
|
||||||
|
'--heroui-warning-400': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-warning-500': '37.01 91.26% 64.12%',
|
||||||
|
'--heroui-warning-600': '36.96 91.24% 73.14%',
|
||||||
|
'--heroui-warning-700': '37.14 91.3% 81.96%',
|
||||||
|
'--heroui-warning-800': '37.14 91.3% 90.98%',
|
||||||
|
'--heroui-warning-900': '54.55 91.67% 95.29%',
|
||||||
|
'--heroui-warning-foreground': '0 0% 0%',
|
||||||
|
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-code-background': '240 5.56% 7.06%',
|
||||||
|
'--heroui-strong': '190.14 94.67% 44.12%',
|
||||||
|
'--heroui-code-mdx': '190.14 94.67% 44.12%',
|
||||||
|
'--heroui-divider-weight': '1px',
|
||||||
|
'--heroui-disabled-opacity': '.5',
|
||||||
|
'--heroui-font-size-tiny': '0.75rem',
|
||||||
|
'--heroui-font-size-small': '0.875rem',
|
||||||
|
'--heroui-font-size-medium': '1rem',
|
||||||
|
'--heroui-font-size-large': '1.125rem',
|
||||||
|
'--heroui-line-height-tiny': '1rem',
|
||||||
|
'--heroui-line-height-small': '1.25rem',
|
||||||
|
'--heroui-line-height-medium': '1.5rem',
|
||||||
|
'--heroui-line-height-large': '1.75rem',
|
||||||
|
'--heroui-radius-small': '8px',
|
||||||
|
'--heroui-radius-medium': '12px',
|
||||||
|
'--heroui-radius-large': '14px',
|
||||||
|
'--heroui-border-width-small': '1px',
|
||||||
|
'--heroui-border-width-medium': '2px',
|
||||||
|
'--heroui-border-width-large': '3px',
|
||||||
|
'--heroui-box-shadow-small':
|
||||||
|
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-box-shadow-medium':
|
||||||
|
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-box-shadow-large':
|
||||||
|
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-hover-opacity': '.9'
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
'--heroui-background': '0 0% 100%',
|
||||||
|
'--heroui-foreground-50': '240 5.88% 95%',
|
||||||
|
'--heroui-foreground-100': '240 3.7% 90%',
|
||||||
|
'--heroui-foreground-200': '240 5.26% 80%',
|
||||||
|
'--heroui-foreground-300': '240 5.2% 70%',
|
||||||
|
'--heroui-foreground-400': '240 3.83% 60%',
|
||||||
|
'--heroui-foreground-500': '240 5.03% 50%',
|
||||||
|
'--heroui-foreground-600': '240 4.88% 40%',
|
||||||
|
'--heroui-foreground-700': '240 5.88% 30%',
|
||||||
|
'--heroui-foreground-800': '240 4.76% 20%',
|
||||||
|
'--heroui-foreground-900': '0 0% 10%',
|
||||||
|
'--heroui-foreground': '210 5.56% 7.06%',
|
||||||
|
'--heroui-focus': '212.01999999999998 100% 53.33%',
|
||||||
|
'--heroui-overlay': '0 0% 100%',
|
||||||
|
'--heroui-divider': '0 0% 0%',
|
||||||
|
'--heroui-divider-opacity': '0.85',
|
||||||
|
'--heroui-content1': '240 5.88% 95%',
|
||||||
|
'--heroui-content1-foreground': '0 0% 10%',
|
||||||
|
'--heroui-content2': '240 3.7% 90%',
|
||||||
|
'--heroui-content2-foreground': '240 4.76% 20%',
|
||||||
|
'--heroui-content3': '240 5.26% 80%',
|
||||||
|
'--heroui-content3-foreground': '240 5.88% 30%',
|
||||||
|
'--heroui-content4': '240 5.2% 70%',
|
||||||
|
'--heroui-content4-foreground': '240 4.88% 40%',
|
||||||
|
'--heroui-default-50': '240 5.88% 95%',
|
||||||
|
'--heroui-default-100': '240 3.7% 90%',
|
||||||
|
'--heroui-default-200': '240 5.26% 80%',
|
||||||
|
'--heroui-default-300': '240 5.2% 70%',
|
||||||
|
'--heroui-default-400': '240 3.83% 60%',
|
||||||
|
'--heroui-default-500': '240 5.03% 50%',
|
||||||
|
'--heroui-default-600': '240 4.88% 40%',
|
||||||
|
'--heroui-default-700': '240 5.88% 30%',
|
||||||
|
'--heroui-default-800': '240 4.76% 20%',
|
||||||
|
'--heroui-default-900': '0 0% 10%',
|
||||||
|
'--heroui-default-foreground': '0 0% 0%',
|
||||||
|
'--heroui-default': '240 5.26% 80%',
|
||||||
|
'--heroui-danger-50': '324 90.91% 95.69%',
|
||||||
|
'--heroui-danger-100': '350.53 90.48% 91.76%',
|
||||||
|
'--heroui-danger-200': '343.42 90.48% 83.53%',
|
||||||
|
'--heroui-danger-300': '337.84 83.46% 73.92%',
|
||||||
|
'--heroui-danger-400': '331.82 75% 65.49%',
|
||||||
|
'--heroui-danger-500': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-danger-600': '319.73 65.64% 44.51%',
|
||||||
|
'--heroui-danger-700': '313.85 70.65% 36.08%',
|
||||||
|
'--heroui-danger-800': '308.18 76.39% 28.24%',
|
||||||
|
'--heroui-danger-900': '301.89 82.61% 22.55%',
|
||||||
|
'--heroui-danger-foreground': '0 0% 100%',
|
||||||
|
'--heroui-danger': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-primary-50': '339.13 92% 95.1%',
|
||||||
|
'--heroui-primary-100': '340 91.84% 90.39%',
|
||||||
|
'--heroui-primary-200': '339.33 90% 80.39%',
|
||||||
|
'--heroui-primary-300': '339.11 90.6% 70.78%',
|
||||||
|
'--heroui-primary-400': '339 90% 60.78%',
|
||||||
|
'--heroui-primary-500': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-primary-600': '339 86.54% 40.78%',
|
||||||
|
'--heroui-primary-700': '339.11 85.99% 30.78%',
|
||||||
|
'--heroui-primary-800': '339.33 86.54% 20.39%',
|
||||||
|
'--heroui-primary-900': '340 84.91% 10.39%',
|
||||||
|
'--heroui-primary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-primary': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-secondary-50': '270 61.54% 94.9%',
|
||||||
|
'--heroui-secondary-100': '270 59.26% 89.41%',
|
||||||
|
'--heroui-secondary-200': '270 59.26% 78.82%',
|
||||||
|
'--heroui-secondary-300': '270 59.26% 68.24%',
|
||||||
|
'--heroui-secondary-400': '270 59.26% 57.65%',
|
||||||
|
'--heroui-secondary-500': '270 66.67% 47.06%',
|
||||||
|
'--heroui-secondary-600': '270 66.67% 37.65%',
|
||||||
|
'--heroui-secondary-700': '270 66.67% 28.24%',
|
||||||
|
'--heroui-secondary-800': '270 66.67% 18.82%',
|
||||||
|
'--heroui-secondary-900': '270 66.67% 9.41%',
|
||||||
|
'--heroui-secondary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-secondary': '270 66.67% 47.06%',
|
||||||
|
'--heroui-success-50': '146.67 64.29% 94.51%',
|
||||||
|
'--heroui-success-100': '145.71 61.4% 88.82%',
|
||||||
|
'--heroui-success-200': '146.2 61.74% 77.45%',
|
||||||
|
'--heroui-success-300': '145.79 62.57% 66.47%',
|
||||||
|
'--heroui-success-400': '146.01 62.45% 55.1%',
|
||||||
|
'--heroui-success-500': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-success-600': '146.01 79.89% 35.1%',
|
||||||
|
'--heroui-success-700': '145.79 79.26% 26.47%',
|
||||||
|
'--heroui-success-800': '146.2 79.78% 17.45%',
|
||||||
|
'--heroui-success-900': '145.71 77.78% 8.82%',
|
||||||
|
'--heroui-success-foreground': '0 0% 0%',
|
||||||
|
'--heroui-success': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-warning-50': '54.55 91.67% 95.29%',
|
||||||
|
'--heroui-warning-100': '37.14 91.3% 90.98%',
|
||||||
|
'--heroui-warning-200': '37.14 91.3% 81.96%',
|
||||||
|
'--heroui-warning-300': '36.96 91.24% 73.14%',
|
||||||
|
'--heroui-warning-400': '37.01 91.26% 64.12%',
|
||||||
|
'--heroui-warning-500': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-warning-600': '37.01 74.22% 44.12%',
|
||||||
|
'--heroui-warning-700': '36.96 73.96% 33.14%',
|
||||||
|
'--heroui-warning-800': '37.14 75% 21.96%',
|
||||||
|
'--heroui-warning-900': '37.14 75% 10.98%',
|
||||||
|
'--heroui-warning-foreground': '0 0% 0%',
|
||||||
|
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-code-background': '221.25 17.39% 18.04%',
|
||||||
|
'--heroui-strong': '316.95 100% 65.29%',
|
||||||
|
'--heroui-code-mdx': '316.95 100% 65.29%',
|
||||||
|
'--heroui-divider-weight': '1px',
|
||||||
|
'--heroui-disabled-opacity': '.5',
|
||||||
|
'--heroui-font-size-tiny': '0.75rem',
|
||||||
|
'--heroui-font-size-small': '0.875rem',
|
||||||
|
'--heroui-font-size-medium': '1rem',
|
||||||
|
'--heroui-font-size-large': '1.125rem',
|
||||||
|
'--heroui-line-height-tiny': '1rem',
|
||||||
|
'--heroui-line-height-small': '1.25rem',
|
||||||
|
'--heroui-line-height-medium': '1.5rem',
|
||||||
|
'--heroui-line-height-large': '1.75rem',
|
||||||
|
'--heroui-radius-small': '8px',
|
||||||
|
'--heroui-radius-medium': '12px',
|
||||||
|
'--heroui-radius-large': '14px',
|
||||||
|
'--heroui-border-width-small': '1px',
|
||||||
|
'--heroui-border-width-medium': '2px',
|
||||||
|
'--heroui-border-width-large': '3px',
|
||||||
|
'--heroui-box-shadow-small':
|
||||||
|
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-box-shadow-medium':
|
||||||
|
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-box-shadow-large':
|
||||||
|
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-hover-opacity': '.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
theme,
|
||||||
|
author: 'NapCat',
|
||||||
|
name: 'nc_pink',
|
||||||
|
description: 'NapCat Pink Theme'
|
||||||
|
} satisfies ThemeInfo
|
@@ -73,4 +73,17 @@ export default class QQManager {
|
|||||||
)
|
)
|
||||||
return data.data.data
|
return data.data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getQuickLoginQQ() {
|
||||||
|
const { data } = await serverRequest.post<ServerResponse<string>>(
|
||||||
|
'/QQLogin/GetQuickLoginQQ'
|
||||||
|
)
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async setQuickLoginQQ(uin: string) {
|
||||||
|
await serverRequest.post<ServerResponse<null>>('/QQLogin/SetQuickLoginQQ', {
|
||||||
|
uin
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,20 @@ export default class WebUIManager {
|
|||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getThemeConfig() {
|
||||||
|
const { data } =
|
||||||
|
await serverRequest.get<ServerResponse<ThemeConfig>>('/base/Theme')
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async setThemeConfig(theme: ThemeConfig) {
|
||||||
|
const { data } = await serverRequest.post<ServerResponse<boolean>>(
|
||||||
|
'/base/SetTheme',
|
||||||
|
{ theme }
|
||||||
|
)
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
public static async getLogList() {
|
public static async getLogList() {
|
||||||
const { data } =
|
const { data } =
|
||||||
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList')
|
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList')
|
||||||
|
@@ -8,6 +8,7 @@ import '@/styles/globals.css'
|
|||||||
|
|
||||||
import key from './const/key'
|
import key from './const/key'
|
||||||
import WebUIManager from './controllers/webui_manager'
|
import WebUIManager from './controllers/webui_manager'
|
||||||
|
import { loadTheme } from './utils/theme'
|
||||||
|
|
||||||
WebUIManager.checkWebUiLogined()
|
WebUIManager.checkWebUiLogined()
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ if (theme && !theme.startsWith('"')) {
|
|||||||
localStorage.setItem(key.theme, JSON.stringify(theme))
|
localStorage.setItem(key.theme, JSON.stringify(theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTheme()
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
// <React.StrictMode>
|
// <React.StrictMode>
|
||||||
<BrowserRouter basename="/webui/">
|
<BrowserRouter basename="/webui/">
|
||||||
|
@@ -1,21 +1,36 @@
|
|||||||
import { Card, CardBody } from '@heroui/card'
|
import { Card, CardBody } from '@heroui/card'
|
||||||
import { Tab, Tabs } from '@heroui/tabs'
|
import { Tab, Tabs } from '@heroui/tabs'
|
||||||
|
import clsx from 'clsx'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import ChangePasswordCard from './change_password'
|
import ChangePasswordCard from './change_password'
|
||||||
|
import LoginConfigCard from './login'
|
||||||
import OneBotConfigCard from './onebot'
|
import OneBotConfigCard from './onebot'
|
||||||
|
import ThemeConfigCard from './theme'
|
||||||
import WebUIConfigCard from './webui'
|
import WebUIConfigCard from './webui'
|
||||||
|
|
||||||
export interface ConfigPageProps {
|
export interface ConfigPageProps {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
|
size?: 'sm' | 'md' | 'lg'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => {
|
const ConfingPageItem: React.FC<ConfigPageProps> = ({
|
||||||
|
children,
|
||||||
|
size = 'md'
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Card className="bg-opacity-50 backdrop-blur-sm">
|
<Card className="bg-opacity-50 backdrop-blur-sm">
|
||||||
<CardBody className="items-center py-5">
|
<CardBody className="items-center py-5">
|
||||||
<div className="w-96 max-w-full flex flex-col gap-2">{children}</div>
|
<div
|
||||||
|
className={clsx('max-w-full flex flex-col gap-2', {
|
||||||
|
'w-72': size === 'sm',
|
||||||
|
'w-96': size === 'md',
|
||||||
|
'w-[32rem]': size === 'lg'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
@@ -57,12 +72,22 @@ export default function ConfigPage() {
|
|||||||
<WebUIConfigCard />
|
<WebUIConfigCard />
|
||||||
</ConfingPageItem>
|
</ConfingPageItem>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab title="登录配置" key="login">
|
||||||
|
<ConfingPageItem>
|
||||||
|
<LoginConfigCard />
|
||||||
|
</ConfingPageItem>
|
||||||
|
</Tab>
|
||||||
<Tab title="修改密码" key="token">
|
<Tab title="修改密码" key="token">
|
||||||
<ConfingPageItem>
|
<ConfingPageItem>
|
||||||
<ChangePasswordCard />
|
<ChangePasswordCard />
|
||||||
</ConfingPageItem>
|
</ConfingPageItem>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="主题配置" key="theme">
|
||||||
|
<ConfingPageItem size="lg">
|
||||||
|
<ThemeConfigCard />
|
||||||
|
</ConfingPageItem>
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
89
napcat.webui/src/pages/dashboard/config/login.tsx
Normal file
89
napcat.webui/src/pages/dashboard/config/login.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Input } from '@heroui/input'
|
||||||
|
import { useRequest } from 'ahooks'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { Controller, useForm } from 'react-hook-form'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
|
import SaveButtons from '@/components/button/save_buttons'
|
||||||
|
import PageLoading from '@/components/page_loading'
|
||||||
|
|
||||||
|
import QQManager from '@/controllers/qq_manager'
|
||||||
|
|
||||||
|
const LoginConfigCard = () => {
|
||||||
|
const {
|
||||||
|
data: quickLoginData,
|
||||||
|
loading: quickLoginLoading,
|
||||||
|
error: quickLoginError,
|
||||||
|
refreshAsync: refreshQuickLogin
|
||||||
|
} = useRequest(QQManager.getQuickLoginQQ)
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit: handleOnebotSubmit,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
setValue: setOnebotValue
|
||||||
|
} = useForm<{
|
||||||
|
quickLoginQQ: string
|
||||||
|
}>({
|
||||||
|
defaultValues: {
|
||||||
|
quickLoginQQ: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setOnebotValue('quickLoginQQ', quickLoginData ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await QQManager.setQuickLoginQQ(data.quickLoginQQ)
|
||||||
|
toast.success('保存成功')
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`保存失败: ${msg}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
try {
|
||||||
|
await refreshQuickLogin()
|
||||||
|
toast.success('刷新成功')
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`刷新失败: ${msg}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset()
|
||||||
|
}, [quickLoginData])
|
||||||
|
|
||||||
|
if (quickLoginLoading) return <PageLoading loading={true} />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>OneBot配置 - NapCat WebUI</title>
|
||||||
|
<div className="flex-shrink-0 w-full">快速登录QQ</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="quickLoginQQ"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
label="快速登录QQ"
|
||||||
|
placeholder="请输入QQ号"
|
||||||
|
isDisabled={!!quickLoginError}
|
||||||
|
errorMessage={quickLoginError ? '获取快速登录QQ失败' : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<SaveButtons
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
reset={reset}
|
||||||
|
isSubmitting={isSubmitting || quickLoginLoading}
|
||||||
|
refresh={onRefresh}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginConfigCard
|
@@ -30,9 +30,9 @@ const OneBotConfigCard = () => {
|
|||||||
setOnebotValue('parseMultMsg', config.parseMultMsg)
|
setOnebotValue('parseMultMsg', config.parseMultMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = handleOnebotSubmit((data) => {
|
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
saveConfigWithoutNetwork(data)
|
await saveConfigWithoutNetwork(data)
|
||||||
toast.success('保存成功')
|
toast.success('保存成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = (error as Error).message
|
const msg = (error as Error).message
|
||||||
|
279
napcat.webui/src/pages/dashboard/config/theme.tsx
Normal file
279
napcat.webui/src/pages/dashboard/config/theme.tsx
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import { Accordion, AccordionItem } from '@heroui/accordion'
|
||||||
|
import { Card, CardBody, CardHeader } from '@heroui/card'
|
||||||
|
import { useRequest } from 'ahooks'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { Controller, useForm, useWatch } from 'react-hook-form'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { FaUserAstronaut } from 'react-icons/fa'
|
||||||
|
import { FaPaintbrush } from 'react-icons/fa6'
|
||||||
|
import { IoIosColorPalette } from 'react-icons/io'
|
||||||
|
import { MdDarkMode, MdLightMode } from 'react-icons/md'
|
||||||
|
|
||||||
|
import themes from '@/const/themes'
|
||||||
|
|
||||||
|
import ColorPicker from '@/components/ColorPicker'
|
||||||
|
import SaveButtons from '@/components/button/save_buttons'
|
||||||
|
import PageLoading from '@/components/page_loading'
|
||||||
|
|
||||||
|
import { colorKeys, generateTheme, loadTheme } from '@/utils/theme'
|
||||||
|
|
||||||
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
|
|
||||||
|
export type PreviewThemeCardProps = {
|
||||||
|
theme: ThemeInfo
|
||||||
|
onPreview: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = [
|
||||||
|
'',
|
||||||
|
'-50',
|
||||||
|
'-100',
|
||||||
|
'-200',
|
||||||
|
'-300',
|
||||||
|
'-400',
|
||||||
|
'-500',
|
||||||
|
'-600',
|
||||||
|
'-700',
|
||||||
|
'-800',
|
||||||
|
'-900'
|
||||||
|
]
|
||||||
|
const colors = [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'success',
|
||||||
|
'danger',
|
||||||
|
'warning',
|
||||||
|
'default'
|
||||||
|
]
|
||||||
|
|
||||||
|
function PreviewThemeCard({ theme, onPreview }: PreviewThemeCardProps) {
|
||||||
|
const style = document.createElement('style')
|
||||||
|
style.innerHTML = generateTheme(theme.theme, theme.name)
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
document.head.appendChild(style)
|
||||||
|
return () => {
|
||||||
|
document.head.removeChild(style)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
ref={cardRef}
|
||||||
|
shadow="sm"
|
||||||
|
radius="sm"
|
||||||
|
isPressable
|
||||||
|
onPress={onPreview}
|
||||||
|
className={clsx('text-primary bg-primary-50', theme.name)}
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-0 flex flex-col items-start gap-1">
|
||||||
|
<div className="px-1 rounded-md bg-primary text-primary-foreground">
|
||||||
|
{theme.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs flex items-center gap-1 text-primary-300">
|
||||||
|
<FaUserAstronaut />
|
||||||
|
{theme.author ?? '未知'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-primary-200">{theme.description}</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{colors.map((color) => (
|
||||||
|
<div className="flex gap-1 items-center flex-wrap" key={color}>
|
||||||
|
<div className="text-xs w-4 text-right">
|
||||||
|
{color[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
{values.map((value) => (
|
||||||
|
<div
|
||||||
|
key={value}
|
||||||
|
className={clsx(
|
||||||
|
'w-2 h-2 rounded-full shadow-small',
|
||||||
|
`bg-${color}${value}`
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeConfigCard = () => {
|
||||||
|
const { data, loading, error, refreshAsync } = useRequest(
|
||||||
|
WebUIManager.getThemeConfig
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit: handleOnebotSubmit,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
setValue: setOnebotValue
|
||||||
|
} = useForm<{
|
||||||
|
theme: ThemeConfig
|
||||||
|
}>({
|
||||||
|
defaultValues: {
|
||||||
|
theme: {
|
||||||
|
dark: {},
|
||||||
|
light: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用 useRef 存储 style 标签引用
|
||||||
|
const styleTagRef = useRef<HTMLStyleElement | null>(null)
|
||||||
|
|
||||||
|
// 在组件挂载时创建 style 标签,并在卸载时清理
|
||||||
|
useEffect(() => {
|
||||||
|
const styleTag = document.createElement('style')
|
||||||
|
document.head.appendChild(styleTag)
|
||||||
|
styleTagRef.current = styleTag
|
||||||
|
return () => {
|
||||||
|
if (styleTagRef.current) {
|
||||||
|
document.head.removeChild(styleTagRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const theme = useWatch({ control, name: 'theme' })
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
if (data) setOnebotValue('theme', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await WebUIManager.setThemeConfig(data.theme)
|
||||||
|
toast.success('保存成功')
|
||||||
|
loadTheme()
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`保存失败: ${msg}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
try {
|
||||||
|
await refreshAsync()
|
||||||
|
toast.success('刷新成功')
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`刷新失败: ${msg}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset()
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme && styleTagRef.current) {
|
||||||
|
const css = generateTheme(theme)
|
||||||
|
styleTagRef.current.innerHTML = css
|
||||||
|
}
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
if (loading) return <PageLoading loading={true} />
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return (
|
||||||
|
<div className="py-24 text-danger-500 text-center">{error.message}</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>主题配置 - NapCat WebUI</title>
|
||||||
|
|
||||||
|
<SaveButtons
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
reset={reset}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
refresh={onRefresh}
|
||||||
|
className="items-end w-full p-4"
|
||||||
|
/>
|
||||||
|
<div className="px-4 text-sm text-default-600">实时预览,记得保存!</div>
|
||||||
|
<Accordion variant="splitted" defaultExpandedKeys={['select']}>
|
||||||
|
<AccordionItem
|
||||||
|
key="select"
|
||||||
|
aria-label="Pick Color"
|
||||||
|
title="选择主题"
|
||||||
|
subtitle="可以切换夜间/白昼模式查看对应颜色"
|
||||||
|
className="shadow-small"
|
||||||
|
startContent={<IoIosColorPalette />}
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{themes.map((theme) => (
|
||||||
|
<PreviewThemeCard
|
||||||
|
key={theme.name}
|
||||||
|
theme={theme}
|
||||||
|
onPreview={() => {
|
||||||
|
setOnebotValue('theme', theme.theme)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
key="pick"
|
||||||
|
aria-label="Pick Color"
|
||||||
|
title="自定义配色"
|
||||||
|
className="shadow-small"
|
||||||
|
startContent={<FaPaintbrush />}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{(['dark', 'light'] as const).map((mode) => (
|
||||||
|
<div
|
||||||
|
key={mode}
|
||||||
|
className={clsx(
|
||||||
|
'p-2 rounded-md',
|
||||||
|
mode === 'dark' ? 'text-white' : 'text-black',
|
||||||
|
mode === 'dark'
|
||||||
|
? 'bg-content1-foreground dark:bg-content1'
|
||||||
|
: 'bg-content1 dark:bg-content1-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h3 className="text-center p-2 rounded-md bg-content2 mb-2 text-default-800 flex items-center justify-center">
|
||||||
|
{mode === 'dark' ? (
|
||||||
|
<MdDarkMode size={24} />
|
||||||
|
) : (
|
||||||
|
<MdLightMode size={24} />
|
||||||
|
)}
|
||||||
|
{mode === 'dark' ? '夜间模式主题' : '白昼模式主题'}
|
||||||
|
</h3>
|
||||||
|
{colorKeys.map((key) => (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className="grid grid-cols-2 items-center mb-2 gap-2"
|
||||||
|
>
|
||||||
|
<label className="text-right">{key}</label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`theme.${mode}.${key}`}
|
||||||
|
render={({ field: { value, onChange } }) => {
|
||||||
|
const hslArray = value?.split(' ') ?? [0, 0, 0]
|
||||||
|
const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`
|
||||||
|
return (
|
||||||
|
<ColorPicker
|
||||||
|
color={color}
|
||||||
|
onChange={(result) => {
|
||||||
|
onChange(
|
||||||
|
`${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%`
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ThemeConfigCard
|
@@ -105,7 +105,7 @@ const DashboardIndexPage: React.FC = () => {
|
|||||||
<SystemStatusCard setArchInfo={setArchInfo} />
|
<SystemStatusCard setArchInfo={setArchInfo} />
|
||||||
</div>
|
</div>
|
||||||
<Networks />
|
<Networks />
|
||||||
<Card className="bg-opacity-60 shadow-sm shadow-primary-50">
|
<Card className="bg-opacity-60 shadow-sm shadow-primary-100">
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Hitokoto />
|
<Hitokoto />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
.shiny-text {
|
.shiny-text {
|
||||||
@apply text-pink-400 text-opacity-60;
|
@apply text-primary-400 text-opacity-60;
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
120deg,
|
120deg,
|
||||||
rgba(255, 50, 50, 0) 40%,
|
rgba(255, 50, 50, 0) 40%,
|
||||||
rgba(255, 76, 76, 0.8) 50%,
|
hsl(var(--heroui-primary-400) / 0.8) 50%,
|
||||||
rgba(255, 50, 50, 0) 60%
|
rgba(255, 50, 50, 0) 60%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,10 @@
|
|||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
120deg,
|
120deg,
|
||||||
rgba(255, 255, 255, 0) 40%,
|
rgba(255, 255, 255, 0) 40%,
|
||||||
rgba(206, 21, 21, 0.8) 50%,
|
hsl(var(--heroui-primary-600) / 0.8) 50%,
|
||||||
rgba(255, 255, 255, 0) 60%
|
rgba(255, 255, 255, 0) 60%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shine {
|
@keyframes shine {
|
||||||
0% {
|
0% {
|
||||||
background-position: 100%;
|
background-position: 100%;
|
||||||
|
133
napcat.webui/src/types/server.d.ts
vendored
133
napcat.webui/src/types/server.d.ts
vendored
@@ -48,3 +48,136 @@ interface SystemStatus {
|
|||||||
}
|
}
|
||||||
arch: string
|
arch: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ThemeConfigItem {
|
||||||
|
'--heroui-background': string
|
||||||
|
'--heroui-foreground-50': string
|
||||||
|
'--heroui-foreground-100': string
|
||||||
|
'--heroui-foreground-200': string
|
||||||
|
'--heroui-foreground-300': string
|
||||||
|
'--heroui-foreground-400': string
|
||||||
|
'--heroui-foreground-500': string
|
||||||
|
'--heroui-foreground-600': string
|
||||||
|
'--heroui-foreground-700': string
|
||||||
|
'--heroui-foreground-800': string
|
||||||
|
'--heroui-foreground-900': string
|
||||||
|
'--heroui-foreground': string
|
||||||
|
'--heroui-focus': string
|
||||||
|
'--heroui-overlay': string
|
||||||
|
'--heroui-divider': string
|
||||||
|
'--heroui-divider-opacity': string
|
||||||
|
'--heroui-content1': string
|
||||||
|
'--heroui-content1-foreground': string
|
||||||
|
'--heroui-content2': string
|
||||||
|
'--heroui-content2-foreground': string
|
||||||
|
'--heroui-content3': string
|
||||||
|
'--heroui-content3-foreground': string
|
||||||
|
'--heroui-content4': string
|
||||||
|
'--heroui-content4-foreground': string
|
||||||
|
'--heroui-default-50': string
|
||||||
|
'--heroui-default-100': string
|
||||||
|
'--heroui-default-200': string
|
||||||
|
'--heroui-default-300': string
|
||||||
|
'--heroui-default-400': string
|
||||||
|
'--heroui-default-500': string
|
||||||
|
'--heroui-default-600': string
|
||||||
|
'--heroui-default-700': string
|
||||||
|
'--heroui-default-800': string
|
||||||
|
'--heroui-default-900': string
|
||||||
|
'--heroui-default-foreground': string
|
||||||
|
'--heroui-default': string
|
||||||
|
// 新增 danger
|
||||||
|
'--heroui-danger-50': string
|
||||||
|
'--heroui-danger-100': string
|
||||||
|
'--heroui-danger-200': string
|
||||||
|
'--heroui-danger-300': string
|
||||||
|
'--heroui-danger-400': string
|
||||||
|
'--heroui-danger-500': string
|
||||||
|
'--heroui-danger-600': string
|
||||||
|
'--heroui-danger-700': string
|
||||||
|
'--heroui-danger-800': string
|
||||||
|
'--heroui-danger-900': string
|
||||||
|
'--heroui-danger-foreground': string
|
||||||
|
'--heroui-danger': string
|
||||||
|
// 新增 primary
|
||||||
|
'--heroui-primary-50': string
|
||||||
|
'--heroui-primary-100': string
|
||||||
|
'--heroui-primary-200': string
|
||||||
|
'--heroui-primary-300': string
|
||||||
|
'--heroui-primary-400': string
|
||||||
|
'--heroui-primary-500': string
|
||||||
|
'--heroui-primary-600': string
|
||||||
|
'--heroui-primary-700': string
|
||||||
|
'--heroui-primary-800': string
|
||||||
|
'--heroui-primary-900': string
|
||||||
|
'--heroui-primary-foreground': string
|
||||||
|
'--heroui-primary': string
|
||||||
|
// 新增 secondary
|
||||||
|
'--heroui-secondary-50': string
|
||||||
|
'--heroui-secondary-100': string
|
||||||
|
'--heroui-secondary-200': string
|
||||||
|
'--heroui-secondary-300': string
|
||||||
|
'--heroui-secondary-400': string
|
||||||
|
'--heroui-secondary-500': string
|
||||||
|
'--heroui-secondary-600': string
|
||||||
|
'--heroui-secondary-700': string
|
||||||
|
'--heroui-secondary-800': string
|
||||||
|
'--heroui-secondary-900': string
|
||||||
|
'--heroui-secondary-foreground': string
|
||||||
|
'--heroui-secondary': string
|
||||||
|
// 新增 success
|
||||||
|
'--heroui-success-50': string
|
||||||
|
'--heroui-success-100': string
|
||||||
|
'--heroui-success-200': string
|
||||||
|
'--heroui-success-300': string
|
||||||
|
'--heroui-success-400': string
|
||||||
|
'--heroui-success-500': string
|
||||||
|
'--heroui-success-600': string
|
||||||
|
'--heroui-success-700': string
|
||||||
|
'--heroui-success-800': string
|
||||||
|
'--heroui-success-900': string
|
||||||
|
'--heroui-success-foreground': string
|
||||||
|
'--heroui-success': string
|
||||||
|
// 新增 warning
|
||||||
|
'--heroui-warning-50': string
|
||||||
|
'--heroui-warning-100': string
|
||||||
|
'--heroui-warning-200': string
|
||||||
|
'--heroui-warning-300': string
|
||||||
|
'--heroui-warning-400': string
|
||||||
|
'--heroui-warning-500': string
|
||||||
|
'--heroui-warning-600': string
|
||||||
|
'--heroui-warning-700': string
|
||||||
|
'--heroui-warning-800': string
|
||||||
|
'--heroui-warning-900': string
|
||||||
|
'--heroui-warning-foreground': string
|
||||||
|
'--heroui-warning': string
|
||||||
|
// 其它配置
|
||||||
|
'--heroui-code-background': string
|
||||||
|
'--heroui-strong': string
|
||||||
|
'--heroui-code-mdx': string
|
||||||
|
'--heroui-divider-weight': string
|
||||||
|
'--heroui-disabled-opacity': string
|
||||||
|
'--heroui-font-size-tiny': string
|
||||||
|
'--heroui-font-size-small': string
|
||||||
|
'--heroui-font-size-medium': string
|
||||||
|
'--heroui-font-size-large': string
|
||||||
|
'--heroui-line-height-tiny': string
|
||||||
|
'--heroui-line-height-small': string
|
||||||
|
'--heroui-line-height-medium': string
|
||||||
|
'--heroui-line-height-large': string
|
||||||
|
'--heroui-radius-small': string
|
||||||
|
'--heroui-radius-medium': string
|
||||||
|
'--heroui-radius-large': string
|
||||||
|
'--heroui-border-width-small': string
|
||||||
|
'--heroui-border-width-medium': string
|
||||||
|
'--heroui-border-width-large': string
|
||||||
|
'--heroui-box-shadow-small': string
|
||||||
|
'--heroui-box-shadow-medium': string
|
||||||
|
'--heroui-box-shadow-large': string
|
||||||
|
'--heroui-hover-opacity': string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemeConfig {
|
||||||
|
dark: ThemeConfigItem
|
||||||
|
light: ThemeConfigItem
|
||||||
|
}
|
||||||
|
6
napcat.webui/src/types/theme.d.ts
vendored
Normal file
6
napcat.webui/src/types/theme.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
interface ThemeInfo {
|
||||||
|
theme: ThemeConfig
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
author?: string
|
||||||
|
}
|
141
napcat.webui/src/utils/theme.ts
Normal file
141
napcat.webui/src/utils/theme.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { request } from './request'
|
||||||
|
|
||||||
|
const style = document.createElement('style')
|
||||||
|
document.head.appendChild(style)
|
||||||
|
|
||||||
|
export function loadTheme() {
|
||||||
|
request('/files/theme.css?_t=' + Date.now())
|
||||||
|
.then((res) => res.data)
|
||||||
|
.then((css) => {
|
||||||
|
style.innerHTML = css
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.error('Failed to load theme.css')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorKeys = [
|
||||||
|
'--heroui-background',
|
||||||
|
|
||||||
|
'--heroui-foreground-50',
|
||||||
|
'--heroui-foreground-100',
|
||||||
|
'--heroui-foreground-200',
|
||||||
|
'--heroui-foreground-300',
|
||||||
|
'--heroui-foreground-400',
|
||||||
|
'--heroui-foreground-500',
|
||||||
|
'--heroui-foreground-600',
|
||||||
|
'--heroui-foreground-700',
|
||||||
|
'--heroui-foreground-800',
|
||||||
|
'--heroui-foreground-900',
|
||||||
|
'--heroui-foreground',
|
||||||
|
|
||||||
|
'--heroui-content1',
|
||||||
|
'--heroui-content1-foreground',
|
||||||
|
'--heroui-content2',
|
||||||
|
'--heroui-content2-foreground',
|
||||||
|
'--heroui-content3',
|
||||||
|
'--heroui-content3-foreground',
|
||||||
|
'--heroui-content4',
|
||||||
|
'--heroui-content4-foreground',
|
||||||
|
|
||||||
|
'--heroui-default-50',
|
||||||
|
'--heroui-default-100',
|
||||||
|
'--heroui-default-200',
|
||||||
|
'--heroui-default-300',
|
||||||
|
'--heroui-default-400',
|
||||||
|
'--heroui-default-500',
|
||||||
|
'--heroui-default-600',
|
||||||
|
'--heroui-default-700',
|
||||||
|
'--heroui-default-800',
|
||||||
|
'--heroui-default-900',
|
||||||
|
'--heroui-default-foreground',
|
||||||
|
'--heroui-default',
|
||||||
|
|
||||||
|
'--heroui-danger-50',
|
||||||
|
'--heroui-danger-100',
|
||||||
|
'--heroui-danger-200',
|
||||||
|
'--heroui-danger-300',
|
||||||
|
'--heroui-danger-400',
|
||||||
|
'--heroui-danger-500',
|
||||||
|
'--heroui-danger-600',
|
||||||
|
'--heroui-danger-700',
|
||||||
|
'--heroui-danger-800',
|
||||||
|
'--heroui-danger-900',
|
||||||
|
'--heroui-danger-foreground',
|
||||||
|
'--heroui-danger',
|
||||||
|
|
||||||
|
'--heroui-primary-50',
|
||||||
|
'--heroui-primary-100',
|
||||||
|
'--heroui-primary-200',
|
||||||
|
'--heroui-primary-300',
|
||||||
|
'--heroui-primary-400',
|
||||||
|
'--heroui-primary-500',
|
||||||
|
'--heroui-primary-600',
|
||||||
|
'--heroui-primary-700',
|
||||||
|
'--heroui-primary-800',
|
||||||
|
'--heroui-primary-900',
|
||||||
|
'--heroui-primary-foreground',
|
||||||
|
'--heroui-primary',
|
||||||
|
|
||||||
|
'--heroui-secondary-50',
|
||||||
|
'--heroui-secondary-100',
|
||||||
|
'--heroui-secondary-200',
|
||||||
|
'--heroui-secondary-300',
|
||||||
|
'--heroui-secondary-400',
|
||||||
|
'--heroui-secondary-500',
|
||||||
|
'--heroui-secondary-600',
|
||||||
|
'--heroui-secondary-700',
|
||||||
|
'--heroui-secondary-800',
|
||||||
|
'--heroui-secondary-900',
|
||||||
|
'--heroui-secondary-foreground',
|
||||||
|
'--heroui-secondary',
|
||||||
|
|
||||||
|
'--heroui-success-50',
|
||||||
|
'--heroui-success-100',
|
||||||
|
'--heroui-success-200',
|
||||||
|
'--heroui-success-300',
|
||||||
|
'--heroui-success-400',
|
||||||
|
'--heroui-success-500',
|
||||||
|
'--heroui-success-600',
|
||||||
|
'--heroui-success-700',
|
||||||
|
'--heroui-success-800',
|
||||||
|
'--heroui-success-900',
|
||||||
|
'--heroui-success-foreground',
|
||||||
|
'--heroui-success',
|
||||||
|
|
||||||
|
'--heroui-warning-50',
|
||||||
|
'--heroui-warning-100',
|
||||||
|
'--heroui-warning-200',
|
||||||
|
'--heroui-warning-300',
|
||||||
|
'--heroui-warning-400',
|
||||||
|
'--heroui-warning-500',
|
||||||
|
'--heroui-warning-600',
|
||||||
|
'--heroui-warning-700',
|
||||||
|
'--heroui-warning-800',
|
||||||
|
'--heroui-warning-900',
|
||||||
|
'--heroui-warning-foreground',
|
||||||
|
'--heroui-warning',
|
||||||
|
|
||||||
|
'--heroui-focus',
|
||||||
|
'--heroui-overlay',
|
||||||
|
'--heroui-divider',
|
||||||
|
'--heroui-code-background',
|
||||||
|
'--heroui-strong',
|
||||||
|
'--heroui-code-mdx'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const generateTheme = (theme: ThemeConfig, validField?: string) => {
|
||||||
|
let css = `:root ${validField ? `.${validField}` : ''}, .light ${validField ? `.${validField}` : ''}, [data-theme="light"] ${validField ? `.${validField}` : ''} {`
|
||||||
|
for (const key in theme.light) {
|
||||||
|
const _key = key as keyof ThemeConfigItem
|
||||||
|
css += `${_key}: ${theme.light[_key]};`
|
||||||
|
}
|
||||||
|
css += `}`
|
||||||
|
css += `.dark ${validField ? `.${validField}` : ''}, [data-theme="dark"] ${validField ? `.${validField}` : ''} {`
|
||||||
|
for (const key in theme.dark) {
|
||||||
|
const _key = key as keyof ThemeConfigItem
|
||||||
|
css += `${_key}: ${theme.dark[_key]};`
|
||||||
|
}
|
||||||
|
css += `}`
|
||||||
|
return css
|
||||||
|
}
|
@@ -9,6 +9,12 @@ export default {
|
|||||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
||||||
],
|
],
|
||||||
|
safelist: [
|
||||||
|
{
|
||||||
|
pattern:
|
||||||
|
/bg-(primary|secondary|success|danger|warning|default)-(50|100|200|300|400|500|600|700|800|900)/
|
||||||
|
}
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {}
|
extend: {}
|
||||||
},
|
},
|
||||||
|
@@ -34,7 +34,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
ws: true,
|
ws: true,
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
'/api': backendDebugUrl
|
'/api': backendDebugUrl,
|
||||||
|
'/files': backendDebugUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.5.16",
|
"version": "4.5.18",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
@@ -32,7 +32,10 @@
|
|||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.0.1",
|
"@types/node": "^22.0.1",
|
||||||
|
"@types/on-finished": "^2.3.4",
|
||||||
"@types/qrcode-terminal": "^0.12.2",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
|
"@types/react-color": "^3.0.13",
|
||||||
|
"@types/type-is": "^1.6.7",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
|
@@ -54,7 +54,7 @@ export class ForwardMsgBuilder {
|
|||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
source = isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录';
|
source = msg.length === 0 ? '聊天记录' : (isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录');
|
||||||
}
|
}
|
||||||
if (!news) {
|
if (!news) {
|
||||||
news = msg.length === 0 ? [{
|
news = msg.length === 0 ? [{
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.5.16';
|
export const napCatVersion = '4.5.18';
|
||||||
|
@@ -9,7 +9,7 @@ export const NapcatConfigSchema = Type.Object({
|
|||||||
fileLogLevel: Type.String({ default: 'debug' }),
|
fileLogLevel: Type.String({ default: 'debug' }),
|
||||||
consoleLogLevel: Type.String({ default: 'info' }),
|
consoleLogLevel: Type.String({ default: 'info' }),
|
||||||
packetBackend: Type.String({ default: 'auto' }),
|
packetBackend: Type.String({ default: 'auto' }),
|
||||||
packetServer: Type.String({ default: '' })
|
packetServer: Type.String({ default: '' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type NapcatConfig = Static<typeof NapcatConfigSchema>;
|
export type NapcatConfig = Static<typeof NapcatConfigSchema>;
|
||||||
|
@@ -9,10 +9,10 @@ import {
|
|||||||
SendFileElement,
|
SendFileElement,
|
||||||
SendMarkdownElement,
|
SendMarkdownElement,
|
||||||
SendMarketFaceElement,
|
SendMarketFaceElement,
|
||||||
|
SendMultiForwardMsgElement,
|
||||||
SendPicElement,
|
SendPicElement,
|
||||||
SendPttElement,
|
SendPttElement,
|
||||||
SendReplyElement,
|
SendReplyElement,
|
||||||
SendStructLongMsgElement,
|
|
||||||
SendTextElement,
|
SendTextElement,
|
||||||
SendVideoElement
|
SendVideoElement
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
@@ -46,7 +46,7 @@ const SupportedElementTypes = [
|
|||||||
ElementType.PTT,
|
ElementType.PTT,
|
||||||
ElementType.ARK,
|
ElementType.ARK,
|
||||||
ElementType.MARKDOWN,
|
ElementType.MARKDOWN,
|
||||||
ElementType.STRUCTLONGMSG
|
ElementType.MULTIFORWARD
|
||||||
];
|
];
|
||||||
|
|
||||||
type SendMessageTypeElementMap = {
|
type SendMessageTypeElementMap = {
|
||||||
@@ -59,7 +59,7 @@ type SendMessageTypeElementMap = {
|
|||||||
[ElementType.REPLY]: SendReplyElement,
|
[ElementType.REPLY]: SendReplyElement,
|
||||||
[ElementType.ARK]: SendArkElement,
|
[ElementType.ARK]: SendArkElement,
|
||||||
[ElementType.MFACE]: SendMarketFaceElement,
|
[ElementType.MFACE]: SendMarketFaceElement,
|
||||||
[ElementType.STRUCTLONGMSG]: SendStructLongMsgElement,
|
[ElementType.MULTIFORWARD]: SendMultiForwardMsgElement,
|
||||||
[ElementType.MARKDOWN]: SendMarkdownElement,
|
[ElementType.MARKDOWN]: SendMarkdownElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,9 +118,8 @@ export class PacketMsgConverter {
|
|||||||
[ElementType.MARKDOWN]: (element) => {
|
[ElementType.MARKDOWN]: (element) => {
|
||||||
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
|
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
|
||||||
},
|
},
|
||||||
// TODO: check this logic, move it in arkElement?
|
[ElementType.MULTIFORWARD]: (element) => {
|
||||||
[ElementType.STRUCTLONGMSG]: (element) => {
|
return new PacketMultiMsgElement(element as SendMultiForwardMsgElement);
|
||||||
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -27,7 +27,7 @@ import {
|
|||||||
SendPicElement,
|
SendPicElement,
|
||||||
SendPttElement,
|
SendPttElement,
|
||||||
SendReplyElement,
|
SendReplyElement,
|
||||||
SendStructLongMsgElement,
|
SendMultiForwardMsgElement,
|
||||||
SendTextElement,
|
SendTextElement,
|
||||||
SendVideoElement
|
SendVideoElement
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
@@ -661,13 +661,13 @@ export class PacketMsgMarkDownElement extends IPacketMsgElement<SendMarkdownElem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMultiMsgElement extends IPacketMsgElement<SendStructLongMsgElement> {
|
export class PacketMultiMsgElement extends IPacketMsgElement<SendMultiForwardMsgElement> {
|
||||||
resid: string;
|
resid: string;
|
||||||
message: PacketMsg[];
|
message: PacketMsg[];
|
||||||
|
|
||||||
constructor(rawElement: SendStructLongMsgElement, message?: PacketMsg[]) {
|
constructor(rawElement: SendMultiForwardMsgElement, message?: PacketMsg[]) {
|
||||||
super(rawElement);
|
super(rawElement);
|
||||||
this.resid = rawElement.structLongMsgElement.resId;
|
this.resid = rawElement.multiForwardMsgElement.resId;
|
||||||
this.message = message ?? [];
|
this.message = message ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { IPacketMsgElement } from '@/core/packet/message/element';
|
import { IPacketMsgElement } from '@/core/packet/message/element';
|
||||||
import { SendMessageElement, SendStructLongMsgElement } from '@/core';
|
import {SendMessageElement, SendMultiForwardMsgElement} from '@/core';
|
||||||
|
|
||||||
export type PacketSendMsgElement = SendMessageElement | SendStructLongMsgElement
|
export type PacketSendMsgElement = SendMessageElement | SendMultiForwardMsgElement
|
||||||
|
|
||||||
export interface PacketMsg {
|
export interface PacketMsg {
|
||||||
seq?: number;
|
seq?: number;
|
||||||
|
@@ -347,6 +347,8 @@ export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & Elemen
|
|||||||
|
|
||||||
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
|
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
|
||||||
|
|
||||||
|
export type SendMultiForwardMsgElement = SendElementBase<ElementType.MULTIFORWARD> & ElementBase<'multiForwardMsgElement'>;
|
||||||
|
|
||||||
export type SendMessageElement = SendTextElement | SendPttElement |
|
export type SendMessageElement = SendTextElement | SendPttElement |
|
||||||
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
|
||||||
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
|
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import {FileNapCatOneBotUUID} from '@/common/file-uuid';
|
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||||
import {MessageUnique} from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import {
|
import {
|
||||||
ChatType,
|
ChatType,
|
||||||
CustomMusicSignPostData,
|
CustomMusicSignPostData,
|
||||||
@@ -29,22 +29,22 @@ import {
|
|||||||
OB11MessageImage,
|
OB11MessageImage,
|
||||||
OB11MessageVideo,
|
OB11MessageVideo,
|
||||||
} from '@/onebot';
|
} from '@/onebot';
|
||||||
import {OB11Construct} from '@/onebot/helper/data';
|
import { OB11Construct } from '@/onebot/helper/data';
|
||||||
import {EventType} from '@/onebot/event/OneBotEvent';
|
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||||
import {encodeCQCode} from '@/onebot/helper/cqcode';
|
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
||||||
import {uriToLocalFile} from '@/common/file';
|
import { uriToLocalFile } from '@/common/file';
|
||||||
import {RequestUtil} from '@/common/request';
|
import { RequestUtil } from '@/common/request';
|
||||||
import fsPromise, {constants} from 'node:fs/promises';
|
import fsPromise, { constants } from 'node:fs/promises';
|
||||||
import {OB11FriendAddNoticeEvent} from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||||
import {ForwardMsgBuilder} from '@/common/forward-msg-builder';
|
import { ForwardMsgBuilder } from '@/common/forward-msg-builder';
|
||||||
import {NapProtoMsg} from '@napneko/nap-proto-core';
|
import { NapProtoMsg } from '@napneko/nap-proto-core';
|
||||||
import {OB11GroupIncreaseEvent} from '../event/notice/OB11GroupIncreaseEvent';
|
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||||
import {GroupDecreaseSubType, OB11GroupDecreaseEvent} from '../event/notice/OB11GroupDecreaseEvent';
|
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
|
||||||
import {GroupAdmin} from '@/core/packet/transformer/proto/message/groupAdmin';
|
import { GroupAdmin } from '@/core/packet/transformer/proto/message/groupAdmin';
|
||||||
import {OB11GroupAdminNoticeEvent} from '../event/notice/OB11GroupAdminNoticeEvent';
|
import { OB11GroupAdminNoticeEvent } from '../event/notice/OB11GroupAdminNoticeEvent';
|
||||||
import {GroupChange, GroupChangeInfo, GroupInvite, PushMsgBody} from '@/core/packet/transformer/proto';
|
import { GroupChange, GroupChangeInfo, GroupInvite, PushMsgBody } from '@/core/packet/transformer/proto';
|
||||||
import {OB11GroupRequestEvent} from '../event/request/OB11GroupRequest';
|
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest';
|
||||||
import {LRUCache} from '@/common/lru-cache';
|
import { LRUCache } from '@/common/lru-cache';
|
||||||
|
|
||||||
type RawToOb11Converters = {
|
type RawToOb11Converters = {
|
||||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||||
@@ -93,12 +93,12 @@ export class OneBotMsgApi {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.text,
|
type: OB11MessageDataType.text,
|
||||||
data: {text},
|
data: { text },
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let qq: string = 'all';
|
let qq: string = 'all';
|
||||||
if (element.atType !== NTMsgAtType.ATTYPEALL) {
|
if (element.atType !== NTMsgAtType.ATTYPEALL) {
|
||||||
const {atNtUid, atUid} = element;
|
const { atNtUid, atUid } = element;
|
||||||
qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : atUid;
|
qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : atUid;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -206,7 +206,7 @@ export class OneBotMsgApi {
|
|||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
const {emojiId} = _;
|
const { emojiId } = _;
|
||||||
const dir = emojiId.substring(0, 2);
|
const dir = emojiId.substring(0, 2);
|
||||||
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
|
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
|
||||||
const filename = `${dir}-${emojiId}.gif`;
|
const filename = `${dir}-${emojiId}.gif`;
|
||||||
@@ -381,7 +381,7 @@ export class OneBotMsgApi {
|
|||||||
}
|
}
|
||||||
const forward: OB11MessageForward = {
|
const forward: OB11MessageForward = {
|
||||||
type: OB11MessageDataType.forward,
|
type: OB11MessageDataType.forward,
|
||||||
data: {id: msg.msgId}
|
data: { id: msg.msgId }
|
||||||
};
|
};
|
||||||
if (!context.parseMultMsg) return forward;
|
if (!context.parseMultMsg) return forward;
|
||||||
forward.data.content = await this.parseMultiMessageContent(
|
forward.data.content = await this.parseMultiMessageContent(
|
||||||
@@ -412,7 +412,7 @@ export class OneBotMsgApi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ob11ToRawConverters: Ob11ToRawConverters = {
|
ob11ToRawConverters: Ob11ToRawConverters = {
|
||||||
[OB11MessageDataType.text]: async ({data: {text}}) => ({
|
[OB11MessageDataType.text]: async ({ data: { text } }) => ({
|
||||||
elementType: ElementType.TEXT,
|
elementType: ElementType.TEXT,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
textElement: {
|
textElement: {
|
||||||
@@ -424,7 +424,7 @@ export class OneBotMsgApi {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[OB11MessageDataType.at]: async ({data: {qq: atQQ}}, context) => {
|
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
|
||||||
function at(atUid: string, atNtUid: string, atType: NTMsgAtType, atName: string): SendTextElement {
|
function at(atUid: string, atNtUid: string, atType: NTMsgAtType, atName: string): SendTextElement {
|
||||||
return {
|
return {
|
||||||
elementType: ElementType.TEXT,
|
elementType: ElementType.TEXT,
|
||||||
@@ -451,7 +451,7 @@ export class OneBotMsgApi {
|
|||||||
return at(atQQ, uid, NTMsgAtType.ATTYPEONE, info.nick || '');
|
return at(atQQ, uid, NTMsgAtType.ATTYPEONE, info.nick || '');
|
||||||
},
|
},
|
||||||
|
|
||||||
[OB11MessageDataType.reply]: async ({data: {id}}) => {
|
[OB11MessageDataType.reply]: async ({ data: { id } }) => {
|
||||||
const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
|
const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
|
||||||
if (!replyMsgM) {
|
if (!replyMsgM) {
|
||||||
this.core.context.logger.logWarn('回复消息不存在', id);
|
this.core.context.logger.logWarn('回复消息不存在', id);
|
||||||
@@ -473,7 +473,7 @@ export class OneBotMsgApi {
|
|||||||
undefined;
|
undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
[OB11MessageDataType.face]: async ({data: {id, resultId, chainCount}}) => {
|
[OB11MessageDataType.face]: async ({ data: { id, resultId, chainCount } }) => {
|
||||||
const parsedFaceId = +id;
|
const parsedFaceId = +id;
|
||||||
// 从face_config.json中获取表情名称
|
// 从face_config.json中获取表情名称
|
||||||
const sysFaces = faceConfig.sysface;
|
const sysFaces = faceConfig.sysface;
|
||||||
@@ -537,12 +537,12 @@ export class OneBotMsgApi {
|
|||||||
},
|
},
|
||||||
|
|
||||||
[OB11MessageDataType.file]: async (sendMsg, context) => {
|
[OB11MessageDataType.file]: async (sendMsg, context) => {
|
||||||
const {path, fileName} = await this.handleOb11FileLikeMessage(sendMsg, context);
|
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||||
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName);
|
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName);
|
||||||
},
|
},
|
||||||
|
|
||||||
[OB11MessageDataType.video]: async (sendMsg, context) => {
|
[OB11MessageDataType.video]: async (sendMsg, context) => {
|
||||||
const {path, fileName} = await this.handleOb11FileLikeMessage(sendMsg, context);
|
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||||
|
|
||||||
let thumb = sendMsg.data.thumb;
|
let thumb = sendMsg.data.thumb;
|
||||||
if (thumb) {
|
if (thumb) {
|
||||||
@@ -560,7 +560,7 @@ export class OneBotMsgApi {
|
|||||||
this.core.apis.FileApi.createValidSendPttElement(
|
this.core.apis.FileApi.createValidSendPttElement(
|
||||||
(await this.handleOb11FileLikeMessage(sendMsg, context)).path),
|
(await this.handleOb11FileLikeMessage(sendMsg, context)).path),
|
||||||
|
|
||||||
[OB11MessageDataType.json]: async ({data: {data}}) => ({
|
[OB11MessageDataType.json]: async ({ data: { data } }) => ({
|
||||||
elementType: ElementType.ARK,
|
elementType: ElementType.ARK,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
arkElement: {
|
arkElement: {
|
||||||
@@ -603,13 +603,13 @@ export class OneBotMsgApi {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// Need signing
|
// Need signing
|
||||||
[OB11MessageDataType.markdown]: async ({data: {content}}) => ({
|
[OB11MessageDataType.markdown]: async ({ data: { content } }) => ({
|
||||||
elementType: ElementType.MARKDOWN,
|
elementType: ElementType.MARKDOWN,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
markdownElement: {content},
|
markdownElement: { content },
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[OB11MessageDataType.music]: async ({data}, context) => {
|
[OB11MessageDataType.music]: async ({ data }, context) => {
|
||||||
// 保留, 直到...找到更好的解决方案
|
// 保留, 直到...找到更好的解决方案
|
||||||
if (data.id !== undefined) {
|
if (data.id !== undefined) {
|
||||||
if (!['qq', '163', 'kugou', 'kuwo', 'migu'].includes(data.type)) {
|
if (!['qq', '163', 'kugou', 'kuwo', 'migu'].includes(data.type)) {
|
||||||
@@ -633,8 +633,8 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
let postData: IdMusicSignPostData | CustomMusicSignPostData;
|
let postData: IdMusicSignPostData | CustomMusicSignPostData;
|
||||||
if (data.id === undefined && data.content) {
|
if (data.id === undefined && data.content) {
|
||||||
const {content, ...others} = data;
|
const { content, ...others } = data;
|
||||||
postData = {singer: content, ...others};
|
postData = { singer: content, ...others };
|
||||||
} else {
|
} else {
|
||||||
postData = data;
|
postData = data;
|
||||||
}
|
}
|
||||||
@@ -646,7 +646,7 @@ export class OneBotMsgApi {
|
|||||||
try {
|
try {
|
||||||
const musicJson = await RequestUtil.HttpGetJson<string>(signUrl, 'POST', postData);
|
const musicJson = await RequestUtil.HttpGetJson<string>(signUrl, 'POST', postData);
|
||||||
return this.ob11ToRawConverters.json({
|
return this.ob11ToRawConverters.json({
|
||||||
data: {data: musicJson},
|
data: { data: musicJson },
|
||||||
type: OB11MessageDataType.json
|
type: OB11MessageDataType.json
|
||||||
}, context);
|
}, context);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -657,10 +657,10 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
[OB11MessageDataType.node]: async () => undefined,
|
[OB11MessageDataType.node]: async () => undefined,
|
||||||
|
|
||||||
[OB11MessageDataType.forward]: async ({data}, context) => {
|
[OB11MessageDataType.forward]: async ({ data }, context) => {
|
||||||
const jsonData = ForwardMsgBuilder.fromResId(data.id);
|
const jsonData = ForwardMsgBuilder.fromResId(data.id);
|
||||||
return this.ob11ToRawConverters.json({
|
return this.ob11ToRawConverters.json({
|
||||||
data: {data: JSON.stringify(jsonData)},
|
data: { data: JSON.stringify(jsonData) },
|
||||||
type: OB11MessageDataType.json
|
type: OB11MessageDataType.json
|
||||||
}, context);
|
}, context);
|
||||||
},
|
},
|
||||||
@@ -680,17 +680,17 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
[OB11MessageDataType.miniapp]: async () => undefined,
|
[OB11MessageDataType.miniapp]: async () => undefined,
|
||||||
|
|
||||||
[OB11MessageDataType.contact]: async ({data: {type = 'qq', id}}, context) => {
|
[OB11MessageDataType.contact]: async ({ data: { type = 'qq', id } }, context) => {
|
||||||
if (type === 'qq') {
|
if (type === 'qq') {
|
||||||
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), '');
|
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), '');
|
||||||
return this.ob11ToRawConverters.json({
|
return this.ob11ToRawConverters.json({
|
||||||
data: {data: arkJson.arkMsg},
|
data: { data: arkJson.arkMsg },
|
||||||
type: OB11MessageDataType.json
|
type: OB11MessageDataType.json
|
||||||
}, context);
|
}, context);
|
||||||
} else if (type === 'group') {
|
} else if (type === 'group') {
|
||||||
const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString());
|
const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString());
|
||||||
return this.ob11ToRawConverters.json({
|
return this.ob11ToRawConverters.json({
|
||||||
data: {data: arkJson.arkJson},
|
data: { data: arkJson.arkJson },
|
||||||
type: OB11MessageDataType.json
|
type: OB11MessageDataType.json
|
||||||
}, context);
|
}, context);
|
||||||
}
|
}
|
||||||
@@ -867,7 +867,7 @@ export class OneBotMsgApi {
|
|||||||
element[key],
|
element[key],
|
||||||
msg,
|
msg,
|
||||||
element,
|
element,
|
||||||
{parseMultMsg}
|
{ parseMultMsg }
|
||||||
);
|
);
|
||||||
if (key === 'faceElement' && !parsedElement) {
|
if (key === 'faceElement' && !parsedElement) {
|
||||||
return null;
|
return null;
|
||||||
@@ -920,13 +920,13 @@ export class OneBotMsgApi {
|
|||||||
) => Promise<SendMessageElement | undefined>;
|
) => Promise<SendMessageElement | undefined>;
|
||||||
const callResult = converter(
|
const callResult = converter(
|
||||||
sendMsg,
|
sendMsg,
|
||||||
{peer, deleteAfterSentFiles},
|
{ peer, deleteAfterSentFiles },
|
||||||
)?.catch(undefined);
|
)?.catch(undefined);
|
||||||
callResultList.push(callResult);
|
callResultList.push(callResult);
|
||||||
}
|
}
|
||||||
const ret = await Promise.all(callResultList);
|
const ret = await Promise.all(callResultList);
|
||||||
const sendElements: SendMessageElement[] = ret.filter(ele => !!ele);
|
const sendElements: SendMessageElement[] = ret.filter(ele => !!ele);
|
||||||
return {sendElements, deleteAfterSentFiles};
|
return { sendElements, deleteAfterSentFiles };
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMsgWithOb11UniqueId(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[]) {
|
async sendMsgWithOb11UniqueId(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[]) {
|
||||||
@@ -937,16 +937,16 @@ export class OneBotMsgApi {
|
|||||||
const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => {
|
const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => {
|
||||||
const sizePromises = elements.map(async element => {
|
const sizePromises = elements.map(async element => {
|
||||||
switch (element.elementType) {
|
switch (element.elementType) {
|
||||||
case ElementType.PTT:
|
case ElementType.PTT:
|
||||||
return (await fsPromise.stat(element.pttElement.filePath)).size;
|
return (await fsPromise.stat(element.pttElement.filePath)).size;
|
||||||
case ElementType.FILE:
|
case ElementType.FILE:
|
||||||
return (await fsPromise.stat(element.fileElement.filePath)).size;
|
return (await fsPromise.stat(element.fileElement.filePath)).size;
|
||||||
case ElementType.VIDEO:
|
case ElementType.VIDEO:
|
||||||
return (await fsPromise.stat(element.videoElement.filePath)).size;
|
return (await fsPromise.stat(element.videoElement.filePath)).size;
|
||||||
case ElementType.PIC:
|
case ElementType.PIC:
|
||||||
return (await fsPromise.stat(element.picElement.sourcePath)).size;
|
return (await fsPromise.stat(element.picElement.sourcePath)).size;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const sizes = await Promise.all(sizePromises);
|
const sizes = await Promise.all(sizePromises);
|
||||||
@@ -988,8 +988,8 @@ export class OneBotMsgApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handleOb11FileLikeMessage(
|
private async handleOb11FileLikeMessage(
|
||||||
{data: inputdata}: OB11MessageFileBase,
|
{ data: inputdata }: OB11MessageFileBase,
|
||||||
{deleteAfterSentFiles}: SendMessageContext
|
{ deleteAfterSentFiles }: SendMessageContext
|
||||||
) {
|
) {
|
||||||
let realUri = [inputdata.url, inputdata.file, inputdata.path].find(uri => uri && uri.trim()) ?? '';
|
let realUri = [inputdata.url, inputdata.file, inputdata.path].find(uri => uri && uri.trim()) ?? '';
|
||||||
if (!realUri) {
|
if (!realUri) {
|
||||||
@@ -998,29 +998,29 @@ export class OneBotMsgApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const downloadFile = async (uri: string) => {
|
const downloadFile = async (uri: string) => {
|
||||||
const {path, fileName, errMsg, success} = await uriToLocalFile(this.core.NapCatTempPath, uri);
|
const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, uri);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
this.core.context.logger.logError('文件下载失败', errMsg);
|
this.core.context.logger.logError('文件下载失败', errMsg);
|
||||||
throw new Error('文件下载失败: ' + errMsg);
|
throw new Error('文件下载失败: ' + errMsg);
|
||||||
}
|
}
|
||||||
return {path, fileName};
|
return { path, fileName };
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const {path, fileName} = await downloadFile(realUri);
|
const { path, fileName } = await downloadFile(realUri);
|
||||||
deleteAfterSentFiles.push(path);
|
deleteAfterSentFiles.push(path);
|
||||||
return {path, fileName: inputdata.name ?? fileName};
|
return { path, fileName: inputdata.name ?? fileName };
|
||||||
} catch {
|
} catch {
|
||||||
realUri = await this.handleObfuckName(realUri);
|
realUri = await this.handleObfuckName(realUri);
|
||||||
const {path, fileName} = await downloadFile(realUri);
|
const { path, fileName } = await downloadFile(realUri);
|
||||||
deleteAfterSentFiles.push(path);
|
deleteAfterSentFiles.push(path);
|
||||||
return {path, fileName: inputdata.name ?? fileName};
|
return { path, fileName: inputdata.name ?? fileName };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleObfuckName(name: string) {
|
async handleObfuckName(name: string) {
|
||||||
const contextMsgFile = FileNapCatOneBotUUID.decode(name);
|
const contextMsgFile = FileNapCatOneBotUUID.decode(name);
|
||||||
if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
|
if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
|
||||||
const {peer, msgId, elementId} = contextMsgFile;
|
const { peer, msgId, elementId } = contextMsgFile;
|
||||||
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList.find(msg => msg.msgId === msgId);
|
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList.find(msg => msg.msgId === msgId);
|
||||||
const mixElement = rawMessage?.elements.find(e => e.elementId === elementId);
|
const mixElement = rawMessage?.elements.find(e => e.elementId === elementId);
|
||||||
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
|
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
|
||||||
@@ -1028,12 +1028,12 @@ export class OneBotMsgApi {
|
|||||||
let url = '';
|
let url = '';
|
||||||
if (mixElement?.picElement && rawMessage) {
|
if (mixElement?.picElement && rawMessage) {
|
||||||
const tempData =
|
const tempData =
|
||||||
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, {parseMultMsg: false}) as OB11MessageImage | undefined;
|
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageImage | undefined;
|
||||||
url = tempData?.data.url ?? '';
|
url = tempData?.data.url ?? '';
|
||||||
}
|
}
|
||||||
if (mixElement?.videoElement && rawMessage) {
|
if (mixElement?.videoElement && rawMessage) {
|
||||||
const tempData =
|
const tempData =
|
||||||
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, {parseMultMsg: false}) as OB11MessageVideo | undefined;
|
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageVideo | undefined;
|
||||||
url = tempData?.data.url ?? '';
|
url = tempData?.data.url ?? '';
|
||||||
}
|
}
|
||||||
return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
|
return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
|
||||||
@@ -1043,14 +1043,14 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 130:
|
case 130:
|
||||||
return 'leave';
|
return 'leave';
|
||||||
case 131:
|
case 131:
|
||||||
return 'kick';
|
return 'kick';
|
||||||
case 3:
|
case 3:
|
||||||
return 'kick_me';
|
return 'kick_me';
|
||||||
default:
|
default:
|
||||||
return 'kick';
|
return 'kick';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1069,7 +1069,7 @@ export class OneBotMsgApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}, 1, 1000).catch(undefined);
|
}, 1, 1000).catch(() => undefined);
|
||||||
if (dataNotify) {
|
if (dataNotify) {
|
||||||
return !dataNotify.actionUser.uid ? dataNotify.user2.uid : dataNotify.actionUser.uid;
|
return !dataNotify.actionUser.uid ? dataNotify.user2.uid : dataNotify.actionUser.uid;
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,8 @@ import { HttpServerConfig } from '@/onebot/config/config';
|
|||||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||||
import { IOB11NetworkAdapter } from '@/onebot/network/adapter';
|
import { IOB11NetworkAdapter } from '@/onebot/network/adapter';
|
||||||
import json5 from 'json5';
|
import json5 from 'json5';
|
||||||
|
import { isFinished } from 'on-finished';
|
||||||
|
import typeis from 'type-is';
|
||||||
export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig> {
|
export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig> {
|
||||||
private app: Express | undefined;
|
private app: Express | undefined;
|
||||||
private server: http.Server | undefined;
|
private server: http.Server | undefined;
|
||||||
@@ -45,13 +46,23 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
|
|||||||
this.app = undefined;
|
this.app = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private initializeServer() {
|
private initializeServer() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
this.server = http.createServer(this.app);
|
this.server = http.createServer(this.app);
|
||||||
|
|
||||||
this.app.use(cors());
|
this.app.use(cors());
|
||||||
this.app.use(express.urlencoded({ extended: true, limit: '5000mb' }));
|
this.app.use(express.urlencoded({ extended: true, limit: '5000mb' }));
|
||||||
|
|
||||||
this.app.use((req, res, next) => {
|
this.app.use((req, res, next) => {
|
||||||
|
if (isFinished(req)) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!typeis.hasBody(req)) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 兼容处理没有带content-type的请求
|
// 兼容处理没有带content-type的请求
|
||||||
req.headers['content-type'] = 'application/json';
|
req.headers['content-type'] = 'application/json';
|
||||||
let rawData = '';
|
let rawData = '';
|
||||||
@@ -98,7 +109,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
|
|||||||
if (req.method == 'get') {
|
if (req.method == 'get') {
|
||||||
payload = req.query;
|
payload = req.query;
|
||||||
} else if (req.query) {
|
} else if (req.query) {
|
||||||
payload = { ...req.query, ...req.body };
|
payload = { ...req.body, ...req.query };
|
||||||
}
|
}
|
||||||
if (req.path === '' || req.path === '/') {
|
if (req.path === '' || req.path === '/') {
|
||||||
const hello = OB11Response.ok({});
|
const hello = OB11Response.ok({});
|
||||||
|
@@ -28,6 +28,7 @@ export let WebUiConfig: WebUiConfigWrapper;
|
|||||||
export let webUiPathWrapper: NapCatPathWrapper;
|
export let webUiPathWrapper: NapCatPathWrapper;
|
||||||
const MAX_PORT_TRY = 100;
|
const MAX_PORT_TRY = 100;
|
||||||
import * as net from 'node:net';
|
import * as net from 'node:net';
|
||||||
|
import { WebUiDataRuntime } from './src/helper/Data';
|
||||||
|
|
||||||
export async function InitPort(parsedConfig: WebUiConfigType): Promise<[string, number, string]> {
|
export async function InitPort(parsedConfig: WebUiConfigType): Promise<[string, number, string]> {
|
||||||
try {
|
try {
|
||||||
@@ -48,7 +49,20 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
|
|||||||
logger.log('[NapCat] [WebUi] Current WebUi is not run.');
|
logger.log('[NapCat] [WebUi] Current WebUi is not run.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setTimeout(async () => {
|
||||||
|
let autoLoginAccount = process.env['NAPCAT_QUICK_ACCOUNT'] || WebUiConfig.getAutoLoginAccount();
|
||||||
|
if (autoLoginAccount) {
|
||||||
|
try {
|
||||||
|
const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
console.log(`[NapCat] [WebUi] Auto login account: ${autoLoginAccount}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`[NapCat] [WebUi] Auto login account failed.` + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
// ------------注册中间件------------
|
// ------------注册中间件------------
|
||||||
// 使用express的json中间件
|
// 使用express的json中间件
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -59,14 +73,32 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
|
|||||||
|
|
||||||
// 如果是webui字体文件,挂载字体文件
|
// 如果是webui字体文件,挂载字体文件
|
||||||
app.use('/webui/fonts/AaCute.woff', async (_req, res, next) => {
|
app.use('/webui/fonts/AaCute.woff', async (_req, res, next) => {
|
||||||
const isFontExist = await WebUiConfigWrapper.CheckWebUIFontExist();
|
const isFontExist = await WebUiConfig.CheckWebUIFontExist();
|
||||||
if (isFontExist) {
|
if (isFontExist) {
|
||||||
res.sendFile(WebUiConfigWrapper.GetWebUIFontPath());
|
res.sendFile(WebUiConfig.GetWebUIFontPath());
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 如果是自定义色彩,构建一个css文件
|
||||||
|
app.use('/files/theme.css', async (_req, res) => {
|
||||||
|
const colors = await WebUiConfig.GetTheme();
|
||||||
|
|
||||||
|
let css = ':root, .light, [data-theme="light"] {';
|
||||||
|
for (const key in colors.light) {
|
||||||
|
css += `${key}: ${colors.light[key]};`;
|
||||||
|
}
|
||||||
|
css += '}';
|
||||||
|
css += '.dark, [data-theme="dark"] {';
|
||||||
|
for (const key in colors.dark) {
|
||||||
|
css += `${key}: ${colors.dark[key]};`;
|
||||||
|
}
|
||||||
|
css += '}';
|
||||||
|
|
||||||
|
res.send(css);
|
||||||
|
});
|
||||||
|
|
||||||
// ------------中间件结束------------
|
// ------------中间件结束------------
|
||||||
|
|
||||||
// ------------挂载路由------------
|
// ------------挂载路由------------
|
||||||
|
@@ -2,14 +2,25 @@ import { RequestHandler } from 'express';
|
|||||||
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
|
|
||||||
import { sendSuccess } from '@webapi/utils/response';
|
import { sendSuccess } from '@webapi/utils/response';
|
||||||
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
export const PackageInfoHandler: RequestHandler = (_, res) => {
|
export const PackageInfoHandler: RequestHandler = (_, res) => {
|
||||||
const data = WebUiDataRuntime.getPackageJson();
|
const data = WebUiDataRuntime.getPackageJson();
|
||||||
sendSuccess(res, data);
|
sendSuccess(res, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const QQVersionHandler: RequestHandler = (_, res) => {
|
export const QQVersionHandler: RequestHandler = (_, res) => {
|
||||||
const data = WebUiDataRuntime.getQQVersion();
|
const data = WebUiDataRuntime.getQQVersion();
|
||||||
sendSuccess(res, data);
|
sendSuccess(res, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GetThemeConfigHandler: RequestHandler = async (_, res) => {
|
||||||
|
const data = await WebUiConfig.GetTheme();
|
||||||
|
sendSuccess(res, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SetThemeConfigHandler: RequestHandler = async (req, res) => {
|
||||||
|
const { theme } = req.body;
|
||||||
|
await WebUiConfig.UpdateTheme(theme);
|
||||||
|
sendSuccess(res, { message: '更新成功' });
|
||||||
|
};
|
||||||
|
@@ -7,9 +7,9 @@ import os from 'os';
|
|||||||
import compressing from 'compressing';
|
import compressing from 'compressing';
|
||||||
import { PassThrough } from 'stream';
|
import { PassThrough } from 'stream';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import { WebUiConfigWrapper } from '../helper/config';
|
|
||||||
import webUIFontUploader from '../uploader/webui_font';
|
import webUIFontUploader from '../uploader/webui_font';
|
||||||
import diskUploader from '../uploader/disk';
|
import diskUploader from '../uploader/disk';
|
||||||
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
const isWindows = os.platform() === 'win32';
|
const isWindows = os.platform() === 'win32';
|
||||||
|
|
||||||
@@ -384,8 +384,8 @@ export const UploadWebUIFontHandler: RequestHandler = async (req, res) => {
|
|||||||
// 删除WebUI字体文件处理方法
|
// 删除WebUI字体文件处理方法
|
||||||
export const DeleteWebUIFontHandler: RequestHandler = async (_req, res) => {
|
export const DeleteWebUIFontHandler: RequestHandler = async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const fontPath = WebUiConfigWrapper.GetWebUIFontPath();
|
const fontPath = WebUiConfig.GetWebUIFontPath();
|
||||||
const exists = await WebUiConfigWrapper.CheckWebUIFontExist();
|
const exists = await WebUiConfig.CheckWebUIFontExist();
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return sendSuccess(res, true);
|
return sendSuccess(res, true);
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import type { RequestHandler } from 'express';
|
import type { RequestHandler } from 'express';
|
||||||
import { sendError, sendSuccess } from '../utils/response';
|
import { sendError, sendSuccess } from '../utils/response';
|
||||||
import { WebUiConfigWrapper } from '../helper/config';
|
|
||||||
import { logSubscription } from '@/common/log';
|
import { logSubscription } from '@/common/log';
|
||||||
import { terminalManager } from '../terminal/terminal_manager';
|
import { terminalManager } from '../terminal/terminal_manager';
|
||||||
|
import { WebUiConfig } from '@/webui';
|
||||||
// 判断是否是 macos
|
// 判断是否是 macos
|
||||||
const isMacOS = process.platform === 'darwin';
|
const isMacOS = process.platform === 'darwin';
|
||||||
// 日志记录
|
// 日志记录
|
||||||
@@ -15,13 +15,13 @@ export const LogHandler: RequestHandler = async (req, res) => {
|
|||||||
if (filename.includes('..')) {
|
if (filename.includes('..')) {
|
||||||
return sendError(res, 'ID不合法');
|
return sendError(res, 'ID不合法');
|
||||||
}
|
}
|
||||||
const logContent = await WebUiConfigWrapper.GetLogContent(filename);
|
const logContent = await WebUiConfig.GetLogContent(filename);
|
||||||
return sendSuccess(res, logContent);
|
return sendSuccess(res, logContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 日志列表
|
// 日志列表
|
||||||
export const LogListHandler: RequestHandler = async (_, res) => {
|
export const LogListHandler: RequestHandler = async (_, res) => {
|
||||||
const logList = await WebUiConfigWrapper.GetLogsList();
|
const logList = await WebUiConfig.GetLogsList();
|
||||||
return sendSuccess(res, logList);
|
return sendSuccess(res, logList);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -3,9 +3,10 @@ import { RequestHandler } from 'express';
|
|||||||
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
import { WebUiDataRuntime } from '@webapi/helper/Data';
|
||||||
import { isEmpty } from '@webapi/utils/check';
|
import { isEmpty } from '@webapi/utils/check';
|
||||||
import { sendError, sendSuccess } from '@webapi/utils/response';
|
import { sendError, sendSuccess } from '@webapi/utils/response';
|
||||||
|
import { WebUiConfig } from '@/webui';
|
||||||
|
|
||||||
// 获取QQ登录二维码
|
// 获取QQ登录二维码
|
||||||
export const QQGetQRcodeHandler: RequestHandler = async (req, res) => {
|
export const QQGetQRcodeHandler: RequestHandler = async (_, res) => {
|
||||||
// 判断是否已经登录
|
// 判断是否已经登录
|
||||||
if (WebUiDataRuntime.getQQLoginStatus()) {
|
if (WebUiDataRuntime.getQQLoginStatus()) {
|
||||||
// 已经登录
|
// 已经登录
|
||||||
@@ -25,7 +26,7 @@ export const QQGetQRcodeHandler: RequestHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 获取QQ登录状态
|
// 获取QQ登录状态
|
||||||
export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => {
|
export const QQCheckLoginStatusHandler: RequestHandler = async (_, res) => {
|
||||||
const data = {
|
const data = {
|
||||||
isLogin: WebUiDataRuntime.getQQLoginStatus(),
|
isLogin: WebUiDataRuntime.getQQLoginStatus(),
|
||||||
qrcodeurl: WebUiDataRuntime.getQQLoginQrcodeURL(),
|
qrcodeurl: WebUiDataRuntime.getQQLoginQrcodeURL(),
|
||||||
@@ -74,3 +75,16 @@ export const getQQLoginInfoHandler: RequestHandler = async (_, res) => {
|
|||||||
const data = WebUiDataRuntime.getQQLoginInfo();
|
const data = WebUiDataRuntime.getQQLoginInfo();
|
||||||
return sendSuccess(res, data);
|
return sendSuccess(res, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取自动登录QQ账号
|
||||||
|
export const getAutoLoginAccountHandler: RequestHandler = async (_, res) => {
|
||||||
|
const data = WebUiConfig.getAutoLoginAccount();
|
||||||
|
return sendSuccess(res, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置自动登录QQ账号
|
||||||
|
export const setAutoLoginAccountHandler: RequestHandler = async (req, res) => {
|
||||||
|
const { uin } = req.body;
|
||||||
|
await WebUiConfig.UpdateAutoLoginAccount(uin);
|
||||||
|
return sendSuccess(res, null);
|
||||||
|
};
|
||||||
|
@@ -5,6 +5,9 @@ import fs, { constants } from 'node:fs/promises';
|
|||||||
|
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
import { deepMerge } from '../utils/object';
|
||||||
|
import { themeType } from '../types/theme';
|
||||||
|
|
||||||
// 限制尝试端口的次数,避免死循环
|
// 限制尝试端口的次数,避免死循环
|
||||||
|
|
||||||
// 定义配置的类型
|
// 定义配置的类型
|
||||||
@@ -13,11 +16,12 @@ const WebUiConfigSchema = Type.Object({
|
|||||||
port: Type.Number({ default: 6099 }),
|
port: Type.Number({ default: 6099 }),
|
||||||
token: Type.String({ default: 'napcat' }),
|
token: Type.String({ default: 'napcat' }),
|
||||||
loginRate: Type.Number({ default: 10 }),
|
loginRate: Type.Number({ default: 10 }),
|
||||||
|
autoLoginAccount: Type.String({ default: '' }),
|
||||||
|
theme: themeType,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type WebUiConfigType = Static<typeof WebUiConfigSchema>;
|
export type WebUiConfigType = Static<typeof WebUiConfigSchema>;
|
||||||
|
|
||||||
|
|
||||||
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
// 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件
|
||||||
export class WebUiConfigWrapper {
|
export class WebUiConfigWrapper {
|
||||||
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
WebUiConfigData: WebUiConfigType | undefined = undefined;
|
||||||
@@ -28,7 +32,10 @@ export class WebUiConfigWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async ensureConfigFileExists(configPath: string): Promise<void> {
|
private async ensureConfigFileExists(configPath: string): Promise<void> {
|
||||||
const configExists = await fs.access(configPath, constants.F_OK).then(() => true).catch(() => false);
|
const configExists = await fs
|
||||||
|
.access(configPath, constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
if (!configExists) {
|
if (!configExists) {
|
||||||
await fs.writeFile(configPath, JSON.stringify(this.validateAndApplyDefaults({}), null, 4));
|
await fs.writeFile(configPath, JSON.stringify(this.validateAndApplyDefaults({}), null, 4));
|
||||||
}
|
}
|
||||||
@@ -40,7 +47,10 @@ export class WebUiConfigWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async writeConfig(configPath: string, config: WebUiConfigType): Promise<void> {
|
private async writeConfig(configPath: string, config: WebUiConfigType): Promise<void> {
|
||||||
const hasWritePermission = await fs.access(configPath, constants.W_OK).then(() => true).catch(() => false);
|
const hasWritePermission = await fs
|
||||||
|
.access(configPath, constants.W_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
if (hasWritePermission) {
|
if (hasWritePermission) {
|
||||||
await fs.writeFile(configPath, JSON.stringify(config, null, 4));
|
await fs.writeFile(configPath, JSON.stringify(config, null, 4));
|
||||||
} else {
|
} else {
|
||||||
@@ -67,7 +77,8 @@ export class WebUiConfigWrapper {
|
|||||||
async UpdateWebUIConfig(newConfig: Partial<WebUiConfigType>): Promise<void> {
|
async UpdateWebUIConfig(newConfig: Partial<WebUiConfigType>): Promise<void> {
|
||||||
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
|
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
|
||||||
const currentConfig = await this.GetWebUIConfig();
|
const currentConfig = await this.GetWebUIConfig();
|
||||||
const updatedConfig = this.validateAndApplyDefaults({ ...currentConfig, ...newConfig });
|
const mergedConfig = deepMerge({ ...currentConfig }, newConfig);
|
||||||
|
const updatedConfig = this.validateAndApplyDefaults(mergedConfig);
|
||||||
await this.writeConfig(configPath, updatedConfig);
|
await this.writeConfig(configPath, updatedConfig);
|
||||||
this.WebUiConfigData = updatedConfig;
|
this.WebUiConfigData = updatedConfig;
|
||||||
}
|
}
|
||||||
@@ -81,24 +92,32 @@ export class WebUiConfigWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取日志文件夹路径
|
// 获取日志文件夹路径
|
||||||
public static async GetLogsPath(): Promise<string> {
|
async GetLogsPath(): Promise<string> {
|
||||||
return resolve(webUiPathWrapper.logsPath);
|
return resolve(webUiPathWrapper.logsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取日志列表
|
// 获取日志列表
|
||||||
public static async GetLogsList(): Promise<string[]> {
|
async GetLogsList(): Promise<string[]> {
|
||||||
const logsPath = resolve(webUiPathWrapper.logsPath);
|
const logsPath = resolve(webUiPathWrapper.logsPath);
|
||||||
const logsExist = await fs.access(logsPath, constants.F_OK).then(() => true).catch(() => false);
|
const logsExist = await fs
|
||||||
|
.access(logsPath, constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
if (logsExist) {
|
if (logsExist) {
|
||||||
return (await fs.readdir(logsPath)).filter(file => file.endsWith('.log')).map(file => file.replace('.log', ''));
|
return (await fs.readdir(logsPath))
|
||||||
|
.filter((file) => file.endsWith('.log'))
|
||||||
|
.map((file) => file.replace('.log', ''));
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取指定日志文件内容
|
// 获取指定日志文件内容
|
||||||
public static async GetLogContent(filename: string): Promise<string> {
|
async GetLogContent(filename: string): Promise<string> {
|
||||||
const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`);
|
const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`);
|
||||||
const logExists = await fs.access(logPath, constants.R_OK).then(() => true).catch(() => false);
|
const logExists = await fs
|
||||||
|
.access(logPath, constants.R_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
if (logExists) {
|
if (logExists) {
|
||||||
return await fs.readFile(logPath, 'utf-8');
|
return await fs.readFile(logPath, 'utf-8');
|
||||||
}
|
}
|
||||||
@@ -106,23 +125,55 @@ export class WebUiConfigWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取字体文件夹内的字体列表
|
// 获取字体文件夹内的字体列表
|
||||||
public static async GetFontList(): Promise<string[]> {
|
async GetFontList(): Promise<string[]> {
|
||||||
const fontsPath = resolve(webUiPathWrapper.configPath, './fonts');
|
const fontsPath = resolve(webUiPathWrapper.configPath, './fonts');
|
||||||
const fontsExist = await fs.access(fontsPath, constants.F_OK).then(() => true).catch(() => false);
|
const fontsExist = await fs
|
||||||
|
.access(fontsPath, constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
if (fontsExist) {
|
if (fontsExist) {
|
||||||
return (await fs.readdir(fontsPath)).filter(file => file.endsWith('.ttf'));
|
return (await fs.readdir(fontsPath)).filter((file) => file.endsWith('.ttf'));
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断字体是否存在(webui.woff)
|
// 判断字体是否存在(webui.woff)
|
||||||
public static async CheckWebUIFontExist(): Promise<boolean> {
|
async CheckWebUIFontExist(): Promise<boolean> {
|
||||||
const fontsPath = resolve(webUiPathWrapper.configPath, './fonts');
|
const fontsPath = resolve(webUiPathWrapper.configPath, './fonts');
|
||||||
return await fs.access(resolve(fontsPath, './webui.woff'), constants.F_OK).then(() => true).catch(() => false);
|
return await fs
|
||||||
|
.access(resolve(fontsPath, './webui.woff'), constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取webui字体文件路径
|
// 获取webui字体文件路径
|
||||||
public static GetWebUIFontPath(): string {
|
GetWebUIFontPath(): string {
|
||||||
return resolve(webUiPathWrapper.configPath, './fonts/webui.woff');
|
return resolve(webUiPathWrapper.configPath, './fonts/webui.woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAutoLoginAccount(): string | undefined {
|
||||||
|
return this.WebUiConfigData?.autoLoginAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取自动登录账号
|
||||||
|
async GetAutoLoginAccount(): Promise<string> {
|
||||||
|
return (await this.GetWebUIConfig()).autoLoginAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新自动登录账号
|
||||||
|
async UpdateAutoLoginAccount(uin: string): Promise<void> {
|
||||||
|
await this.UpdateWebUIConfig({ autoLoginAccount: uin });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取主题内容
|
||||||
|
async GetTheme(): Promise<WebUiConfigType['theme']> {
|
||||||
|
const config = await this.GetWebUIConfig();
|
||||||
|
|
||||||
|
return config.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新主题内容
|
||||||
|
async UpdateTheme(theme: WebUiConfigType['theme']): Promise<void> {
|
||||||
|
await this.UpdateWebUIConfig({ theme: theme });
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { PackageInfoHandler, QQVersionHandler } from '../api/BaseInfo';
|
import { GetThemeConfigHandler, PackageInfoHandler, QQVersionHandler, SetThemeConfigHandler } from '../api/BaseInfo';
|
||||||
import { StatusRealTimeHandler } from '@webapi/api/Status';
|
import { StatusRealTimeHandler } from '@webapi/api/Status';
|
||||||
import { GetProxyHandler } from '../api/Proxy';
|
import { GetProxyHandler } from '../api/Proxy';
|
||||||
|
|
||||||
@@ -9,4 +9,7 @@ router.get('/QQVersion', QQVersionHandler);
|
|||||||
router.get('/PackageInfo', PackageInfoHandler);
|
router.get('/PackageInfo', PackageInfoHandler);
|
||||||
router.get('/GetSysStatusRealTime', StatusRealTimeHandler);
|
router.get('/GetSysStatusRealTime', StatusRealTimeHandler);
|
||||||
router.get('/proxy', GetProxyHandler);
|
router.get('/proxy', GetProxyHandler);
|
||||||
|
router.get('/Theme', GetThemeConfigHandler);
|
||||||
|
router.post('/SetTheme', SetThemeConfigHandler);
|
||||||
|
|
||||||
export { router as BaseRouter };
|
export { router as BaseRouter };
|
||||||
|
@@ -7,6 +7,8 @@ import {
|
|||||||
QQSetQuickLoginHandler,
|
QQSetQuickLoginHandler,
|
||||||
QQGetLoginListNewHandler,
|
QQGetLoginListNewHandler,
|
||||||
getQQLoginInfoHandler,
|
getQQLoginInfoHandler,
|
||||||
|
getAutoLoginAccountHandler,
|
||||||
|
setAutoLoginAccountHandler,
|
||||||
} from '@webapi/api/QQLogin';
|
} from '@webapi/api/QQLogin';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@@ -22,5 +24,9 @@ router.post('/GetQQLoginQrcode', QQGetQRcodeHandler);
|
|||||||
router.post('/SetQuickLogin', QQSetQuickLoginHandler);
|
router.post('/SetQuickLogin', QQSetQuickLoginHandler);
|
||||||
// router:获取QQ登录信息
|
// router:获取QQ登录信息
|
||||||
router.post('/GetQQLoginInfo', getQQLoginInfoHandler);
|
router.post('/GetQQLoginInfo', getQQLoginInfoHandler);
|
||||||
|
// router:获取快速登录QQ账号
|
||||||
|
router.post('/GetQuickLoginQQ', getAutoLoginAccountHandler);
|
||||||
|
// router:设置自动登录QQ账号
|
||||||
|
router.post('/SetQuickLoginQQ', setAutoLoginAccountHandler);
|
||||||
|
|
||||||
export { router as QQLoginRouter };
|
export { router as QQLoginRouter };
|
||||||
|
260
src/webui/src/types/theme.ts
Normal file
260
src/webui/src/types/theme.ts
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
import { Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
|
export const themeType = Type.Object(
|
||||||
|
{
|
||||||
|
dark: Type.Record(Type.String(), Type.String()),
|
||||||
|
light: Type.Record(Type.String(), Type.String()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: {
|
||||||
|
dark: {
|
||||||
|
'--heroui-background': '0 0% 0%',
|
||||||
|
'--heroui-foreground-50': '240 5.88% 10%',
|
||||||
|
'--heroui-foreground-100': '240 3.7% 15.88%',
|
||||||
|
'--heroui-foreground-200': '240 5.26% 26.08%',
|
||||||
|
'--heroui-foreground-300': '240 5.2% 33.92%',
|
||||||
|
'--heroui-foreground-400': '240 3.83% 46.08%',
|
||||||
|
'--heroui-foreground-500': '240 5.03% 64.9%',
|
||||||
|
'--heroui-foreground-600': '240 4.88% 83.92%',
|
||||||
|
'--heroui-foreground-700': '240 5.88% 90%',
|
||||||
|
'--heroui-foreground-800': '240 4.76% 95.88%',
|
||||||
|
'--heroui-foreground-900': '0 0% 98.04%',
|
||||||
|
'--heroui-foreground': '210 5.56% 92.94%',
|
||||||
|
'--heroui-focus': '212.01999999999998 100% 46.67%',
|
||||||
|
'--heroui-overlay': '0 0% 0%',
|
||||||
|
'--heroui-divider': '0 0% 100%',
|
||||||
|
'--heroui-divider-opacity': '0.15',
|
||||||
|
'--heroui-content1': '240 5.88% 10%',
|
||||||
|
'--heroui-content1-foreground': '0 0% 98.04%',
|
||||||
|
'--heroui-content2': '240 3.7% 15.88%',
|
||||||
|
'--heroui-content2-foreground': '240 4.76% 95.88%',
|
||||||
|
'--heroui-content3': '240 5.26% 26.08%',
|
||||||
|
'--heroui-content3-foreground': '240 5.88% 90%',
|
||||||
|
'--heroui-content4': '240 5.2% 33.92%',
|
||||||
|
'--heroui-content4-foreground': '240 4.88% 83.92%',
|
||||||
|
'--heroui-default-50': '240 5.88% 10%',
|
||||||
|
'--heroui-default-100': '240 3.7% 15.88%',
|
||||||
|
'--heroui-default-200': '240 5.26% 26.08%',
|
||||||
|
'--heroui-default-300': '240 5.2% 33.92%',
|
||||||
|
'--heroui-default-400': '240 3.83% 46.08%',
|
||||||
|
'--heroui-default-500': '240 5.03% 64.9%',
|
||||||
|
'--heroui-default-600': '240 4.88% 83.92%',
|
||||||
|
'--heroui-default-700': '240 5.88% 90%',
|
||||||
|
'--heroui-default-800': '240 4.76% 95.88%',
|
||||||
|
'--heroui-default-900': '0 0% 98.04%',
|
||||||
|
'--heroui-default-foreground': '0 0% 100%',
|
||||||
|
'--heroui-default': '240 5.26% 26.08%',
|
||||||
|
'--heroui-danger-50': '301.89 82.61% 22.55%',
|
||||||
|
'--heroui-danger-100': '308.18 76.39% 28.24%',
|
||||||
|
'--heroui-danger-200': '313.85 70.65% 36.08%',
|
||||||
|
'--heroui-danger-300': '319.73 65.64% 44.51%',
|
||||||
|
'--heroui-danger-400': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-danger-500': '331.82 75% 65.49%',
|
||||||
|
'--heroui-danger-600': '337.84 83.46% 73.92%',
|
||||||
|
'--heroui-danger-700': '343.42 90.48% 83.53%',
|
||||||
|
'--heroui-danger-800': '350.53 90.48% 91.76%',
|
||||||
|
'--heroui-danger-900': '324 90.91% 95.69%',
|
||||||
|
'--heroui-danger-foreground': '0 0% 100%',
|
||||||
|
'--heroui-danger': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-primary-50': '340 84.91% 10.39%',
|
||||||
|
'--heroui-primary-100': '339.33 86.54% 20.39%',
|
||||||
|
'--heroui-primary-200': '339.11 85.99% 30.78%',
|
||||||
|
'--heroui-primary-300': '339 86.54% 40.78%',
|
||||||
|
'--heroui-primary-400': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-primary-500': '339 90% 60.78%',
|
||||||
|
'--heroui-primary-600': '339.11 90.6% 70.78%',
|
||||||
|
'--heroui-primary-700': '339.33 90% 80.39%',
|
||||||
|
'--heroui-primary-800': '340 91.84% 90.39%',
|
||||||
|
'--heroui-primary-900': '339.13 92% 95.1%',
|
||||||
|
'--heroui-primary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-primary': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-secondary-50': '270 66.67% 9.41%',
|
||||||
|
'--heroui-secondary-100': '270 66.67% 18.82%',
|
||||||
|
'--heroui-secondary-200': '270 66.67% 28.24%',
|
||||||
|
'--heroui-secondary-300': '270 66.67% 37.65%',
|
||||||
|
'--heroui-secondary-400': '270 66.67% 47.06%',
|
||||||
|
'--heroui-secondary-500': '270 59.26% 57.65%',
|
||||||
|
'--heroui-secondary-600': '270 59.26% 68.24%',
|
||||||
|
'--heroui-secondary-700': '270 59.26% 78.82%',
|
||||||
|
'--heroui-secondary-800': '270 59.26% 89.41%',
|
||||||
|
'--heroui-secondary-900': '270 61.54% 94.9%',
|
||||||
|
'--heroui-secondary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-secondary': '270 59.26% 57.65%',
|
||||||
|
'--heroui-success-50': '145.71 77.78% 8.82%',
|
||||||
|
'--heroui-success-100': '146.2 79.78% 17.45%',
|
||||||
|
'--heroui-success-200': '145.79 79.26% 26.47%',
|
||||||
|
'--heroui-success-300': '146.01 79.89% 35.1%',
|
||||||
|
'--heroui-success-400': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-success-500': '146.01 62.45% 55.1%',
|
||||||
|
'--heroui-success-600': '145.79 62.57% 66.47%',
|
||||||
|
'--heroui-success-700': '146.2 61.74% 77.45%',
|
||||||
|
'--heroui-success-800': '145.71 61.4% 88.82%',
|
||||||
|
'--heroui-success-900': '146.67 64.29% 94.51%',
|
||||||
|
'--heroui-success-foreground': '0 0% 0%',
|
||||||
|
'--heroui-success': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-warning-50': '37.14 75% 10.98%',
|
||||||
|
'--heroui-warning-100': '37.14 75% 21.96%',
|
||||||
|
'--heroui-warning-200': '36.96 73.96% 33.14%',
|
||||||
|
'--heroui-warning-300': '37.01 74.22% 44.12%',
|
||||||
|
'--heroui-warning-400': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-warning-500': '37.01 91.26% 64.12%',
|
||||||
|
'--heroui-warning-600': '36.96 91.24% 73.14%',
|
||||||
|
'--heroui-warning-700': '37.14 91.3% 81.96%',
|
||||||
|
'--heroui-warning-800': '37.14 91.3% 90.98%',
|
||||||
|
'--heroui-warning-900': '54.55 91.67% 95.29%',
|
||||||
|
'--heroui-warning-foreground': '0 0% 0%',
|
||||||
|
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-code-background': '240 5.56% 7.06%',
|
||||||
|
'--heroui-strong': '190.14 94.67% 44.12%',
|
||||||
|
'--heroui-code-mdx': '190.14 94.67% 44.12%',
|
||||||
|
'--heroui-divider-weight': '1px',
|
||||||
|
'--heroui-disabled-opacity': '.5',
|
||||||
|
'--heroui-font-size-tiny': '0.75rem',
|
||||||
|
'--heroui-font-size-small': '0.875rem',
|
||||||
|
'--heroui-font-size-medium': '1rem',
|
||||||
|
'--heroui-font-size-large': '1.125rem',
|
||||||
|
'--heroui-line-height-tiny': '1rem',
|
||||||
|
'--heroui-line-height-small': '1.25rem',
|
||||||
|
'--heroui-line-height-medium': '1.5rem',
|
||||||
|
'--heroui-line-height-large': '1.75rem',
|
||||||
|
'--heroui-radius-small': '8px',
|
||||||
|
'--heroui-radius-medium': '12px',
|
||||||
|
'--heroui-radius-large': '14px',
|
||||||
|
'--heroui-border-width-small': '1px',
|
||||||
|
'--heroui-border-width-medium': '2px',
|
||||||
|
'--heroui-border-width-large': '3px',
|
||||||
|
'--heroui-box-shadow-small':
|
||||||
|
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-box-shadow-medium':
|
||||||
|
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-box-shadow-large':
|
||||||
|
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
|
||||||
|
'--heroui-hover-opacity': '.9',
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
'--heroui-background': '0 0% 100%',
|
||||||
|
'--heroui-foreground-50': '240 5.88% 95%',
|
||||||
|
'--heroui-foreground-100': '240 3.7% 90%',
|
||||||
|
'--heroui-foreground-200': '240 5.26% 80%',
|
||||||
|
'--heroui-foreground-300': '240 5.2% 70%',
|
||||||
|
'--heroui-foreground-400': '240 3.83% 60%',
|
||||||
|
'--heroui-foreground-500': '240 5.03% 50%',
|
||||||
|
'--heroui-foreground-600': '240 4.88% 40%',
|
||||||
|
'--heroui-foreground-700': '240 5.88% 30%',
|
||||||
|
'--heroui-foreground-800': '240 4.76% 20%',
|
||||||
|
'--heroui-foreground-900': '0 0% 10%',
|
||||||
|
'--heroui-foreground': '210 5.56% 7.06%',
|
||||||
|
'--heroui-focus': '212.01999999999998 100% 53.33%',
|
||||||
|
'--heroui-overlay': '0 0% 100%',
|
||||||
|
'--heroui-divider': '0 0% 0%',
|
||||||
|
'--heroui-divider-opacity': '0.85',
|
||||||
|
'--heroui-content1': '240 5.88% 95%',
|
||||||
|
'--heroui-content1-foreground': '0 0% 10%',
|
||||||
|
'--heroui-content2': '240 3.7% 90%',
|
||||||
|
'--heroui-content2-foreground': '240 4.76% 20%',
|
||||||
|
'--heroui-content3': '240 5.26% 80%',
|
||||||
|
'--heroui-content3-foreground': '240 5.88% 30%',
|
||||||
|
'--heroui-content4': '240 5.2% 70%',
|
||||||
|
'--heroui-content4-foreground': '240 4.88% 40%',
|
||||||
|
'--heroui-default-50': '240 5.88% 95%',
|
||||||
|
'--heroui-default-100': '240 3.7% 90%',
|
||||||
|
'--heroui-default-200': '240 5.26% 80%',
|
||||||
|
'--heroui-default-300': '240 5.2% 70%',
|
||||||
|
'--heroui-default-400': '240 3.83% 60%',
|
||||||
|
'--heroui-default-500': '240 5.03% 50%',
|
||||||
|
'--heroui-default-600': '240 4.88% 40%',
|
||||||
|
'--heroui-default-700': '240 5.88% 30%',
|
||||||
|
'--heroui-default-800': '240 4.76% 20%',
|
||||||
|
'--heroui-default-900': '0 0% 10%',
|
||||||
|
'--heroui-default-foreground': '0 0% 0%',
|
||||||
|
'--heroui-default': '240 5.26% 80%',
|
||||||
|
'--heroui-danger-50': '324 90.91% 95.69%',
|
||||||
|
'--heroui-danger-100': '350.53 90.48% 91.76%',
|
||||||
|
'--heroui-danger-200': '343.42 90.48% 83.53%',
|
||||||
|
'--heroui-danger-300': '337.84 83.46% 73.92%',
|
||||||
|
'--heroui-danger-400': '331.82 75% 65.49%',
|
||||||
|
'--heroui-danger-500': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-danger-600': '319.73 65.64% 44.51%',
|
||||||
|
'--heroui-danger-700': '313.85 70.65% 36.08%',
|
||||||
|
'--heroui-danger-800': '308.18 76.39% 28.24%',
|
||||||
|
'--heroui-danger-900': '301.89 82.61% 22.55%',
|
||||||
|
'--heroui-danger-foreground': '0 0% 100%',
|
||||||
|
'--heroui-danger': '325.82 69.62% 53.53%',
|
||||||
|
'--heroui-primary-50': '339.13 92% 95.1%',
|
||||||
|
'--heroui-primary-100': '340 91.84% 90.39%',
|
||||||
|
'--heroui-primary-200': '339.33 90% 80.39%',
|
||||||
|
'--heroui-primary-300': '339.11 90.6% 70.78%',
|
||||||
|
'--heroui-primary-400': '339 90% 60.78%',
|
||||||
|
'--heroui-primary-500': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-primary-600': '339 86.54% 40.78%',
|
||||||
|
'--heroui-primary-700': '339.11 85.99% 30.78%',
|
||||||
|
'--heroui-primary-800': '339.33 86.54% 20.39%',
|
||||||
|
'--heroui-primary-900': '340 84.91% 10.39%',
|
||||||
|
'--heroui-primary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-primary': '339.2 90.36% 51.18%',
|
||||||
|
'--heroui-secondary-50': '270 61.54% 94.9%',
|
||||||
|
'--heroui-secondary-100': '270 59.26% 89.41%',
|
||||||
|
'--heroui-secondary-200': '270 59.26% 78.82%',
|
||||||
|
'--heroui-secondary-300': '270 59.26% 68.24%',
|
||||||
|
'--heroui-secondary-400': '270 59.26% 57.65%',
|
||||||
|
'--heroui-secondary-500': '270 66.67% 47.06%',
|
||||||
|
'--heroui-secondary-600': '270 66.67% 37.65%',
|
||||||
|
'--heroui-secondary-700': '270 66.67% 28.24%',
|
||||||
|
'--heroui-secondary-800': '270 66.67% 18.82%',
|
||||||
|
'--heroui-secondary-900': '270 66.67% 9.41%',
|
||||||
|
'--heroui-secondary-foreground': '0 0% 100%',
|
||||||
|
'--heroui-secondary': '270 66.67% 47.06%',
|
||||||
|
'--heroui-success-50': '146.67 64.29% 94.51%',
|
||||||
|
'--heroui-success-100': '145.71 61.4% 88.82%',
|
||||||
|
'--heroui-success-200': '146.2 61.74% 77.45%',
|
||||||
|
'--heroui-success-300': '145.79 62.57% 66.47%',
|
||||||
|
'--heroui-success-400': '146.01 62.45% 55.1%',
|
||||||
|
'--heroui-success-500': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-success-600': '146.01 79.89% 35.1%',
|
||||||
|
'--heroui-success-700': '145.79 79.26% 26.47%',
|
||||||
|
'--heroui-success-800': '146.2 79.78% 17.45%',
|
||||||
|
'--heroui-success-900': '145.71 77.78% 8.82%',
|
||||||
|
'--heroui-success-foreground': '0 0% 0%',
|
||||||
|
'--heroui-success': '145.96 79.46% 43.92%',
|
||||||
|
'--heroui-warning-50': '54.55 91.67% 95.29%',
|
||||||
|
'--heroui-warning-100': '37.14 91.3% 90.98%',
|
||||||
|
'--heroui-warning-200': '37.14 91.3% 81.96%',
|
||||||
|
'--heroui-warning-300': '36.96 91.24% 73.14%',
|
||||||
|
'--heroui-warning-400': '37.01 91.26% 64.12%',
|
||||||
|
'--heroui-warning-500': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-warning-600': '37.01 74.22% 44.12%',
|
||||||
|
'--heroui-warning-700': '36.96 73.96% 33.14%',
|
||||||
|
'--heroui-warning-800': '37.14 75% 21.96%',
|
||||||
|
'--heroui-warning-900': '37.14 75% 10.98%',
|
||||||
|
'--heroui-warning-foreground': '0 0% 0%',
|
||||||
|
'--heroui-warning': '37.03 91.27% 55.1%',
|
||||||
|
'--heroui-code-background': '221.25 17.39% 18.04%',
|
||||||
|
'--heroui-strong': '316.95 100% 65.29%',
|
||||||
|
'--heroui-code-mdx': '316.95 100% 65.29%',
|
||||||
|
'--heroui-divider-weight': '1px',
|
||||||
|
'--heroui-disabled-opacity': '.5',
|
||||||
|
'--heroui-font-size-tiny': '0.75rem',
|
||||||
|
'--heroui-font-size-small': '0.875rem',
|
||||||
|
'--heroui-font-size-medium': '1rem',
|
||||||
|
'--heroui-font-size-large': '1.125rem',
|
||||||
|
'--heroui-line-height-tiny': '1rem',
|
||||||
|
'--heroui-line-height-small': '1.25rem',
|
||||||
|
'--heroui-line-height-medium': '1.5rem',
|
||||||
|
'--heroui-line-height-large': '1.75rem',
|
||||||
|
'--heroui-radius-small': '8px',
|
||||||
|
'--heroui-radius-medium': '12px',
|
||||||
|
'--heroui-radius-large': '14px',
|
||||||
|
'--heroui-border-width-small': '1px',
|
||||||
|
'--heroui-border-width-medium': '2px',
|
||||||
|
'--heroui-border-width-large': '3px',
|
||||||
|
'--heroui-box-shadow-small':
|
||||||
|
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-box-shadow-medium':
|
||||||
|
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-box-shadow-large':
|
||||||
|
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
|
||||||
|
'--heroui-hover-opacity': '.8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
22
src/webui/src/utils/object.ts
Normal file
22
src/webui/src/utils/object.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
||||||
|
for (const key in source) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||||
|
// 如果 source[key] 为 undefined,则跳过(保留 target[key])
|
||||||
|
if (source[key] === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
target[key] !== undefined &&
|
||||||
|
typeof target[key] === 'object' &&
|
||||||
|
!Array.isArray(target[key]) &&
|
||||||
|
typeof source[key] === 'object' &&
|
||||||
|
!Array.isArray(source[key])
|
||||||
|
) {
|
||||||
|
target[key] = deepMerge({ ...target[key] }, source[key]!) as T[Extract<keyof T, string>];
|
||||||
|
} else {
|
||||||
|
target[key] = source[key]! as T[Extract<keyof T, string>];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
Reference in New Issue
Block a user