mirror of
https://github.com/m1ngsama/FUJI.git
synced 2025-12-25 02:56:38 +00:00
add export dialog
This commit is contained in:
parent
6a99120e14
commit
74c95626c8
5 changed files with 132 additions and 16 deletions
|
|
@ -24,6 +24,7 @@
|
||||||
"@fullcalendar/react": "^6.1.17",
|
"@fullcalendar/react": "^6.1.17",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@heroui/react": "2.7.6",
|
"@heroui/react": "2.7.6",
|
||||||
|
"@internationalized/date": "^3.8.1",
|
||||||
"@logto/browser": "^2.2.18",
|
"@logto/browser": "^2.2.18",
|
||||||
"@react-stately/data": "^3.13.0",
|
"@react-stately/data": "^3.13.0",
|
||||||
"@stylistic/eslint-plugin": "^2.13.0",
|
"@stylistic/eslint-plugin": "^2.13.0",
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ importers:
|
||||||
'@heroui/react':
|
'@heroui/react':
|
||||||
specifier: 2.7.6
|
specifier: 2.7.6
|
||||||
version: 2.7.6(@types/react@18.3.20)(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17)
|
version: 2.7.6(@types/react@18.3.20)(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17)
|
||||||
|
'@internationalized/date':
|
||||||
|
specifier: ^3.8.1
|
||||||
|
version: 3.8.1
|
||||||
'@logto/browser':
|
'@logto/browser':
|
||||||
specifier: ^2.2.18
|
specifier: ^2.2.18
|
||||||
version: 2.2.18
|
version: 2.2.18
|
||||||
|
|
@ -1468,8 +1471,8 @@ packages:
|
||||||
'@internationalized/date@3.7.0':
|
'@internationalized/date@3.7.0':
|
||||||
resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==}
|
resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==}
|
||||||
|
|
||||||
'@internationalized/date@3.8.0':
|
'@internationalized/date@3.8.1':
|
||||||
resolution: {integrity: sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==}
|
resolution: {integrity: sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==}
|
||||||
|
|
||||||
'@internationalized/message@3.1.7':
|
'@internationalized/message@3.1.7':
|
||||||
resolution: {integrity: sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg==}
|
resolution: {integrity: sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg==}
|
||||||
|
|
@ -7404,7 +7407,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
|
|
||||||
'@internationalized/date@3.8.0':
|
'@internationalized/date@3.8.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
|
|
||||||
|
|
@ -7522,7 +7525,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-aria/calendar@3.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/calendar@3.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.1
|
||||||
'@react-aria/i18n': 3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/i18n': 3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-aria/interactions': 3.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/interactions': 3.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-aria/live-announcer': 3.4.2
|
'@react-aria/live-announcer': 3.4.2
|
||||||
|
|
@ -7574,7 +7577,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-aria/datepicker@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/datepicker@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.1
|
||||||
'@internationalized/number': 3.6.1
|
'@internationalized/number': 3.6.1
|
||||||
'@internationalized/string': 3.2.6
|
'@internationalized/string': 3.2.6
|
||||||
'@react-aria/focus': 3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/focus': 3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
@ -7666,7 +7669,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-aria/i18n@3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/i18n@3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.8.0
|
'@internationalized/date': 3.8.1
|
||||||
'@internationalized/message': 3.1.7
|
'@internationalized/message': 3.1.7
|
||||||
'@internationalized/number': 3.6.1
|
'@internationalized/number': 3.6.1
|
||||||
'@internationalized/string': 3.2.6
|
'@internationalized/string': 3.2.6
|
||||||
|
|
@ -7679,7 +7682,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-aria/i18n@3.12.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/i18n@3.12.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.8.0
|
'@internationalized/date': 3.8.1
|
||||||
'@internationalized/message': 3.1.7
|
'@internationalized/message': 3.1.7
|
||||||
'@internationalized/number': 3.6.1
|
'@internationalized/number': 3.6.1
|
||||||
'@internationalized/string': 3.2.6
|
'@internationalized/string': 3.2.6
|
||||||
|
|
@ -8115,7 +8118,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-stately/calendar@3.7.1(react@18.3.1)':
|
'@react-stately/calendar@3.7.1(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.1
|
||||||
'@react-stately/utils': 3.10.5(react@18.3.1)
|
'@react-stately/utils': 3.10.5(react@18.3.1)
|
||||||
'@react-types/calendar': 3.6.1(react@18.3.1)
|
'@react-types/calendar': 3.6.1(react@18.3.1)
|
||||||
'@react-types/shared': 3.28.0(react@18.3.1)
|
'@react-types/shared': 3.28.0(react@18.3.1)
|
||||||
|
|
@ -8164,7 +8167,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-stately/datepicker@3.13.0(react@18.3.1)':
|
'@react-stately/datepicker@3.13.0(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.1
|
||||||
'@internationalized/string': 3.2.6
|
'@internationalized/string': 3.2.6
|
||||||
'@react-stately/form': 3.1.3(react@18.3.1)
|
'@react-stately/form': 3.1.3(react@18.3.1)
|
||||||
'@react-stately/overlays': 3.6.15(react@18.3.1)
|
'@react-stately/overlays': 3.6.15(react@18.3.1)
|
||||||
|
|
@ -8400,13 +8403,13 @@ snapshots:
|
||||||
|
|
||||||
'@react-types/calendar@3.6.1(react@18.3.1)':
|
'@react-types/calendar@3.6.1(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.1
|
||||||
'@react-types/shared': 3.28.0(react@18.3.1)
|
'@react-types/shared': 3.28.0(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
'@react-types/calendar@3.7.0(react@18.3.1)':
|
'@react-types/calendar@3.7.0(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.8.0
|
'@internationalized/date': 3.8.1
|
||||||
'@react-types/shared': 3.29.0(react@18.3.1)
|
'@react-types/shared': 3.29.0(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
|
|
@ -8427,7 +8430,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-types/datepicker@3.11.0(react@18.3.1)':
|
'@react-types/datepicker@3.11.0(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.1
|
||||||
'@react-types/calendar': 3.7.0(react@18.3.1)
|
'@react-types/calendar': 3.7.0(react@18.3.1)
|
||||||
'@react-types/overlays': 3.8.14(react@18.3.1)
|
'@react-types/overlays': 3.8.14(react@18.3.1)
|
||||||
'@react-types/shared': 3.29.0(react@18.3.1)
|
'@react-types/shared': 3.29.0(react@18.3.1)
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ export const EventActionCommit = (props: EventActionProps) => {
|
||||||
variant="flat"
|
variant="flat"
|
||||||
color="primary"
|
color="primary"
|
||||||
isLoading={props.isLoading === "commit"}
|
isLoading={props.isLoading === "commit"}
|
||||||
onPress={() => onSubmit()}
|
onPress={onSubmit}
|
||||||
>
|
>
|
||||||
提交
|
提交
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -175,7 +175,7 @@ export const EventActionAlterCommit = (props: EventActionProps) => {
|
||||||
<Button
|
<Button
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isLoading={props.isLoading === "commit"}
|
isLoading={props.isLoading === "commit"}
|
||||||
onPress={() => onSubmit()}
|
onPress={onSubmit}
|
||||||
>
|
>
|
||||||
修改提交
|
修改提交
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
107
src/pages/repair/ExportEventDialog.tsx
Normal file
107
src/pages/repair/ExportEventDialog.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { useState } from "react"
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Button,
|
||||||
|
DateRangePicker,
|
||||||
|
} from "@heroui/react"
|
||||||
|
import { parseDate } from "@internationalized/date"
|
||||||
|
import { saturdayApiBaseUrl } from "../../utils/client"
|
||||||
|
import { makeLogtoClient } from "../../utils/auth"
|
||||||
|
|
||||||
|
export function ExportExcelModal() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [dateRange, setDateRange] = useState({
|
||||||
|
start: parseDate("2025-01-01"),
|
||||||
|
end: parseDate("2025-05-16"),
|
||||||
|
})
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const openModal = () => setIsOpen(true)
|
||||||
|
const closeModal = () => setIsOpen(false)
|
||||||
|
|
||||||
|
const downloadExcel = async () => {
|
||||||
|
if (!dateRange.start || !dateRange.end) return
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const start = dateRange.start.toString() // Format: 'YYYY-MM-DD'
|
||||||
|
const end = dateRange.end.toString()
|
||||||
|
const url = `${saturdayApiBaseUrl}/events/xlsx?start_time=${start}&end_time=${end}`
|
||||||
|
|
||||||
|
const token = await makeLogtoClient().getAccessToken()
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (!response.ok) throw new Error("Download failed")
|
||||||
|
|
||||||
|
// Extract filename from Content-Disposition header
|
||||||
|
const disposition = response.headers.get("Content-Disposition")
|
||||||
|
let filename = "export.xlsx" // Default filename
|
||||||
|
if (disposition && disposition.includes("filename=")) {
|
||||||
|
const filenameMatch = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||||
|
if (filenameMatch != null && filenameMatch[1]) {
|
||||||
|
filename = filenameMatch[1].replace(/['"]/g, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement("a")
|
||||||
|
link.href = downloadUrl
|
||||||
|
link.setAttribute("download", filename)
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
link.remove()
|
||||||
|
window.URL.revokeObjectURL(downloadUrl) // Clean up
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
alert("Failed to download Excel file: " + error.message)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setLoading(false)
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onPress={openModal} color="primary">
|
||||||
|
导出为Excel
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Modal isOpen={isOpen} onClose={closeModal}>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>导出为Excel</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<DateRangePicker
|
||||||
|
label="选择日期范围"
|
||||||
|
value={dateRange}
|
||||||
|
onChange={setDateRange}
|
||||||
|
granularity="day"
|
||||||
|
visibleMonths={2}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="ghost" onClick={closeModal}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={downloadExcel}
|
||||||
|
isLoading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "导出中..." : "导出"}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ import { makeLogtoClient } from "../../utils/auth"
|
||||||
import type { PublicMember } from "../../store/member"
|
import type { PublicMember } from "../../store/member"
|
||||||
import type { UserInfoResponse } from "@logto/browser"
|
import type { UserInfoResponse } from "@logto/browser"
|
||||||
import { getAvailableEventActions, type EventAction, type IdentityContext } from "./EventAction"
|
import { getAvailableEventActions, type EventAction, type IdentityContext } from "./EventAction"
|
||||||
|
import { ExportExcelModal } from "./ExportEventDialog"
|
||||||
|
|
||||||
type PublicEvent = components["schemas"]["PublicEvent"]
|
type PublicEvent = components["schemas"]["PublicEvent"]
|
||||||
|
|
||||||
|
|
@ -385,11 +386,15 @@ export default function App() {
|
||||||
return cellValue
|
return cellValue
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="box-border mb-24">
|
<section className="box-border mb-24">
|
||||||
<div className="section-content mt-6">
|
<div className="section-content mt-6 flex justify-between items-center">
|
||||||
<h2 className="text-2xl font-bold">维修管理</h2>
|
<h2 className="text-2xl font-bold">维修管理</h2>
|
||||||
|
{
|
||||||
|
userInfo?.roles?.find(v => v.toLowerCase() == "repair admin")
|
||||||
|
? <ExportExcelModal></ExportExcelModal>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="section-content my-8 flex flex-col gap-4">
|
<div className="section-content my-8 flex flex-col gap-4">
|
||||||
<Table
|
<Table
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue