Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
wangsen
MinerU
Commits
791e653c
Commit
791e653c
authored
Sep 19, 2024
by
dechen lin
Browse files
feat: add web project
parent
9d689790
Changes
381
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
731 additions
and
0 deletions
+731
-0
projects/web/src/pages/extract/index.module.scss
projects/web/src/pages/extract/index.module.scss
+0
-0
projects/web/src/pages/extract/index.tsx
projects/web/src/pages/extract/index.tsx
+0
-0
projects/web/src/pages/extract/table/index.tsx
projects/web/src/pages/extract/table/index.tsx
+5
-0
projects/web/src/pages/extract/table/table-detail/index.tsx
projects/web/src/pages/extract/table/table-detail/index.tsx
+5
-0
projects/web/src/pages/home.module.scss
projects/web/src/pages/home.module.scss
+0
-0
projects/web/src/pages/home.tsx
projects/web/src/pages/home.tsx
+79
-0
projects/web/src/routes/index.tsx
projects/web/src/routes/index.tsx
+17
-0
projects/web/src/store/jobProgress.ts
projects/web/src/store/jobProgress.ts
+180
-0
projects/web/src/store/languageStore.ts
projects/web/src/store/languageStore.ts
+47
-0
projects/web/src/store/mdStore.ts
projects/web/src/store/mdStore.ts
+196
-0
projects/web/src/styles/variable.scss
projects/web/src/styles/variable.scss
+1
-0
projects/web/src/types/extract-task-type.ts
projects/web/src/types/extract-task-type.ts
+21
-0
projects/web/src/utils/download.ts
projects/web/src/utils/download.ts
+44
-0
projects/web/src/utils/locale.ts
projects/web/src/utils/locale.ts
+16
-0
projects/web/src/utils/windowOpen.ts
projects/web/src/utils/windowOpen.ts
+24
-0
projects/web/src/vite-env.d.ts
projects/web/src/vite-env.d.ts
+1
-0
projects/web/tailwind.config.js
projects/web/tailwind.config.js
+35
-0
projects/web/tsconfig.app.json
projects/web/tsconfig.app.json
+27
-0
projects/web/tsconfig.json
projects/web/tsconfig.json
+11
-0
projects/web/tsconfig.node.json
projects/web/tsconfig.node.json
+22
-0
No files found.
projects/web/src/pages/extract/index.module.scss
0 → 100644
View file @
791e653c
projects/web/src/pages/extract/index.tsx
0 → 100644
View file @
791e653c
projects/web/src/pages/extract/table/index.tsx
0 → 100644
View file @
791e653c
const
ExtractorTable
=
()
=>
{
return
<>
ExtractorTable
</>;
};
export
default
ExtractorTable
;
projects/web/src/pages/extract/table/table-detail/index.tsx
0 → 100644
View file @
791e653c
const
TableDetail
=
()
=>
{
return
<>
TableDetail
</>;
};
export
default
TableDetail
;
projects/web/src/pages/home.module.scss
0 → 100644
View file @
791e653c
projects/web/src/pages/home.tsx
0 → 100644
View file @
791e653c
"
use client
"
;
import
ErrorBoundary
from
"
@/components/error-boundary
"
;
import
styles
from
"
./home.module.scss
"
;
import
{
SlotID
,
Path
}
from
"
@/constant/route
"
;
import
{
BrowserRouter
,
Routes
,
Route
,
Outlet
}
from
"
react-router-dom
"
;
import
{
ExtractorSide
}
from
"
./extract-side
"
;
import
{
LanguageProvider
}
from
"
@/context/language-provider
"
;
import
PDFUpload
from
"
@/pages/extract/components/pdf-upload
"
;
import
PDFExtractionJob
from
"
@/pages/extract/components/pdf-extraction
"
;
// judge if the app has hydrated
// const useHasHydrated = () => {
// const [hasHydrated, setHasHydrated] = useState<boolean>(false);
// useEffect(() => {
// setHasHydrated(true);
// }, []);
// return hasHydrated;
// };
export
function
WindowContent
()
{
const
isHome
=
location
.
pathname
===
Path
.
Home
;
return
(
<>
<
ExtractorSide
className
=
{
isHome
?
styles
[
"
sidebar-show
"
]
:
""
}
/>
<
div
className
=
"flex-1"
>
<
Outlet
/>
</
div
>
</>
);
}
function
Screen
()
{
// if you do not need to use the renderContent for rendering router, you can use the other render function to interrupt before the renderContent
const
renderContent
=
()
=>
{
return
(
<
div
className
=
"w-full h-full flex"
id
=
{
SlotID
.
AppBody
}
>
<
Routes
>
<
Route
path
=
"/"
element
=
{
<
WindowContent
/>
}
>
<
Route
path
=
"/OpenSourceTools/Extractor/PDF"
element
=
{
<
PDFUpload
/>
}
/>
<
Route
path
=
"/OpenSourceTools/Extractor/PDF/:jobID"
element
=
{
<
PDFExtractionJob
/>
}
/>
{
/* <Route path="*" element={<PDFUpload />} /> */
}
</
Route
>
</
Routes
>
</
div
>
// <ExtractorSide className={isHome ? styles["sidebar-show"] : ""} />
// <WindowContent className="flex-1">
// <AppRoutes />
// </WindowContent>
);
};
return
<>
{
renderContent
()
}
</>;
}
export
function
Home
()
{
// leave this comment to check if the app has hydrated
// if (!useHasHydrated()) {
// return <LoadingAnimation />;
// }
return
(
<
ErrorBoundary
>
<
LanguageProvider
>
<
BrowserRouter
>
<
Screen
/>
</
BrowserRouter
>
</
LanguageProvider
>
</
ErrorBoundary
>
);
}
projects/web/src/routes/index.tsx
0 → 100644
View file @
791e653c
import
{
Routes
,
Route
}
from
"
react-router-dom
"
;
import
PDFUpload
from
"
@/pages/extract/components/pdf-upload
"
;
import
PDFExtractionJob
from
"
@/pages/extract/components/pdf-extraction
"
;
function
AppRoutes
()
{
return
(
<>
<
Route
path
=
"/OpenSourceTools/Extractor/PDF"
element
=
{
<
PDFUpload
/>
}
/>
<
Route
path
=
"/OpenSourceTools/Extractor/PDF/:jobID"
element
=
{
<
PDFExtractionJob
/>
}
/>
</>
);
}
export
default
AppRoutes
;
projects/web/src/store/jobProgress.ts
0 → 100644
View file @
791e653c
import
{
getExtractTaskIdProgress
,
getPdfExtractQueue
,
TaskIdResItem
,
}
from
"
@/api/extract
"
;
import
{
create
}
from
"
zustand
"
;
import
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
"
react
"
;
import
{
useParams
}
from
"
react-router-dom
"
;
import
{
UPDATE_TASK_LIST
}
from
"
@/constant/event
"
;
import
{
useQuery
}
from
"
@tanstack/react-query
"
;
interface
ExtractorState
{
taskInfo
:
TaskIdResItem
;
queueLoading
:
boolean
|
null
;
interfaceError
:
boolean
;
setTaskInfo
:
(
taskInfo
:
TaskIdResItem
)
=>
void
;
setQueueLoading
:
(
loading
:
boolean
|
null
)
=>
void
;
setInterfaceError
:
(
error
:
boolean
)
=>
void
;
}
const
defaultTaskInfo
:
TaskIdResItem
=
{
id
:
0
,
rank
:
0
,
state
:
"
pending
"
,
url
:
""
,
type
:
"
unknown
"
,
queues
:
-
1
,
};
const
useExtractorStore
=
create
<
ExtractorState
>
((
set
)
=>
({
taskInfo
:
defaultTaskInfo
,
queueLoading
:
null
,
interfaceError
:
false
,
setTaskInfo
:
(
taskInfo
:
any
)
=>
set
({
taskInfo
}),
setQueueLoading
:
(
loading
)
=>
set
({
queueLoading
:
loading
}),
setInterfaceError
:
(
error
)
=>
set
({
interfaceError
:
error
}),
}));
export
const
useJobExtraction
=
()
=>
{
const
{
jobID
}
=
useParams
<
{
jobID
:
string
}
>
();
const
{
setTaskInfo
,
setQueueLoading
,
queueLoading
,
taskInfo
,
interfaceError
,
setInterfaceError
,
}
=
useExtractorStore
();
const
timeoutRef
=
useRef
<
NodeJS
.
Timeout
|
null
>
(
null
);
const
[
isPolling
,
setIsPolling
]
=
useState
(
true
);
const
stopTaskLoading
=
()
=>
{
setQueueLoading
(
false
);
};
// Query for task progress
const
taskProgressQuery
=
useQuery
({
queryKey
:
[
"
taskProgress
"
,
jobID
],
queryFn
:
()
=>
{
setQueueLoading
(
true
);
setIsPolling
(
true
);
return
getExtractTaskIdProgress
(
jobID
!
)
.
then
((
res
)
=>
{
if
(
res
?.
state
===
"
done
"
||
res
?.
state
===
"
failed
"
)
{
stopTaskLoading
();
document
.
dispatchEvent
(
new
CustomEvent
(
"
UPDATE_TASK_LIST
"
,
{
detail
:
{
state
:
res
.
state
,
id
:
jobID
},
})
);
}
if
(
res
)
{
setTaskInfo
(
res
);
}
return
res
;
})
.
catch
(()
=>
{
stopTaskLoading
();
setTaskInfo
({
state
:
"
failed
"
});
});
},
enabled
:
false
,
});
// Query for queue status
const
queueStatusQuery
=
useQuery
({
queryKey
:
[
"
queueStatus
"
,
jobID
],
queryFn
:
async
()
=>
{
setQueueLoading
(
true
);
const
response
=
await
getPdfExtractQueue
(
jobID
).
then
((
res
)
=>
{
// setTaskInfo({ rand: "failed" });
if
(
res
)
{
const
targetPendingRunningJob
=
res
?.
filter
(
(
i
)
=>
String
(
i
.
id
)
===
jobID
)?.[
0
];
if
(
targetPendingRunningJob
)
{
setTaskInfo
(
targetPendingRunningJob
);
}
else
{
setIsPolling
(
false
);
setQueueLoading
(
false
);
}
}
return
res
;
});
return
response
;
},
enabled
:
isPolling
&&
(
taskProgressQuery
?.
data
?.
state
===
"
running
"
||
taskProgressQuery
?.
data
?.
state
===
"
pending
"
),
refetchInterval
:
2000
,
// Poll every 2 seconds
});
useEffect
(()
=>
{
if
(
taskProgressQuery
.
data
?.
state
===
"
done
"
)
{
stopTaskLoading
();
setInterfaceError
(
false
);
setIsPolling
(
false
);
if
(
timeoutRef
.
current
)
{
clearTimeout
(
timeoutRef
.
current
);
}
else
{
timeoutRef
.
current
=
setTimeout
(()
=>
{
document
.
dispatchEvent
(
new
CustomEvent
(
UPDATE_TASK_LIST
,
{
detail
:
{
state
:
"
done
"
,
jobID
},
})
);
},
10
);
}
}
else
if
(
taskProgressQuery
.
data
?.
state
===
"
failed
"
)
{
stopTaskLoading
();
setInterfaceError
(
true
);
setIsPolling
(
false
);
if
(
timeoutRef
.
current
)
{
clearTimeout
(
timeoutRef
.
current
);
}
else
{
timeoutRef
.
current
=
setTimeout
(()
=>
{
document
.
dispatchEvent
(
new
CustomEvent
(
UPDATE_TASK_LIST
,
{
detail
:
{
state
:
"
failed
"
,
jobID
},
})
);
},
10
);
}
}
// TIP这里得用taskInfo
},
[
taskProgressQuery
.
data
]);
const
refreshQueue
=
()
=>
{
// stop last ID polling
setIsPolling
(
false
);
setTaskInfo
(
defaultTaskInfo
);
taskProgressQuery
.
refetch
();
};
useEffect
(()
=>
{
if
(
jobID
)
{
// stop last ID polling d
setTaskInfo
(
defaultTaskInfo
);
taskProgressQuery
.
refetch
();
}
},
[
jobID
]);
return
{
taskInfo
:
taskInfo
,
isLoading
:
queueLoading
,
isError
:
interfaceError
||
taskProgressQuery
.
isError
||
queueStatusQuery
.
isError
,
refreshQueue
,
};
};
projects/web/src/store/languageStore.ts
0 → 100644
View file @
791e653c
import
{
create
}
from
"
zustand
"
;
import
{
Language
}
from
"
@/constant
"
;
import
{
LOCALE_STORAGE_KEY
}
from
"
@/constant/storage
"
;
type
LanguageType
=
(
typeof
Language
)[
keyof
typeof
Language
];
type
LanguageStore
=
{
language
:
LanguageType
;
setLanguage
:
(
language
:
LanguageType
)
=>
void
;
toggleLanguage
:
()
=>
void
;
};
const
getInitialLanguage
=
():
LanguageType
=>
{
// Try to get language setting from localStorage
const
savedLanguage
=
localStorage
.
getItem
(
LOCALE_STORAGE_KEY
)
as
LanguageType
;
if
(
savedLanguage
&&
Object
.
values
(
Language
).
includes
(
savedLanguage
))
{
return
savedLanguage
;
}
// If no valid language setting in localStorage, try to get browser language
const
browserLanguage
=
navigator
.
language
.
toLowerCase
();
if
(
browserLanguage
.
startsWith
(
"
zh
"
))
{
return
Language
.
ZH_CN
;
}
else
if
(
browserLanguage
.
startsWith
(
"
en
"
))
{
return
Language
.
EN_US
;
}
// Default to Chinese
return
Language
.
ZH_CN
;
};
export
const
useLanguageStore
=
create
<
LanguageStore
>
((
set
)
=>
({
language
:
getInitialLanguage
(),
setLanguage
:
(
language
)
=>
{
localStorage
.
setItem
(
LOCALE_STORAGE_KEY
,
language
);
set
({
language
});
},
toggleLanguage
:
()
=>
set
((
state
)
=>
{
const
newLanguage
=
state
.
language
===
Language
.
ZH_CN
?
Language
.
EN_US
:
Language
.
ZH_CN
;
localStorage
.
setItem
(
LOCALE_STORAGE_KEY
,
newLanguage
);
return
{
language
:
newLanguage
};
}),
}));
projects/web/src/store/mdStore.ts
0 → 100644
View file @
791e653c
// mdStore.ts
import
{
create
}
from
"
zustand
"
;
import
axios
from
"
axios
"
;
interface
MdContent
{
content
:
string
;
isLoading
:
boolean
;
}
type
AnchorType
=
|
"
span
"
|
"
div
"
|
"
comment
"
|
"
data-attribute
"
|
"
hr
"
|
"
mark
"
|
"
p
"
;
interface
AnchorOptions
{
type
:
AnchorType
;
prefix
?:
string
;
style
?:
string
;
className
?:
string
;
customAttributes
?:
Record
<
string
,
string
>
;
}
const
defaultAnchorOptions
:
AnchorOptions
=
{
type
:
"
span
"
,
prefix
:
"
md-anchor-
"
,
style
:
"
display:none;
"
,
className
:
""
,
customAttributes
:
{},
};
interface
MdState
{
mdContents
:
Record
<
string
,
MdContent
>
;
allMdContent
:
string
;
allMdContentWithAnchor
:
string
;
error
:
Error
|
null
;
currentRequestId
:
number
;
setMdUrlArr
:
(
urls
:
string
[])
=>
Promise
<
void
>
;
getAllMdContent
:
(
data
:
string
[])
=>
string
;
setAllMdContent
:
(
val
?:
string
)
=>
void
;
setAllMdContentWithAnchor
:
(
val
?:
string
)
=>
void
;
getContentWithAnchors
:
(
data
:
string
[],
options
?:
Partial
<
AnchorOptions
>
)
=>
string
;
jumpToAnchor
:
(
anchorId
:
string
)
=>
number
;
reset
:
()
=>
void
;
}
const
MAX_CONCURRENT_REQUESTS
=
2
;
const
initialState
=
{
mdContents
:
{},
allMdContent
:
""
,
allMdContentWithAnchor
:
""
,
error
:
null
,
currentRequestId
:
0
,
};
const
useMdStore
=
create
<
MdState
>
((
set
,
get
)
=>
({
...
initialState
,
reset
:
()
=>
{
set
(
initialState
);
},
setAllMdContent
:
(
value
?:
string
)
=>
{
set
(()
=>
({
allMdContent
:
value
,
}));
},
setAllMdContentWithAnchor
:
(
value
?:
string
)
=>
{
set
(()
=>
({
allMdContentWithAnchor
:
value
,
}));
},
setMdUrlArr
:
async
(
urls
:
string
[])
=>
{
const
requestId
=
get
().
currentRequestId
+
1
;
set
((
state
)
=>
({
currentRequestId
:
requestId
,
error
:
null
}));
const
fetchContent
=
async
(
url
:
string
):
Promise
<
[
string
,
string
]
>
=>
{
try
{
const
response
=
await
axios
.
get
<
string
>
(
url
);
return
[
url
,
response
.
data
];
}
catch
(
error
)
{
if
(
get
().
currentRequestId
===
requestId
)
{
set
((
state
)
=>
({
error
:
error
as
Error
}));
}
return
[
url
,
""
];
}
};
const
fetchWithConcurrency
=
async
(
urls
:
string
[]
):
Promise
<
[
string
,
string
][]
>
=>
{
const
queue
=
[...
urls
];
const
results
:
[
string
,
string
][]
=
[];
const
inProgress
=
new
Set
<
Promise
<
[
string
,
string
]
>>
();
while
(
queue
.
length
>
0
||
inProgress
.
size
>
0
)
{
while
(
inProgress
.
size
<
MAX_CONCURRENT_REQUESTS
&&
queue
.
length
>
0
)
{
const
url
=
queue
.
shift
()
!
;
const
promise
=
fetchContent
(
url
);
inProgress
.
add
(
promise
);
promise
.
then
((
result
)
=>
{
results
.
push
(
result
);
inProgress
.
delete
(
promise
);
});
}
if
(
inProgress
.
size
>
0
)
{
await
Promise
.
race
(
inProgress
);
}
}
return
results
;
};
const
results
=
await
fetchWithConcurrency
(
urls
);
// 只有当这是最新的请求时,才更新状态
if
(
get
().
currentRequestId
===
requestId
)
{
const
newMdContents
:
Record
<
string
,
MdContent
>
=
{};
results
.
forEach
(([
url
,
content
])
=>
{
newMdContents
[
url
]
=
{
content
,
isLoading
:
false
};
});
set
((
state
)
=>
({
mdContents
:
newMdContents
,
allMdContent
:
state
.
getAllMdContent
(
results
.
map
((
i
)
=>
i
[
1
])),
allMdContentWithAnchor
:
state
.
getContentWithAnchors
(
results
.
map
((
i
)
=>
i
[
1
])
),
}));
}
},
getAllMdContent
:
(
data
)
=>
{
return
data
?.
join
(
"
\n\n
"
);
},
getContentWithAnchors
:
(
data
:
string
[],
options
?:
Partial
<
AnchorOptions
>
)
=>
{
const
opts
=
{
...
defaultAnchorOptions
,
...
options
};
const
generateAnchorTag
=
(
index
:
number
)
=>
{
const
id
=
`
${
opts
.
prefix
}${
index
}
`
;
const
attributes
=
Object
.
entries
(
opts
.
customAttributes
||
{})
.
map
(([
key
,
value
])
=>
`
${
key
}
="
${
value
}
"`
)
.
join
(
"
"
);
switch
(
opts
.
type
)
{
case
"
span
"
:
case
"
div
"
:
case
"
mark
"
:
case
"
p
"
:
return
`<
${
opts
.
type
}
id="
${
id
}
" style="
${
opts
.
style
}
" class="
${
opts
.
className
}
"
${
attributes
}
></
${
opts
.
type
}
>`
;
case
"
comment
"
:
return
`<!-- anchor:
${
id
}
-->`
;
case
"
data-attribute
"
:
return
`<span data-anchor="
${
id
}
" style="
${
opts
.
style
}
" class="
${
opts
.
className
}
"
${
attributes
}
></span>`
;
case
"
hr
"
:
return
`<hr id="
${
id
}
" style="
${
opts
.
style
}
" class="
${
opts
.
className
}
"
${
attributes
}
>`
;
default
:
return
`<span id="
${
id
}
" style="
${
opts
.
style
}
" class="
${
opts
.
className
}
"
${
attributes
}
></span>`
;
}
};
return
data
?.
map
((
content
,
index
)
=>
{
const
anchorTag
=
generateAnchorTag
(
index
);
return
`
${
anchorTag
}
\n\n
${
content
}
`
;
})
.
join
(
"
\n\n
"
);
},
jumpToAnchor
:
(
anchorId
:
string
)
=>
{
const
{
mdContents
}
=
get
();
const
contentArray
=
Object
.
values
(
mdContents
).
map
(
(
content
)
=>
content
.
content
);
let
totalLength
=
0
;
for
(
let
i
=
0
;
i
<
contentArray
.
length
;
i
++
)
{
if
(
anchorId
===
`md-anchor-
${
i
}
`
)
{
return
totalLength
;
}
totalLength
+=
contentArray
[
i
].
length
+
2
;
// +2 for "\n\n"
}
return
-
1
;
// Anchor not found
},
}));
export
default
useMdStore
;
projects/web/src/styles/variable.scss
0 → 100644
View file @
791e653c
$page-min-witch
:
1260px
;
\ No newline at end of file
projects/web/src/types/extract-task-type.ts
0 → 100644
View file @
791e653c
export
type
ExtractTaskType
=
|
"
pdf
"
|
"
formula-detect
"
|
"
formula-extract
"
|
"
table-recogn
"
;
export
const
EXTRACTOR_TYPE_LIST
=
{
table
:
"
table
"
,
formula
:
"
formula
"
,
pdf
:
"
PDF
"
,
};
export
enum
FORMULA_TYPE
{
extract
=
"
extract
"
,
detect
=
"
detect
"
,
}
export
enum
MD_PREVIEW_TYPE
{
preview
=
"
preview
"
,
code
=
"
code
"
,
}
projects/web/src/utils/download.ts
0 → 100644
View file @
791e653c
export
async
function
downloadFileUseAScript
(
url
:
string
,
filename
?:
string
):
Promise
<
void
>
{
try
{
// 发起请求获取文件
const
response
=
await
fetch
(
url
);
if
(
!
response
.
ok
)
{
throw
new
Error
(
`HTTP error! status:
${
response
.
status
}
`
);
}
// 获取文件内容的 Blob
const
blob
=
await
response
.
blob
();
// 创建一个 Blob URL
const
blobUrl
=
window
.
URL
.
createObjectURL
(
blob
);
// 创建一个隐藏的<a>元素
const
link
=
document
.
createElement
(
"
a
"
);
link
.
style
.
display
=
"
none
"
;
link
.
href
=
blobUrl
;
// 设置下载的文件名
const
contentDisposition
=
response
.
headers
.
get
(
"
Content-Disposition
"
);
const
fileName
=
filename
||
(
contentDisposition
?
contentDisposition
.
split
(
"
filename=
"
)[
1
].
replace
(
/
[
'"
]
/g
,
""
)
:
url
.
split
(
"
/
"
).
pop
()
||
"
download
"
);
link
.
download
=
fileName
;
// 将链接添加到文档中并触发点击
document
.
body
.
appendChild
(
link
);
link
.
click
();
// 清理
document
.
body
.
removeChild
(
link
);
window
.
URL
.
revokeObjectURL
(
blobUrl
);
}
catch
(
error
)
{
console
.
error
(
"
Download failed:
"
,
error
);
}
}
projects/web/src/utils/locale.ts
0 → 100644
View file @
791e653c
export
enum
ELocal
{
"
zh-CN
"
=
"
zh-CN
"
,
"
en-US
"
=
"
en-US
"
,
}
export
const
locale
:
{
[
key
:
string
]:
string
}
=
{
[
ELocal
[
"
zh-CN
"
]]:
"
中文
"
,
[
ELocal
[
"
en-US
"
]]:
"
En
"
,
};
export
const
localeName
:
{
[
key
:
string
]:
string
}
=
{
[
ELocal
[
"
zh-CN
"
]]:
"
nameZh
"
,
[
ELocal
[
"
en-US
"
]]:
"
name
"
,
};
export
const
getLocale
=
()
=>
{
return
localStorage
.
getItem
(
"
umi_locale
"
)
||
ELocal
[
"
zh-CN
"
];
};
projects/web/src/utils/windowOpen.ts
0 → 100644
View file @
791e653c
export
const
windowOpen
=
(
url
:
string
,
type
?:
"
_blank
"
|
"
_parent
"
|
"
_self
"
|
"
_top
"
)
=>
{
const
a
=
document
.
createElement
(
"
a
"
);
a
.
setAttribute
(
"
href
"
,
url
);
a
.
setAttribute
(
"
target
"
,
type
||
"
_blank
"
);
a
.
rel
=
"
noreferrer
"
;
document
.
body
.
appendChild
(
a
);
if
(
a
.
click
)
{
a
?.
click
();
}
else
{
try
{
let
evt
=
new
Event
(
"
click
"
,
{
bubbles
:
false
,
cancelable
:
true
,
});
a
.
dispatchEvent
(
evt
);
}
catch
(
error
)
{
window
.
open
(
url
,
type
||
"
_blank
"
);
}
}
document
.
body
.
removeChild
(
a
);
};
projects/web/src/vite-env.d.ts
0 → 100644
View file @
791e653c
/// <reference types="vite/client" />
projects/web/tailwind.config.js
0 → 100644
View file @
791e653c
module
.
exports
=
{
content
:
[
"
./src/**/*.{js,jsx,ts,tsx}
"
,
],
plugins
:
[
function
({
addUtilities
})
{
const
newUtilities
=
{
'
.scrollbar-thin
'
:
{
scrollbarWidth
:
'
2px
'
,
// scrollbarColor: 'rgba(13, 83, 222, 1)',
'
&::-webkit-scrollbar
'
:
{
width
:
'
6px
'
,
height
:
'
6px
'
},
'
&::-webkit-scrollbar-track
'
:
{
backgroundColor
:
'
transparent
'
},
'
&::-webkit-scrollbar-thumb
'
:
{
// backgroundColor: 'rgba(13, 83, 222, 0.01)',
borderRadius
:
'
20px
'
,
border
:
'
3px solid transparent
'
},
'
&:hover::-webkit-scrollbar-thumb
'
:
{
width
:
'
6px
'
,
border
:
'
3px solid rgb(229 231 235)
'
,
backgroundColor
:
'
rgb(229 231 235)
'
}
}
// 你可以添加更多自定义的滚动条样式
};
addUtilities
(
newUtilities
,
[
'
responsive
'
,
'
hover
'
]);
},
],
// ...other configurations
}
\ No newline at end of file
projects/web/tsconfig.app.json
0 → 100644
View file @
791e653c
{
"compilerOptions"
:
{
"target"
:
"ES2020"
,
"useDefineForClassFields"
:
true
,
"lib"
:
[
"ES2020"
,
"DOM"
,
"DOM.Iterable"
],
"module"
:
"ESNext"
,
"skipLibCheck"
:
true
,
/*
Bundler
mode
*/
"moduleResolution"
:
"bundler"
,
"allowImportingTsExtensions"
:
true
,
"isolatedModules"
:
true
,
"moduleDetection"
:
"force"
,
"noEmit"
:
true
,
"jsx"
:
"react-jsx"
,
/*
Linting
*/
"strict"
:
true
,
"noUnusedLocals"
:
true
,
"noUnusedParameters"
:
true
,
"noFallthroughCasesInSwitch"
:
true
,
"paths"
:
{
"@/*"
:
[
"./src/*"
]
}
},
"include"
:
[
"src"
]
}
projects/web/tsconfig.json
0 → 100644
View file @
791e653c
{
"files"
:
[],
"compilerOptions"
:
{
//
...
other
options
...
"types"
:
[
"node"
]
},
"references"
:
[
{
"path"
:
"./tsconfig.app.json"
},
{
"path"
:
"./tsconfig.node.json"
}
]
}
projects/web/tsconfig.node.json
0 → 100644
View file @
791e653c
{
"compilerOptions"
:
{
"target"
:
"ES2022"
,
"lib"
:
[
"ES2023"
],
"module"
:
"ESNext"
,
"skipLibCheck"
:
true
,
/*
Bundler
mode
*/
"moduleResolution"
:
"bundler"
,
"allowImportingTsExtensions"
:
true
,
"isolatedModules"
:
true
,
"moduleDetection"
:
"force"
,
"noEmit"
:
true
,
/*
Linting
*/
"strict"
:
true
,
"noUnusedLocals"
:
true
,
"noUnusedParameters"
:
true
,
"noFallthroughCasesInSwitch"
:
true
},
"include"
:
[
"vite.config.ts"
]
}
Prev
1
…
15
16
17
18
19
20
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment