This commit is contained in:
shixiaohua 2024-04-12 18:43:55 +08:00
parent a2db003a01
commit c5abcf2166
29 changed files with 3852 additions and 290 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See https://help.github.com/articles/ignoring-files/ four more about ignoring files.
# dependencies # dependencies
/node_modules /node_modules

3272
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,16 +9,25 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@heroicons/react": "^2.0.18",
"@tailwindcss/forms": "^0.5.7",
"antd": "^5.16.1",
"axios": "^1.6.8",
"next": "14.1.3",
"postcss": "8.4.31",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"next": "14.1.3" "tailwindcss": "3.3.3"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@vercel/style-guide": "^5.0.1",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.1.3" "eslint-config-next": "14.1.3",
"prettier": "3.0.3",
"prettier-plugin-tailwindcss": "0.5.4",
"typescript": "^5"
} }
} }

View File

@ -0,0 +1,3 @@
export default function Page() {
return <p>Customer Page</p>;
}

View File

@ -0,0 +1,3 @@
export default function Page() {
return <p>Invoice Page</p>;
}

View File

@ -0,0 +1,12 @@
import SideNav from '@/app/ui/dashboard/sidenav';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
<div className="w-full flex-none md:w-64">
<SideNav />
</div>
<div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
</div>
);
}

View File

@ -0,0 +1,3 @@
export default function Loading() {
return <div>Loading...</div>;
}

View File

@ -0,0 +1,3 @@
export default function Page() {
return <p>Dashboard Page</p>;
}

View File

@ -1,107 +0,0 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--primary-glow: conic-gradient(
from 180deg at 50% 50%,
#16abff33 0deg,
#0885ff33 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg
);
--secondary-glow: radial-gradient(
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(
#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080
);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
--secondary-glow: linear-gradient(
to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3)
);
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(
#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80
);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

View File

@ -1,8 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] }); import "@/app/ui/globals.css";
// import { inter } from '@/app/ui/fonts';
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Create Next App",
@ -15,8 +14,8 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html>
<body className={inter.className}>{children}</body> <body>{children}</body>
</html> </html>
); );
} }

View File

@ -0,0 +1,40 @@
import React from "react";
export type Invoice = {
id: string;
customer_id: string;
amount: number;
date: string;
// In TypeScript, this is called a string union type.
// It means that the "status" property can only be one of the two strings: 'pending' or 'paid'.
status: 'pending' | 'paid';
};
type Status={
success:boolean;
code:number ;
message: string;
}
export type ResultPage<T> = {
content:T[];
totalPages:number;
totalElements:number;
}
export type ResponseVO<T>={
data:T;
timeStamp:number;
status:Status;
}
export type DataType ={
key: React.ReactNode;
id: number;
code: string;
name: string;
description: string;
state: number;
priority: number;
type:number;
action?:React.ReactNode;
children: DataType[];
}

View File

@ -0,0 +1,14 @@
const invoices = [
{
customer_id: customers[0].id,
amount: 15795,
status: 'pending',
date: '2022-12-06',
},
{
customer_id: customers[1].id,
amount: 20348,
status: 'pending',
date: '2022-11-14',
},
];

View File

@ -0,0 +1,21 @@
import {unstable_noStore as noStore} from 'next/cache';
import axios, {AxiosResponse} from "axios";
import {DataType, ResponseVO, ResultPage} from "@/app/lib/definitions";
export async function taskTreeResult():Promise<ResponseVO<ResultPage<DataType>>> {
noStore();
try {
// 使用 Axios 发送 POST 请求获取数据
const response: AxiosResponse<ResponseVO<ResultPage<DataType>>> = await axios.post('http://localhost:8090/task/tree', {
pageSize: 10,
pageNumber: 1
});
// 从响应中提取数据并返回
return response.data;
} catch (error) {
// 处理错误
console.error('Error fetching data:', error);
// 返回一个默认值或者抛出错误
throw new Error('Failed to fetch data');
}
}

View File

@ -1,95 +1,52 @@
import Image from "next/image"; import AcmeLogo from '@/app/ui/acme-logo';
import styles from "./page.module.css"; import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import Image from 'next/image';
export default function Home() { export default function Home() {
return ( return (
<main className={styles.main}> <main className="flex min-h-screen flex-col p-6">
<div className={styles.description}> <div className="flex h-20 shrink-0 items-end rounded-lg bg-blue-500 p-4 md:h-52">
<p> <AcmeLogo/>
Get started by editing&nbsp;
<code className={styles.code}>src/app/page.tsx</code>
</p>
<div>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{" "}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
</a>
</div> </div>
</div> <div className="mt-4 flex grow flex-col gap-4 md:flex-row">
<div className="flex flex-col justify-center gap-6 rounded-lg bg-gray-50 px-6 py-10 md:w-2/5 md:px-20">
<div className={styles.center}> <p
<Image className={`text-xl text-gray-800 md:text-3xl md:leading-normal`}
className={styles.logo} >
src="/next.svg" <strong>Welcome to Acme.</strong> This is the example for the{' '}
alt="Next.js Logo" <a href="https://nextjs.org/learn/" className="text-blue-500">
width={180} Next.js Learn Course
height={37} </a>
priority , brought to you by Vercel.
/> </p>
</div> <Link
href="/login"
<div className={styles.grid}> className="flex items-center gap-5 self-start rounded-lg bg-blue-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-blue-400 md:text-base"
<a >
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" <span>Log in</span>
className={styles.card} {/*<ArrowRightIcon className="w-5 md:w-6"/>*/}
target="_blank" </Link>
rel="noopener noreferrer" </div>
> <div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
<h2> {/* Add Hero Images Here */}
Docs <span>-&gt;</span> <Image
</h2> src="/hero-desktop.png"
<p>Find in-depth information about Next.js features and API.</p> width={1000}
</a> height={760}
alt="Screenshots of the dashboard project showing desktop version"
<a className="hidden md:block"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" />
className={styles.card} <Image
target="_blank" src="/hero-mobile.png"
rel="noopener noreferrer" width={560}
> height={620}
<h2> alt="Screenshot of the dashboard project showing mobile version"
Learn <span>-&gt;</span> className="block md:hidden"
</h2> />
<p>Learn about Next.js in an interactive course with&nbsp;quizzes!</p> </div>
</a> </div>
</main>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Templates <span>-&gt;</span>
</h2>
<p>Explore starter templates for Next.js.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Deploy <span>-&gt;</span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
); );
} }

View File

@ -0,0 +1,12 @@
import SideNav from '@/app/ui/dashboard/sidenav';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
<div className="w-full flex-none md:w-64">
<SideNav />
</div>
<div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
</div>
);
}

View File

@ -0,0 +1,3 @@
export default function Page() {
return <p>Dashboard Page</p>;
}

7
src/app/task/layout.tsx Normal file
View File

@ -0,0 +1,7 @@
import SideNav from '@/app/ui/dashboard/sidenav';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div>{children}</div>
);
}

3
src/app/task/loading.tsx Normal file
View File

@ -0,0 +1,3 @@
export default function Loading() {
return <div>Loading...</div>;
}

View File

@ -0,0 +1,3 @@
export default function Loading() {
return <div>Loading...</div>;
}

View File

@ -0,0 +1,14 @@
import TreeTable from "@/app/ui/task/project/TreeTable";
import {DetailForm} from "@/app/ui/task/project/DetailForm";
const Page: React.FC = () => {
return (
<>
<TreeTable/>
<DetailForm itemId={12} operationId={"1"}></DetailForm>
</>
);
};
export default Page;

13
src/app/ui/acme-logo.tsx Normal file
View File

@ -0,0 +1,13 @@
import { GlobeAltIcon } from '@heroicons/react/24/outline';
// import { lusitana } from '@/app/ui/fonts';
export default function AcmeLogo() {
return (
<div
className={`flex flex-row items-center leading-none text-white`}
>
{/*<GlobeAltIcon className="h-12 w-12 rotate-[15deg]" />*/}
<p className="text-[44px]">Acme</p>
</div>
);
}

View File

