完善功能

This commit is contained in:
WenC 2024-03-20 18:30:39 +08:00
parent 9160a37bbe
commit c83637d1bc
10 changed files with 392 additions and 116 deletions

View File

@ -14,5 +14,7 @@ module.exports = {
'warn',
{allowConstantExport: true},
],
// 去除any报错
'@typescript-eslint/no-explicit-any': ['off']
},
}

View File

@ -2,10 +2,13 @@ import './card-proj-type.css';
import {ICardProj, ICardProjChargeLine} from "../../interfaces/proj/ICardProj.ts";
import {Checkbox} from 'antd';
import {useState} from "react";
import {ProjAdditionalType} from "../../interfaces/proj/IProj.ts";
export default function CardProjType(props: ICardProj) {
const [chargeAmount, setChargeAmount] = useState(0);
const [pkg, setPkg] = useState(false);
const [videoDemo, setVideoDemo] = useState(false);
const renderContents = (lineIndex: number, contents: string[]) => {
return contents.map((item, index) => <li key={`content_${lineIndex}_${index}`}>
@ -29,8 +32,18 @@ export default function CardProjType(props: ICardProj) {
const onChargeChange = (checked: boolean, line: ICardProjChargeLine) => {
if (checked) {
if (line.id == ProjAdditionalType.PKG) {
setPkg(true);
} else if (line.id == ProjAdditionalType.VIDEO_DEMO) {
setVideoDemo(true);
}
setChargeAmount(chargeAmount + line.price);
} else {
if (line.id == ProjAdditionalType.PKG) {
setPkg(false);
} else if (line.id == ProjAdditionalType.VIDEO_DEMO) {
setVideoDemo(false);
}
setChargeAmount(chargeAmount - line.price);
}
}
@ -60,13 +73,15 @@ export default function CardProjType(props: ICardProj) {
return props.buyArray.map((buy, index) => {
return (
<div className="buy-btn" key={`buy_${index}`}>
<div className="price">{buy.label}{buy.price + chargeAmount} </div>
<div className="price">{buy.label}{(buy.price + chargeAmount) / 100} </div>
<hr/>
<div className="buy">
<button onClick={() => {
buy.handleClick(props.head, buy.price + chargeAmount);
}}>
</button>
buy.handleClick(props.head, {
pkg: pkg,
videoDemo: videoDemo
});
}}></button>
</div>
</div>
)

View File

@ -1,9 +1,9 @@
import './list-proj.css'
import CardProj from "../card/CardProj.tsx";
import {useRef, MutableRefObject, useState, useEffect} from "react";
import {Input, Pagination} from 'antd';
import {Input, Pagination, message} from 'antd';
import type {SearchProps} from 'antd/es/input/Search';
import {Axios} from "../../util/AjaxUtils.ts";
import {get} from "../../util/AjaxUtils.ts";
const {Search} = Input;
@ -17,25 +17,28 @@ export default function ListProj() {
const listProjRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
const listRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
const [page, _setPage] = useState(1);
const [total, _setTotal] = useState(0);
const [projs, _setProjs] = useState([]);
const [messageApi, contextHolder] = message.useMessage();
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [projs, setProjs] = useState([]);
const domHeight = window.innerHeight - 280;
const renderData = () => {
Axios.get('/api/proj/listpage/self', {
const reqData = (currentPage: number) => {
get({
messageApi: messageApi,
url: '/api/proj/listpage/self',
config: {
params: {
page: page,
page: currentPage,
rows: 20
}
}).then(res => {
const data = res.data;
_setProjs(data.page);
_setTotal(data.total);
_setProjs(data.rows);
}).catch(res => {
console.log(res);
},
onSuccess({data}) {
setProjs(data.page);
setTotal(data.total);
setProjs(data.rows);
}
})
}
@ -44,10 +47,12 @@ export default function ListProj() {
}
useEffect(() => {
renderData();
reqData(page);
}, [])
return (
<>
{contextHolder}
<div className="list-proj" ref={listProjRef}>
<div className="top">
<Search placeholder="按项目名搜索" onSearch={onSearch} style={{width: 200}}/>
@ -57,9 +62,13 @@ export default function ListProj() {
{renderList()}
</div>
<div className="page">
<Pagination defaultCurrent={page} total={total}/>
<Pagination defaultCurrent={page} total={total} onChange={(page) => {
setPage(page);
reqData(page);
}}/>
</div>
</div>
</div>
</>
)
}

View File

@ -1,18 +1,24 @@
export interface ICardProjBodyLine {
title: string;
contents: string[];
}
export interface ICardProjChargeLine {
id: string;
title: string;
price: number;
}
export interface ICardProjBuy {
id: string;
label?: string,
price: number;
handleClick(title: string, price: number): void;
handleClick(title: string, additional: {
pkg: boolean,
videoDemo: boolean
}): void;
}
export interface ICardProj {

View File

@ -20,6 +20,34 @@ export enum GenerateStatus {
ERROR = 'ERROR'
}
export enum ProjChargeType {
ALL = 'ALL',
MATERIAL_AGENT = 'MATERIAL_AGENT',
MATERIAL_AGENT_URGENT = 'MATERIAL_AGENT_URGENT',
MATERIAL = 'MATERIAL',
FREE = 'FREE',
}
export enum ProjAdditionalType {
PKG = 'PKG',
VIDEO_DEMO = 'VIDEO_DEMO'
}
export interface IProjCharge {
proj: {
all: number;
materialAgent: number;
materialAgentUrgent: number;
material: number;
free: number;
},
additional: {
pkg: number;
videoDemo: number;
}
}
export interface IProj {
projId: string,
@ -33,5 +61,4 @@ export interface IProj {
gmtCreate: string,
generateStatus: GenerateStatus
}

View File

@ -16,11 +16,11 @@ const router = createBrowserRouter([
element: <ProjCreate />
},
{
path: '/proj-new/:type/:price',
path: '/proj-new/:projChargeType',
element: <ProjNew />
},
{
path: '/proj-edit',
path: '/proj-edit/:projId',
element: <ProjEdit />
},
{

View File

@ -1,4 +1,3 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

View File

@ -1,13 +1,42 @@
import './proj-create.css'
import {Link, useNavigate} from "react-router-dom";
import {Breadcrumb} from "antd";
import {Breadcrumb, message} from "antd";
import CardProjType from "../../components/card/CardProjType.tsx";
import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts";
import {useEffect, useState} from "react";
import {get} from "../../util/AjaxUtils.ts";
export default function ProjCreate() {
const [messageApi, contextHolder] = message.useMessage();
const [charge, setCharge] = useState<IProjCharge>({
proj: {
all: 0,
materialAgent: 0,
materialAgentUrgent: 0,
material: 0,
free: 0,
},
additional: {
pkg: 0,
videoDemo: 0
}
});
const nav = useNavigate();
const height = window.innerHeight - 150;
useEffect(() => {
get({
messageApi: messageApi,
url: '/api/proj/charge/get',
onSuccess({data}) {
setCharge(data);
}
})
}, [])
return (
<>
{contextHolder}
<Breadcrumb
items={[
{title: <Link to={'/'}></Link>},
@ -42,9 +71,10 @@ export default function ProjCreate() {
]}
buyArray={[
{
price: 500,
handleClick: (_title, price) => {
nav(`/proj-new/q/${price}`)
id: ProjChargeType.ALL,
price: charge.proj.all,
handleClick: () => {
nav(`/proj-new/${ProjChargeType.ALL}`)
}
}
]}
@ -72,27 +102,31 @@ export default function ProjCreate() {
]}
chargeLineArray={[
{
price: 100,
title: '安装包100元'
id: ProjAdditionalType.PKG,
price: charge.additional.pkg,
title: `安装包 ${charge.additional.pkg / 100}`
},
{
price: 50,
title: '系统演示视频文件50元'
id: ProjAdditionalType.VIDEO_DEMO,
price: charge.additional.videoDemo,
title: `系统演示视频文件 ${charge.additional.videoDemo / 100}`
}
]}
buyArray={[
{
id: ProjChargeType.MATERIAL_AGENT,
label: '普件:',
price: 200,
handleClick: (_title, price) => {
nav(`/proj-new/xdp/${price}`)
price: charge.proj.materialAgent,
handleClick: (_title, additional) => {
nav(`/proj-new/${ProjChargeType.MATERIAL_AGENT}?pkg=${additional.pkg}&videoDemo=${additional.videoDemo}`)
}
},
{
id: ProjChargeType.MATERIAL_AGENT_URGENT,
label: '加急:',
price: 300,
handleClick: (_title, price) => {
nav(`/proj-new/xdj/${price}`)
price: charge.proj.materialAgentUrgent,
handleClick: (_title, additional) => {
nav(`/proj-new/${ProjChargeType.MATERIAL_AGENT_URGENT}?pkg=${additional.pkg}&videoDemo=${additional.videoDemo}`)
}
}
]}
@ -123,19 +157,22 @@ export default function ProjCreate() {
]}
chargeLineArray={[
{
price: 100,
title: '安装包100元'
id: ProjAdditionalType.PKG,
price: charge.additional.pkg,
title: `安装包 ${charge.additional.pkg / 100}`
},
{
price: 50,
title: '系统演示视频文件50元'
id: ProjAdditionalType.VIDEO_DEMO,
price: charge.additional.videoDemo,
title: `系统演示视频文件 ${charge.additional.videoDemo / 100}`
}
]}
buyArray={[
{
price: 200,
handleClick: (_title, price) => {
nav(`/proj-new/x/${price}`)
id: ProjChargeType.MATERIAL,
price: charge.proj.material,
handleClick: () => {
nav(`/proj-new/${ProjChargeType.MATERIAL}`)
}
}
]}
@ -159,9 +196,10 @@ export default function ProjCreate() {
]}
buyArray={[
{
price: 0,
handleClick: (_title, price) => {
nav(`/proj-new/m/${price}`)
id: ProjChargeType.FREE,
price: charge.proj.free,
handleClick: () => {
nav(`/proj-new/${ProjChargeType.FREE}`)
}
}
]}

View File

@ -1,38 +1,76 @@
import './proj-new.css';
import {Link, useNavigate, useParams} from "react-router-dom";
import {Breadcrumb, Button, Flex, Form, type FormProps, Input, Modal} from "antd";
import {useState} from "react";
import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom";
import {Breadcrumb, Button, Flex, Form, Input, message, Modal, Spin} from "antd";
import {useEffect, useState} from "react";
import {get, post} from "../../util/AjaxUtils.ts";
import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts";
const {TextArea} = Input;
type FieldType = {
projTitle: string;
projDesc: string;
type ProjInfo = {
projName: string;
projIntroduction: string;
};
export default function ProjNew() {
const height = window.innerHeight - 150;
const [isModalOpen, setIsModalOpen] = useState(false);
const nav = useNavigate();
const params = useParams();
const pathParams = useParams();
const [queryParams] = useSearchParams();
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
console.log('Success:', values);
setIsModalOpen(true);
};
const [messageApi, contextHolder] = message.useMessage();
const [loading, setLoading] = useState<boolean>(false);
const height = window.innerHeight - 150;
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [chargePrice, setChargePrice] = useState(0);
const [projInfo, setProjInfo] = useState<ProjInfo>({
projName: '',
projIntroduction: '',
});
const listProjChargeAdditional: string[] = [];
let createProjId = '';
const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const handleConfirmOk = () => {
// 扣款
nav('/proj-edit');
useEffect(() => {
get({
messageApi: messageApi,
url: '/api/proj/charge/get',
onSuccess({data}) {
const charge = data as IProjCharge;
let price = 0;
switch (pathParams.projChargeType) {
case ProjChargeType.ALL:
price = charge.proj.all;
break;
case ProjChargeType.MATERIAL_AGENT:
price = charge.proj.materialAgent;
break;
case ProjChargeType.MATERIAL_AGENT_URGENT:
price = charge.proj.materialAgentUrgent;
break;
case ProjChargeType.MATERIAL:
price = charge.proj.material;
break;
case ProjChargeType.FREE:
price = charge.proj.free;
break;
default:
messageApi.error({
type: 'error',
content: '价格类型错误',
})
}
const handleConfirmCancel = () => {
setIsModalOpen(false);
if (queryParams.get('pkg')) {
price += charge.additional.pkg;
listProjChargeAdditional.push(ProjAdditionalType.PKG);
}
if (queryParams.get('videoDemo')) {
price += charge.additional.videoDemo;
listProjChargeAdditional.push(ProjAdditionalType.VIDEO_DEMO);
}
setChargePrice(price);
}
})
}, []);
const onBack = () => {
nav(-1);
@ -40,6 +78,7 @@ export default function ProjNew() {
return (
<>
{contextHolder}
<Breadcrumb
items={[
{title: <Link to={'/'}></Link>},
@ -58,21 +97,26 @@ export default function ProjNew() {
wrapperCol={{span: 24}}
style={{width: 500}}
initialValues={{remember: true}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
onFinish={(formData) => {
setIsCreateModalOpen(true);
setProjInfo({
projName: formData.projName,
projIntroduction: formData.projIntroduction
})
}}
autoComplete="off"
>
<Form.Item<FieldType>
<Form.Item<ProjInfo>
label="系统标题"
name="projTitle"
name="projName"
rules={[{required: true, message: '请输入系统标题'}]}
>
<Input placeholder="请输入系统标题"/>
</Form.Item>
<Form.Item<FieldType>
<Form.Item<ProjInfo>
label="简单描述"
name="projDesc"
name="projIntroduction"
rules={[{required: true, message: '请输入简单描述'}]}
>
<TextArea rows={6} placeholder="请用一段话简单描述系统"/>
@ -80,7 +124,8 @@ export default function ProjNew() {
<Form.Item>
<Flex align="center" justify="center" gap="large">
<Button type="primary" htmlType="submit" style={{backgroundColor: 'var(--color-primary)'}}>
<Button type="primary" htmlType="submit"
style={{backgroundColor: 'var(--color-primary)'}}>
</Button>
<Button type="default" htmlType="button" onClick={onBack}>
@ -92,10 +137,51 @@ export default function ProjNew() {
</div>
</div>
</div>
<Modal title="提示" okText="确定" cancelText="取消" open={isModalOpen} onOk={handleConfirmOk}
onCancel={handleConfirmCancel}>
<div>{params.price}</div>
<Modal title="提示"
okText="确定"
cancelText="取消"
open={isCreateModalOpen}
onOk={() => {
setIsCreateModalOpen(false);
post({
messageApi,
url: '/api/proj/create',
body: {
projName: projInfo.projName,
projIntroduction: projInfo.projIntroduction,
projChargeType: pathParams.projChargeType,
listProjChargeAdditional: listProjChargeAdditional
},
onBefore() {
setLoading(true);
},
onSuccess({data}) {
setIsEditModalOpen(true);
createProjId = data.data;
},
onFinally() {
setLoading(false);
}
})
}}
onCancel={() => {
setIsCreateModalOpen(false);
}}>
<div> {chargePrice / 100} </div>
</Modal>
<Modal title="提示"
okText="确定"
cancelText="取消"
open={isEditModalOpen}
onOk={() => {
nav(`/proj-edit/${createProjId}`);
}}
onCancel={() => {
setIsEditModalOpen(false);
}}>
<div></div>
</Modal>
<Spin tip="正在提交..." spinning={loading} fullscreen/>
</>
)
}

View File

@ -1,6 +1,100 @@
import axios from "axios";
import axios, {AxiosRequestConfig, AxiosResponse} from "axios";
import type {MessageInstance} from "antd/es/message/interface";
type Req = {
messageApi: MessageInstance;
url: string;
body?: any;
config?: AxiosRequestConfig;
onBefore?(): void;
onSuccess(data: AxiosResponse): void;
onFinally?(): void;
}
axios.defaults.baseURL = 'http://127.0.0.1:7025/copyright';
axios.defaults.headers['X-USER-ID'] = '80d3365e-0597-4988-979e-18ef1c3ec671';
axios.interceptors.request.use(config => {
if (config.method === 'get') {
config.data = {unused: 0} // 这个是关键点解决get 请求添加不上content_type
}
config.headers['X-USER-ID'] = '80d3365e-0597-4988-979e-18ef1c3ec671';
return config
}
);
export const Axios = axios;
export function get(req: Req) {
req.onBefore?.();
Axios.get(req.url, req.config).then(res => {
req.onSuccess(res);
}).catch(error => {
if (error.response) {
const data = error.response.data;
req.messageApi.open({
type: 'error',
content: data.msg ? data.msg : `${data.path}(${data.status})`,
});
} else {
console.error(error)
}
}).finally(() => {
req.onFinally?.();
})
}
export function post(req: Req) {
req.onBefore?.();
Axios.post(req.url, req.body, req.config).then(res => {
req.onSuccess(res);
}).catch(error => {
if (error.response) {
const data = error.response.data;
req.messageApi.open({
type: 'error',
content: data.msg ? data.msg : `${data.path}(${data.status})`,
});
} else {
console.error(error)
}
}).finally(() => {
req.onFinally?.();
})
}
export function put(req: Req) {
req.onBefore?.();
Axios.put(req.url, req.config).then(res => {
req.onSuccess(res);
}).catch(error => {
if (error.response) {
const data = error.response.data;
req.messageApi.open({
type: 'error',
content: data.msg ? data.msg : `${data.path}(${data.status})`,
});
} else {
console.error(error)
}
}).finally(() => {
req.onFinally?.();
})
}
export function del(req: Req) {
req.onBefore?.();
Axios.delete(req.url, req.config).then(res => {
req.onSuccess(res);
}).catch(error => {
if (error.response) {
const data = error.response.data;
req.messageApi.open({
type: 'error',
content: data.msg ? data.msg : `${data.path}(${data.status})`,
});
} else {
console.error(error)
}
}).finally(() => {
req.onFinally?.();
})
}