@ -0,0 +1,37 @@
import {
UserGroupIcon,
HomeIcon,
DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
// Map of links to display in the side navigation.
// Depending on the size of the application, this would be stored in a database.
const links = [
{ name: 'Home', href: '/dashboard', icon: HomeIcon },
{
name: 'Invoices',
href: '/dashboard/invoices',
icon: DocumentDuplicateIcon,
},
{ name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },
];
export default function NavLinks() {
return (
<>
{links.map((link) => {
const LinkIcon = link.icon;
return (
<a
key={link.name}
href={link.href}
className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
>
{/*<LinkIcon className="w-6" />*/}
<p className="hidden md:block">{link.name}</p>
</a>
);
})}
</>
);
}

View File

@ -0,0 +1,29 @@
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
export default function SideNav() {
return (
<div className="flex h-full flex-col px-3 py-4 md:px-2">
<Link
className="mb-2 flex h-20 items-end justify-start rounded-md bg-blue-600 p-4 md:h-40"
href="/public"
>
<div className="w-32 text-white md:w-40">
<AcmeLogo />
</div>
</Link>
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form>
<button className="flex h-[48px] w-full grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
</button>
</form>
</div>
</div>
);
}

7
src/app/ui/fonts.ts Normal file
View File

@ -0,0 +1,7 @@
// import { Inter,Lusitana } from 'next/font/google';
//
// export const inter = Inter({ subsets: ['latin'] });
// export const lusitana = Lusitana({
// weight: ['400', '700'],
// subsets: ['latin'],
// });

18
src/app/ui/globals.css Normal file
View File

@ -0,0 +1,18 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
input[type='number'] {
-moz-appearance: textfield;
appearance: textfield;
}
input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}

View File

@ -0,0 +1,131 @@
'use client'
import React, { useState } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import {
Button,
Cascader,
Checkbox,
ColorPicker,
DatePicker,
Form,
Input,
InputNumber,
Radio,
Select,
Slider,
Switch,
TreeSelect,
Upload,
} from 'antd';
export interface DetailFormProps {
itemId: number,
operationId: string,
}
export const DetailForm: React.FC<DetailFormProps> = (props) => {
const [componentDisabled, setComponentDisabled] =
useState<boolean>(props.operationId==='1');
const { RangePicker } = DatePicker;
const { TextArea } = Input;
const normFile = (e: any) => {
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
return (
<>
<Checkbox
checked={componentDisabled}
onChange={(e) => setComponentDisabled(e.target.checked)}
>
Form disabled
</Checkbox>
<Form
labelCol={{span: 4}}
wrapperCol={{span: 14}}
layout="horizontal"
disabled={componentDisabled}
style={{maxWidth: 600}}
>
<Form.Item label="Checkbox" name="disabled" valuePropName="checked">
<Checkbox>Checkbox</Checkbox>
</Form.Item>
<Form.Item label="Radio">
<Radio.Group>
<Radio value="apple"> Apple </Radio>
<Radio value="pear"> Pear </Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="Input">
<Input/>
</Form.Item>
<Form.Item label="Select">
<Select>
<Select.Option value="demo">Demo</Select.Option>
</Select>
</Form.Item>
<Form.Item label="TreeSelect">
<TreeSelect
treeData={[
{title: 'Light', value: 'light', children: [{title: 'Bamboo', value: 'bamboo'}]},
]}
/>
</Form.Item>
<Form.Item label="Cascader">
<Cascader
options={[
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
},
],
},
]}
/>
</Form.Item>
<Form.Item label="DatePicker">
<DatePicker/>
</Form.Item>
<Form.Item label="RangePicker">
<RangePicker/>
</Form.Item>
<Form.Item label="InputNumber">
<InputNumber/>
</Form.Item>
<Form.Item label="TextArea">
<TextArea rows={4}/>
</Form.Item>
<Form.Item label="Switch" valuePropName="checked">
<Switch/>
</Form.Item>
<Form.Item label="Upload" valuePropName="fileList" getValueFromEvent={normFile}>
<Upload action="/upload.do" listType="picture-card">
<button style={{border: 0, background: 'none'}} type="button">
<PlusOutlined/>
<div style={{marginTop: 8}}>Upload</div>
</button>
</Upload>
</Form.Item>
<Form.Item label="Button">
<Button>Button</Button>
</Form.Item>
<Form.Item label="Slider">
<Slider/>
</Form.Item>
<Form.Item label="ColorPicker">
<ColorPicker/>
</Form.Item>
</Form>
</>
);
}

View File

@ -0,0 +1,107 @@
import React, {Fragment} from "react";
import {Button, Dropdown, MenuProps, Modal, Popconfirm, Space} from "antd";
import {DownOutlined, QuestionCircleOutlined} from "@ant-design/icons";
import {DetailForm} from "@/app/ui/task/project/DetailForm";
export interface OperationButtonProps {
itemId: number
}
interface OperationModelProps {
operationId: string,
openModal:boolean
}
class OperationButton extends React.Component<OperationButtonProps,OperationModelProps> {
constructor(props: OperationButtonProps) {
super(props);
this.state = {
operationId: '',
openModal:false
};
}
render() {
const handleCancel =()=>{
this.setState({...this.state,openModal:false})
}
const items: MenuProps['items'] = [
{
key: '1',
label: <a onClick={(e) => {
this.setState({...this.state,openModal:true})
}}></a>,
},
{
key: '2',
label: <a onClick={(e) => {
this.setState({...this.state,openModal:true})
}}></a>,
},
{
key: '3',
label: <a onClick={(e) => {
this.setState({...this.state,openModal:true})
}}></a>,
}
,
{
key: '4',
label: <Popconfirm
title="删除任务"
description="确认要删除任务?"
icon={<QuestionCircleOutlined style={{color: 'red'}}/>}
okText="确认"
cancelText="取消"
></Popconfirm>,
}
,
{
key: '5',
label: <Popconfirm
title="完成任务"
description="确认要完成任务?"
okText="确认"
cancelText="取消"
></Popconfirm>,
}
];
return <Fragment>
<Dropdown menu={{items}}>
<a onClick={(e) => {
e.preventDefault()
}}>
<Space>
<DownOutlined/>
</Space>
</a>
</Dropdown>
<Modal
open={this.state.openModal}
title="Title"
// open={open}
// onOk={handleOk}
onCancel={handleCancel}
// footer={[
// <Button key="back" onClick={handleCancel}>
// Return
// </Button>,
// <Button key="submit" type="primary" loading={loading} onClick={handleOk}>
// Submit
// </Button>,
// <Button
// key="link"
// href="https://google.com"
// type="primary"
// loading={loading}
// onClick={handleOk}
// >
// Search on Google
// </Button>,
// ]}
>
<DetailForm itemId={this.props.itemId} operationId={'1'}/>
</Modal>
</Fragment>
}
}
export default OperationButton;

View File

@ -0,0 +1,13 @@

View File

@ -0,0 +1,106 @@
'use client'
import React, {useEffect, useState} from 'react';
import {Button, ColorPicker, Dropdown, MenuProps, Space, Switch, Table} from 'antd';
import type { TableColumnsType, TableProps } from 'antd';
import { taskTreeResult} from "@/app/lib/task/project/data";
import {DataType, ResponseVO, ResultPage} from "@/app/lib/definitions";
import {DownOutlined} from "@ant-design/icons";
import OperationButton from "@/app/ui/task/project/OperationButton";
type TableRowSelection<T> = TableProps<T>['rowSelection'];
const columns: TableColumnsType<DataType> = [
{
title: '任务编码',
dataIndex: 'code',
key: 'code',
width: '10%',
},
{
title: '任务名称',
dataIndex: 'name',
key: 'name',
width: '20%',
},
{
title: '任务描述',
dataIndex: 'description',
width: '30%',
key: 'description',
},
{
title: '任务状态',
dataIndex: 'state',
width: '10%',
key: 'state',
},
{
title: '优先级',
dataIndex: 'priority',
width: '10%',
key: 'priority',
},
{
title: '操作',
dataIndex: 'action',
width: '10%',
key: 'action',
},
];
// rowSelection objects indicates the need for row selection
const rowSelection: TableRowSelection<DataType> = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
function recursionActionChild(children:DataType[]){
if (children.length===0){
return;
}
children.forEach(item=>{
item.key=item.id;
item.action=(<OperationButton itemId={item.id}></OperationButton>)
recursionActionChild(item.children)
})
}
const TreeTable: React.FC = () => {
// const [checkStrictly, setCheckStrictly] = useState(false);
const [data, setData] = useState<DataType[]>([]);
const [pageNumber, setPageNumber] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
useEffect(() => {
console.log("useEffect::taskTreeResult")
taskTreeResult().then((result:ResponseVO<ResultPage<DataType>>)=>{
if (result.status.success){
recursionActionChild(result.data.content);
setData(result.data.content)
}
})
}, []);
return (
<>
{/*<Space align="center" style={{ marginBottom: 16 }}>*/}
{/* CheckStrictly: <Switch checked={checkStrictly} onChange={setCheckStrictly} />*/}
{/*</Space>*/}
<Button type="primary">线</Button>
<ColorPicker defaultValue="#1677ff" showText/>
<Table
columns={columns}
// rowSelection={{ ...rowSelection, checkStrictly}}
dataSource={data}
/>
</>
);
};
export default TreeTable;