小程序可以加载账单

This commit is contained in:
qiaomu 2024-04-29 17:30:51 +08:00
parent f1185057b8
commit b0257eccea
84 changed files with 10377 additions and 0 deletions

170
palette.js Normal file
View File

@ -0,0 +1,170 @@
module.exports = {
red: {
1: '#FFECE8',
2: '#FDCDC5',
3: '#FBACA3',
4: '#F98981',
5: '#F76560',
6: '#F53F3F',
7: '#CB272D',
8: '#A1151E',
9: '#770813',
10: '#4D000A'
},
orangered: {
1: '#FFF3E8',
2: '#FDDDC3',
3: '#FCC59F',
4: '#FAAC7B',
5: '#F99057',
6: '#F77234',
7: '#CC5120',
8: '#A23511',
9: '#771F06',
10: '#4D0E00'
},
orange: {
1: '#FFF7E8',
2: '#FFE4BA',
3: '#FFCF8B',
4: '#FFB65D',
5: '#FF9A2E',
6: '#FF7D00',
7: '#D25F00',
8: '#A64500',
9: '#792E00',
10: '#4D1B00'
},
gold: {
1: '#FFFCE8',
2: '#FDF4BF',
3: '#FCE996',
4: '#FADC6D',
5: '#F9CC45',
6: '#F7BA1E',
7: '#CC9213',
8: '#A26D0A',
9: '#774B04',
10: '#4D2D00'
},
yellow: {
1: '#FEFFE8',
2: '#FEFEBE',
3: '#FDFA94',
4: '#FCF26B',
5: '#FBE842',
6: '#FADC19',
7: '#CFAF0F',
8: '#A38408',
9: '#785D03',
10: '#4D3800'
},
lime: {
1: '#FCFFE8',
2: '#EDF8BB',
3: '#DCF190',
4: '#C9E968',
5: '#B5E241',
6: '#9FDB1D',
7: '#7EB712',
8: '#5F940A',
9: '#437004',
10: '#2A4D00'
},
green: {
1: '#E8FFEA',
2: '#AFF0B5',
3: '#7BE188',
4: '#4CD263',
5: '#23C343',
6: '#00B42A',
7: '#009A29',
8: '#008026',
9: '#006622',
10: '#004D1C'
},
sggreen: {
1: '#DCF5E9',
2: '#95E8C1',
3: '#69DBAA',
4: '#42CF96',
5: '#1FC286',
6: '#00B578',
7: '#008F64',
8: '#00694D',
9: '#004233',
10: '#001C16'
},
cyan: {
1: '#E8FFFB',
2: '#B7F4EC',
3: '#89E9E0',
4: '#5EDFD6',
5: '#37D4CF',
6: '#14C9C9',
7: '#0DA5AA',
8: '#07828B',
9: '#03616C',
10: '#00424D'
},
blue: {
1: '#E8F7FF',
2: '#C3E7FE',
3: '#9FD4FD',
4: '#7BC0FC',
5: '#57A9FB',
6: '#3491FA',
7: '#206CCF',
8: '#114BA3',
9: '#063078',
10: '#001A4D'
},
arcoblue: {
1: '#E8F3FF',
2: '#BEDAFF',
3: '#94BFFF',
4: '#6AA1FF',
5: '#4080FF',
6: '#165DFF',
7: '#0E42D2',
8: '#072CA6',
9: '#031A79',
10: '#000D4D'
},
purple: {
1: '#F5E8FF',
2: '#DDBEF6',
3: '#C396ED',
4: '#A871E3',
5: '#8D4EDA',
6: '#722ED1',
7: '#551DB0',
8: '#3C108F',
9: '#27066E',
10: '#16004D'
},
magenta: {
1: '#FFE8F1',
2: '#FDC2DB',
3: '#FB9DC7',
4: '#F979B7',
5: '#F754A8',
6: '#F5319D',
7: '#CB1E83',
8: '#A11069',
9: '#77064F',
10: '#4D0034'
},
gray: {
1: '#f7f8fa',
2: '#f2f3f5',
3: '#e5e6eb',
4: '#c9cdd4',
5: '#a9aeb8',
6: '#86909c',
7: '#6b7785',
8: '#4e5969',
9: '#272e3b',
10: '#1d2129'
}
};

View File

@ -0,0 +1,54 @@
import { BaseResponse } from "@/shared/model-components";
import {tenementSearchChoice, tenementSearchChoiceByName} from "@q/charge";
import { useQuery } from "@tanstack/react-query";
import { isCorrectResult } from "@u/asyncs";
import { Select } from "antd";
import { equals } from "ramda";
import { useState } from "react";
import { useDebounce } from "react-use";
import 'twin.macro'
interface P {
parkId?: string,
disabled?: boolean
allowClear?: boolean,
placeholder?: string,
onChange?: (e: any) => void,
api?: (keyword: string) => Promise<BaseResponse & { tenements: { fullName: string; id: string; park: string; shortName: string; parkName: string }[] }>
}
const CompanyNoPark = (props: P) => {
const [searchTenement, setSearchTenement] = useState('');
const [searchTenementTemp, setSearchTenementTemp] = useState('');
// 商户列表
const { data: tenements, status: tenementStatus } = useQuery(
['tenements-getSelectList', searchTenement],
() => props.api ? props.api(searchTenement) : props.parkId ? tenementSearchChoice(props.parkId, searchTenement) : tenementSearchChoiceByName(searchTenement),
{ enabled: !!searchTenement, select: res => (isCorrectResult(res) ? res.tenements : []) }
)
useDebounce(() => setSearchTenement(searchTenementTemp), 800, [searchTenementTemp]);
return (
<Select
tw="w-full"
showSearch
disabled={props.disabled}
allowClear={props.allowClear}
// showArrow={false}
filterOption={false}
notFoundContent={null}
onChange={e => props.onChange && props.onChange(e)}
onSearch={setSearchTenementTemp}
defaultActiveFirstOption={false}
placeholder={props.placeholder || "请输入商户全称检索"}
loading={equals(tenementStatus, 'loading')}
options={(tenements || []).map(d => ({ value: d.id, label: `${d.parkName ?( d.parkName || '') + ' - ' : ""} ${d.fullName}` }))}
dropdownStyle={{minWidth: "250px", width: 'auto'}}
/>
)
}
export default CompanyNoPark;

View File

@ -0,0 +1,110 @@
// @ts-ignore
import {ProTable} from '@ant-design/pro-components';
import React, {ReactNode} from "react";
import "twin.macro"
import {ExpandableConfig} from "antd/lib/table/interface";
interface Props {
columns: any[],
dataSource: any[],
localStorageKey: string,
rowKey: (value: any) => string,
rowClassName?: (record: any, index: number) => string,
loading: boolean,
pagination?:false | {
total: number,
current: number,
onChange: (value: number) => void,
},
scroll?: {
x?: number|string,
y?: number|string
}
toolbarRender?: ReactNode[],
toolbar?: ReactNode,
expandable?: ExpandableConfig<any>
rowSelection?:any,
size?: string,
customPagination?: boolean,
footer?: ReactNode,
// search?: ReactNode
}
export default function (props: Props) {
const {
columns = [],
dataSource = [],
loading,
rowKey,
pagination= { total: 0, current: 1, onChange: () => {}, showSizeChanger: false },
scroll,
localStorageKey,
toolbarRender,
toolbar,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
size,
customPagination,
footer,
...other
// search
} = props
return (
<>
<style>
{
`
.ant-table-fixed-left table,
.ant-table-fixed-right table {
width: min-content;
}
.ant-pro-table-list-toolbar-left, .ant-pro-table-list-toolbar-title {
flex: 1 !important;
}
.ant-pro-table-list-toolbar-right {
flex: 0 !important;
flex-wrap: nowrap;
}
.ant-pro-table-list-toolbar-container {
padding-top: 0;
}
`
}
</style>
<ProTable
columns={columns}
columnsState={{
persistenceKey: localStorageKey,
persistenceType: 'sessionStorage',
}}
tableExtraRender={null}
cardProps={false}
search={false}
rowKey={rowKey}
tableAlertRender={false}
loading={loading}
columnEmptyText={""}
options={{
setting: true,
reload: false,
density: false,
}}
scroll={scroll}
dataSource={dataSource}
pagination={customPagination ? false : (pagination === false ? pagination : {
size: "default",
pageSize: 20,
showSizeChanger: false,
...pagination
})}
toolBarRender={() => toolbarRender}
toolbar={{
title: <div tw={"w-full"}> {toolbar} </div>
}}
{...other}
/>
{footer}
</>
)
}

View File

@ -0,0 +1,24 @@
import { Pagination } from "antd";
import { ReactNode } from "react";
import "twin.macro"
interface Props {
content: ReactNode,
current: number,
onChange: (value) => void,
total: number,
}
export default function(props: Props) {
const { content, current, total, onChange } = props;
return <div tw="flex flex-row justify-end items-center w-full mt-3">
<div tw="mr-4"> {content} </div>
<Pagination
pageSize={20}
current={current}
total={total}
showTotal={total => <div> {total} </div>}
onChange={onChange}
/>
</div>
}

View File

@ -0,0 +1,75 @@
import { useAuthenticated } from '@h/useAuthenticated';
import { logout } from '@q/session';
import { useLayoutStore } from '@st/layout_store';
import { useSessionStore } from '@st/session_store';
import { norNilOrEmpty } from '@u/funcs';
import { notification } from 'antd';
import dayjs from 'dayjs';
import { lte, not, range } from 'ramda';
import { FC, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useInterval } from 'react-use';
import 'twin.macro';
import { useAutoAsyncFn } from '@h/useAutoAsyncFn';
/**
*
*/
export const Daemon: FC = () => {
const isAuthenticated = useAuthenticated();
const navigate = useNavigate();
const [, logoutUser] = useAutoAsyncFn(logout);
const warningDisplayed = useSessionStore.use.warningDisplayed();
const showMask = useLayoutStore.use.showMask();
const hideMask = useLayoutStore.use.hideMask();
const unauthenticated = useMemo(() => {
return not(isAuthenticated);
}, [isAuthenticated]);
const checkAndLogout = useCallback(() => {
// 这里务必实时获取LocalStorage中保存的内容如果依靠select函数那么在各个计时中数据将得不到变化。
// ! 这里的内容在修改的时候务必与useSession Hook中的LocalStorage使用方法、键名保持一致。
const expiresAt = localStorage.getItem('expires') ?? '2000-01-01';
const isLoggedInLocal = norNilOrEmpty(localStorage.getItem('token') ?? null);
const isTokenExpiredLocal = dayjs(expiresAt).isBefore(dayjs());
const difference = dayjs(expiresAt).diff(dayjs(), 'minute');
[1, 5, 10].forEach((minutes, index) => {
const warningDisplays = useSessionStore.getState().expireWarning;
if (lte(difference, minutes) && not(warningDisplays[index])) {
notification.warning({
message: '登录会话即将结束',
description: (
<div>
<span tw="mx-2 text-xl text-red-6">{minutes}</span>
</div>
),
placement: 'bottomRight',
duration: 10
});
range(index, 3).forEach(s => warningDisplayed(s));
}
});
if (not(isLoggedInLocal) || isTokenExpiredLocal) {
showMask();
notification.warning({
message: '登录会话结束',
description: <div></div>,
placement: 'bottomRight',
duration: 5,
onClose: async () => {
await logoutUser();
navigate('/login', { replace: true });
hideMask();
}
});
}
}, []);
useInterval(checkAndLogout, unauthenticated ? null : 30000);
return null;
};

View File

@ -0,0 +1,14 @@
import { useLayoutStore } from '@st/layout_store';
import { FC } from 'react';
import tw, { styled } from 'twin.macro';
const Mask = styled.div(({ visible }: { visible: boolean }) => [
visible ? tw`block` : tw`hidden`,
tw`fixed top-0 left-0 w-screen h-screen bg-black opacity-50 z-[1001]`
]);
export const GlobalMask: FC = () => {
const visible = useLayoutStore.use.maskVisible();
return <Mask visible={visible} />;
};

View File

@ -0,0 +1,61 @@
// 用于生成一个根据关键字搜索商户的输入选择框
import { BaseResponse } from "@/shared/model-components";
import { tenementSearchChoice } from "@q/charge";
import { useQuery } from "@tanstack/react-query";
import { isCorrectResult } from "@u/asyncs";
import { Select } from "antd";
import { useState } from "react";
import { useDebounce } from "react-use";
import 'twin.macro'
interface P {
parkId?: string,
tenement?: string,
disabled?: boolean
allowClear?: boolean
placeholder?: string
onChange?: (e) => void,
valueKey?: string,
labelKey?: string,
api?: (parkId: string, tenement: string) => Promise<BaseResponse & {
tenements: {
fullName: string;
id: string;
park: string;
shortName: string;
}[];
}>
}
const Company = (props: P) => {
const { parkId } = props;
const [searchTenement, setSearchTenement] = useState('');
const [searchTenementTemp, setSearchTenementTemp] = useState('');
const request = props.api ? props.api : tenementSearchChoice
// 商户列表
const { data: tenements } = useQuery(
['tenements-choice', parkId, searchTenement],
() => request(parkId, searchTenement),
{ enabled: !!searchTenement && !!parkId, select: res => (isCorrectResult(res) ? res.tenements : []) }
);
useDebounce(() => setSearchTenement(searchTenementTemp), 800, [searchTenementTemp]);
return (
<Select
tw="w-full"
showSearch
allowClear={props.allowClear}
filterOption={false}
notFoundContent={null}
onChange={e => { props.onChange && props.onChange(e) }}
onSearch={setSearchTenementTemp}
defaultActiveFirstOption={false}
value={props.tenement}
placeholder={props.placeholder || "请输入商户名称检索"}
options={(tenements || []).map(d => ({ value: props.valueKey ? d[props.valueKey]: d.id, label: props.labelKey ? d[props.labelKey] : d.fullName }))}
/>
)
}
export default Company;

View File

@ -0,0 +1,147 @@
//@ts-nocheck
import { useAuthenticated } from '@h/useAuthenticated';
import { useAuthorization } from '@h/useAuthorization';
import { isEmpty, mergeDeepLeft } from 'ramda';
import { Component, FunctionComponent, PropsWithChildren } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
/**
* `requireAuthorize`
*
* `all``any``all``any`
*/
export interface AuthorizeOptions {
/**
*
*/
all?: number[];
/**
*
*/
any?: number[];
/**
*
*/
toLogin?: boolean;
}
/**
*
* !
*
* @param param0
* @returns
*/
export const AuthRequired: FC<PropsWithChildren<AuthorizeOptions>> = ({
all,
any,
toLogin,
children
}: PropsWithChildren<AuthorizeOptions>) => {
const requiredAllPrivileges = all ?? [];
const requiredAnyPrivileges = any ?? [];
const redirectToLogin = toLogin ?? false;
const isAuthenticated = useAuthenticated();
const { hasAll, hasAny } = useAuthorization();
const location = useLocation();
// 注意,这里的判断逻辑是:
// 如果所需全部权限和任意权限都为空,即不需要任何权限,那么判断为通过;
// 如果所需全部权限为空,那么取任意权限的判定;
// 如果全部权限不为空,那么取全部权限的判断。
const isUserMatchNeeds =
(isEmpty(requiredAllPrivileges) && isEmpty(requiredAnyPrivileges)) ||
(isEmpty(requiredAllPrivileges)
? hasAny(...requiredAnyPrivileges)
: hasAll(...requiredAllPrivileges));
if (isAuthenticated && isUserMatchNeeds) {
return children;
} else {
return redirectToLogin ? (
<Navigate to="/login" state={{ from: location }} replace={true} />
) : (
<Navigate to="/unauthorized" state={{ from: location }} replace={true} />
);
}
};
/**
*
* !
*
* @param Comp
* @param options 访
* @returns
*/
export const requireAuthorize = (
Comp: Component | FunctionComponent,
options?: AuthorizeOptions
): FunctionComponent<any> => {
const altedOptions = mergeDeepLeft(options, { any: [], all: [] });
return props => {
const isAuthenticated = useAuthenticated();
const { hasAll, hasAny } = useAuthorization();
const location = useLocation();
const requiredAllPrivileges: string[] = altedOptions.all;
const requiredAnyPrivileges: string[] = altedOptions.any;
const redirectToLogin = altedOptions.toLogin ?? false;
// 注意,这里的判断逻辑是:
// 如果所需全部权限和任意权限都为空,即不需要任何权限,那么判断为通过;
// 如果所需全部权限为空,那么取任意权限的判定;
// 如果全部权限不为空,那么取全部权限的判断。
const isUserMatchNeeds =
(isEmpty(requiredAllPrivileges) && isEmpty(requiredAnyPrivileges)) ||
(isEmpty(requiredAllPrivileges)
? hasAny(...requiredAnyPrivileges)
: hasAll(...requiredAllPrivileges));
if (isAuthenticated && isUserMatchNeeds) {
return <Comp {...props} />;
} else {
return redirectToLogin ? (
<Navigate to="/login" state={{ from: location }} replace={true} />
) : (
<Navigate to="/unauthorized" state={{ from: location }} replace={true} />
);
}
};
};
/**
*
* !
*
* @param param0
* @returns null
*/
export const WithAuth: FC<PropsWithChildren<AuthorizeOptions>> = ({
all,
any,
children
}: PropsWithChildren<AuthorizeOptions>) => {
const requiredAllPrivileges = all ?? [];
const requiredAnyPrivileges = any ?? [];
const isAuthenticated = useAuthenticated();
const { hasAll, hasAny } = useAuthorization();
// 注意,这里的判断逻辑是:
// 如果所需全部权限和任意权限都为空,即不需要任何权限,那么判断为通过;
// 如果所需全部权限为空,那么取任意权限的判定;
// 如果全部权限不为空,那么取全部权限的判断。
const isUserMatchNeeds =
(isEmpty(requiredAllPrivileges) && isEmpty(requiredAnyPrivileges)) ||
(isEmpty(requiredAllPrivileges)
? hasAny(...requiredAnyPrivileges)
: hasAll(...requiredAllPrivileges));
if (isAuthenticated && isUserMatchNeeds) {
return children;
}
return null;
};

View File

@ -0,0 +1,31 @@
//@ts-nocheck
import { CentralSpin } from '@c/ui/CentralSpin';
import { lazyLoad } from '@u/lazyload';
import { prop } from 'ramda';
import { FC, Suspense } from 'react';
/**
*
* @param componentName
* @param loader
* @returns
*/
export const suspense = (componentName: string, loader: () => Promise): FC<any> => {
const Component = prop(componentName, lazyLoad(loader));
return props => (
<Suspense fallback={<CentralSpin />}>
<Component {...props} />
</Suspense>
);
};
/**
*
* @param componentName
* @param loader
* @returns
*/
export const asyncLoad = (componentName: string, loader: () => Promise): FC<any> => {
const Component = prop(componentName, lazyLoad(loader));
return props => <Component {...props} />;
};

View File

@ -0,0 +1,54 @@
// 用于生成一个根据关键字搜索商户的输入选择框
import { meter04List } from "@q/park";
import { useQuery } from "@tanstack/react-query";
import { isCorrectResult } from "@u/asyncs";
import { Select } from "antd";
import { equals } from "ramda";
import { useState } from "react";
import { useDebounce } from "react-use";
import 'twin.macro'
interface P {
parkId?: string,
Meter?: string,
disabled?: boolean
allowClear?: boolean
placeholder?: string
onChange?: (e) => void,
valueKey?: string,
labelKey?: string,
}
const Meter = (props: P) => {
const { parkId } = props;
const [searchMeter, setSearchMeter] = useState('');
const [searchMeterTemp, setSearchMeterTemp] = useState('');
// 商户列表
const { data: Meters, status: MeterStatus } = useQuery(
['meter-select-list', parkId, searchMeter],
() => { return meter04List(parkId, 1, searchMeter, undefined)},
{ enabled: !!searchMeter, select: res => (isCorrectResult(res) ? res.meters : []) }
);
useDebounce(() => setSearchMeter(searchMeterTemp), 800, [searchMeterTemp]);
return (
<Select
tw="w-full"
showSearch
allowClear={props.allowClear}
filterOption={false}
notFoundContent={null}
onChange={e => { props.onChange && props.onChange(e) }}
onSearch={setSearchMeterTemp}
defaultActiveFirstOption={false}
value={props.Meter}
placeholder={props.placeholder || "请输入表号检索"}
loading={equals(MeterStatus, 'loading')}
options={(Meters || []).map(d => ({ value: props.valueKey ? d[props.valueKey]: d.code, label: props.labelKey ? d[props.labelKey] : d.code }))}
/>
)
}
export default Meter;

View File

@ -0,0 +1,143 @@
//@ts-nocheck
import { omit } from 'ramda';
import { FC, PropsWithChildren } from 'react';
import tw, { styled, TwStyle } from 'twin.macro';
/**
*
*/
type CapsuleType =
| 'info'
| 'warn'
| 'error'
| 'success'
| 'primary'
| 'plain'
| 'gold'
| 'silver'
| 'green'
| 'lime';
/**
*
*/
type CapsuleSize = 'nano' | 'mini' | 'small' | 'normal' | 'large' | 'extra';
const TypeStyles: { [key in CapsuleType]: TwStyle } = {
info: tw`bg-blue-3 text-white`,
warn: tw`bg-orange-6 text-white`,
error: tw`bg-red-6 text-white`,
success: tw`bg-green-4 text-white`,
primary: tw`bg-sggreen-6 text-white`,
plain: tw`bg-gray-4 text-white`,
gold: tw`bg-gold-6 text-white`,
silver: tw`bg-gray-6 text-white`,
green: tw`bg-green-6 text-white`,
lime: tw`bg-lime-6 text-white`
};
const HoverTypeStyles: { [key in CapsuleType]: TwStyle } = {
info: tw`hover:bg-blue-3 hover:text-white`,
warn: tw`hover:bg-orange-6 hover:text-white`,
error: tw`hover:bg-red-6 hover:text-white`,
success: tw`hover:bg-green-4 hover:text-white`,
primary: tw`hover:bg-sggreen-6 hover:text-white`,
plain: tw`hover:bg-gray-4 hover:text-white`,
gold: tw`hover:bg-gold-6 hover:text-white`,
silver: tw`hover:bg-gray-6 hover:text-white`,
green: tw`hover:bg-green-6 hover:text-white`,
lime: tw`hover:bg-lime-6 hover:text-white`
};
const ActiveTypeStyles: { [key in CapsuleType]: TwStyle } = {
info: tw`active:text-white active:bg-blue-5 cursor-pointer`,
warn: tw`active:text-white active:bg-orange-8 cursor-pointer`,
error: tw`active:text-white active:bg-red-8 cursor-pointer`,
success: tw`active:text-white active:bg-green-6 cursor-pointer`,
primary: tw`active:text-white active:bg-sggreen-8 cursor-pointer`,
plain: tw`active:text-white active:bg-gray-6 cursor-pointer`,
gold: tw`active:text-white active:bg-gold-8 cursor-pointer`,
silver: tw`active:text-white active:bg-gray-8 cursor-pointer`,
green: tw`active:text-white active:bg-green-8 cursor-pointer`,
lime: tw`active:text-white active:bg-lime-8 cursor-pointer`
};
const SizeStyles: Record<CapsuleSize, TwStyle> = {
nano: tw`text-[0.35rem] leading-[0.5rem] px-0.5 py-0.5 rounded`,
mini: tw`text-[0.5rem] leading-[0.75rem] px-1 py-0.5 rounded`,
small: tw`text-xs px-1 py-0.5 rounded`,
normal: tw`text-sm px-1.5 py-1 rounded-md`,
large: tw`text-base px-1.5 py-1 rounded-md`,
extra: tw`text-lg px-2 py-1.5 rounded-lg`
};
const CapsulePanel = styled.div(({ type, size, hover, activable }: Partial<CapsuleProps>) => [
tw`select-none`,
TypeStyles[type ?? 'plain'],
SizeStyles[size ?? 'normal'],
(hover ?? false) && HoverTypeStyles[hover ?? 'plain'],
(activable ?? false) && (hover ?? false) && ActiveTypeStyles[hover ?? 'plain']
]);
/**
*
*/
export interface CapsuleProps {
/**
*
*/
type: CapsuleType;
/**
*
*/
hover?: CapsuleType | boolean;
/**
* 使
*/
activable?: boolean;
/**
* `activable`
*/
onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
/**
*
*/
size: CapsuleSize;
/**
*
*/
content: string;
}
/**
*
* @param props
* @returns
*/
export const Capsule: FC<PropsWithChildren<CapsuleProps>> = (
props: PropsWithChildren<CapsuleProps>
) => {
const otherProps = omit(['type', 'hover', 'activable', 'size', 'content', 'onClick'], props);
const handleClickAction = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
(props.activable ?? false) && props.onClick && props.onClick(event);
};
return (
<CapsulePanel
type={props.type}
size={props.size}
hover={props.hover}
activable={props.activable}
onClick={handleClickAction}
{...otherProps}
>
{props.content}
</CapsulePanel>
);
};

View File

@ -0,0 +1,15 @@
import { Spin } from 'antd';
import { FC } from 'react';
import tw from 'twin.macro';
const CentralSpinLayout = tw.div`h-full w-full overflow-hidden flex flex-row justify-center items-center`;
/**
*
* @returns
*/
export const CentralSpin: FC = () => (
<CentralSpinLayout>
<Spin size="large" />
</CentralSpinLayout>
);

View File

@ -0,0 +1,7 @@
import Layout from 'antd/lib/layout';
import tw from 'twin.macro';
/**
*
*/
export const ContentArea = tw(Layout.Content)`bg-white px-4 py-5 min-h-full w-full box-border`;

View File

@ -0,0 +1,40 @@
import { isNil } from 'ramda';
import tw, { styled } from 'twin.macro';
import { Panel } from './Panel';
/**
*
*/
export interface DynamicStretchPanelProps {
/**
*
*/
extended: boolean;
/**
* 使CSS百分比或者具体像素大小描述
*/
minWidth: string;
}
/**
*
*/
export const DynamicStretchPanel = styled(Panel)(({ extended, minWidth }: DynamicStretchPanelProps) => [
tw`flex flex-col justify-start items-start space-y-1 p-2`,
!isNil(minWidth) && !extended && `max-width: ${minWidth}`,
extended && tw`flex-grow`
]);
/**
* 使
*/
export const VerticalLayout = tw.div`flex flex-col justify-start items-stretch w-full h-full`;
/**
* 使
*/
export const HorizontalLayout = tw.div`flex flex-row justify-start items-stretch w-full h-full`;
/**
* 使
*/
export const HeadLine = tw.div`flex flex-row justify-start items-center space-x-1`;

View File

@ -0,0 +1,16 @@
import tw from 'twin.macro';
/**
*
*/
export const Panel = tw.div`border rounded-sm p-2 bg-white border-gray-3`;
/**
*
*/
export const VerticalPanel = tw(Panel)`h-full w-full flex flex-col justify-start p-2 space-y-2`;
/**
*
*/
export const HorizontalPanel = tw(Panel)`h-full w-full flex flex-col justify-start p-2 space-x-2`;

View File

@ -0,0 +1,99 @@
import {useCallback, useEffect, useState} from 'react';
import { Region, regions, regionsDetail } from '@q/common';
import { isCorrectResult } from '@u/asyncs';
import { App, Cascader } from 'antd';
import { last, map } from 'ramda';
import { useQuery } from '@tanstack/react-query';
interface Option {
value: string;
label: string;
children?: Option[];
isLeaf?: boolean;
loading?: boolean;
}
/**
*
*/
export function SelectRegions(props: {
// 外部应嵌套Form初始化时Form传递的默认值为string如果用户修改则会变为array
value?: string | string[];
onChange?: (value: string[]) => void;
}) {
const { message } = App.useApp();
// 行政区划数据源
const [options, setOptions] = useState<Option[]>([]);
const [current, setCurrent] = useState<(string | 0)[]>([
typeof props.value === 'string' ? 0 : props.value ? last(props.value) : 0
]);
// 加载子级行政区划数据
const onLoadRegion = useCallback(
(selectedOptions?: Option[]) => {
// 获取选择的最后的行政区
const select = selectedOptions?.[selectedOptions.length - 1];
setCurrent(selectedOptions.map(e => e.value));
// 如果不是初次加载则更新点击的项的loading状态
if (select) select.loading = true;
},
[options]
);
useEffect(() => {
}, []);
// 手动触发时,加载逻辑
useQuery(['select-region', ...current, props.value], () => regions(last(current)), {
onSuccess: res => {
if (isCorrectResult(res)) {
setOptions(state => {
if (last(current) === 0) return mapRegionsToOptions(res.regions);
let target: Option;
current.forEach(e => (target = (target?.children ?? state).find(o => o.value === e)));
if (target) {
target.children = mapRegionsToOptions(res.regions);
target.loading = false;
}
return [...state];
});
return null;
} else message.error(res.message ?? '加载行政区划失败!');
}
});
// 初始选择的默认值,通过数组还是字符串来判断是否是表单自动填充的
const { data: defaultValue, isFetching: defaultLoading } = useQuery(
['select-region-detail', props.value],
() => regionsDetail(props.value as string),
{
enabled: typeof props.value === 'string' && !!props.value,
select: res => {
if (isCorrectResult(res)) {
return {
value: [props.value] as string[],
label: res.regions
.map(e => e.name)
.reverse()
.join('/')
};
} else {
message.error(res.message ?? '加载行政区信息失败!');
return null;
}
}
}
);
return (
<Cascader
options={options}
loadData={onLoadRegion}
loading={defaultLoading}
onChange={props.onChange}
placeholder="请选择行政区划"
value={typeof props.value === 'string' ? [defaultValue?.label] : props.value}
/>
);
}
const mapRegionsToOptions = (regions: Region[]) =>
map<Region, Option>(e => ({ value: e.code, label: e.name, loading: false, isLeaf: e.level === 3 }), regions);

View File

@ -0,0 +1,6 @@
import tw from 'twin.macro';
/**
* Flex布局中支撑空白内容
*/
export const SpringGap = tw.div`flex-grow`;

View File

@ -0,0 +1,42 @@
//@ts-nocheck
import { gte } from 'ramda';
import { FC } from 'react';
import tw, { styled } from 'twin.macro';
const WeakIndicator = styled.div(({ enabled }) => [
tw`flex-grow h-2`,
enabled ? tw`bg-red-6` : tw`bg-gray-4`
]);
const MediumIndicator = styled.div(({ enabled }) => [
tw`flex-grow h-2`,
enabled ? tw`bg-yellow-6` : tw`bg-gray-4`
]);
const StrongIndicator = styled.div(({ enabled }) => [
tw`flex-grow h-2`,
enabled ? tw`bg-green-6` : tw`bg-gray-4`
]);
interface StrengthGadgetProps {
/**
*
*/
strength: number;
}
export const StrengthGadget: FC<StrengthGadgetProps> = ({ strength }) => {
return (
<div tw="w-full pb-1 flex flex-col justify-start items-center">
<div tw="w-full flex flex-row justify-between items-center space-x-0.5">
<WeakIndicator enabled={gte(strength, 0)} />
<MediumIndicator enabled={gte(strength, 1)} />
<MediumIndicator enabled={gte(strength, 2)} />
<StrongIndicator enabled={gte(strength, 3)} />
</div>
<div tw="w-full flex flex-row justify-between items-center text-xs text-gray-6">
<div tw="text-left flex-grow"></div>
<div tw="text-center flex-grow"></div>
<div tw="text-right flex-grow"></div>
</div>
</div>
);
};

View File

@ -0,0 +1,6 @@
import tw from 'twin.macro';
/**
*
*/
export const ToolBar = tw.div`flex flex-row justify-start items-center space-x-2`;

View File

@ -0,0 +1,6 @@
import tw from 'twin.macro';
/**
*
*/
export const WorkArea = tw.div`relative flex flex-col justify-start items-stretch h-full w-full space-y-2`;

View File

@ -0,0 +1,32 @@
import { useSessionStore } from '@st/session_store';
import { notNil } from '@u/funcs';
import { App } from 'antd';
import { AxiosError } from 'axios';
import { equals } from 'ramda';
import { useNavigate } from 'react-router-dom';
/**
* 403403
*
*
* @returns check()403403
*/
export function useAnswer403Forbidden() {
const { message } = App.useApp();
const logout = useSessionStore.use.logout();
const navigate = useNavigate();
function checkError(error: AxiosError) {
if (notNil(error) && error.isAxiosError) {
if (equals(error.response.status, 403)) {
// 这里做错误提示,主要用于提示用户不具有操作权限,并且将会自动结束用户会话。
void message.error('你当前会话没有访问指定内容的权限,你的会话已被系统自动结束。', 2.5);
logout();
navigate('/login');
}
}
}
return { check: checkError };
}

View File

@ -0,0 +1,33 @@
import { norNilOrEmpty } from '@u/funcs';
import dayjs from 'dayjs';
import { not } from 'ramda';
import { useExpiresAt, useToken } from './useSession';
/**
*
* @returns
*/
export function useIsLoggedIn(): boolean {
const [token] = useToken();
return norNilOrEmpty(token);
}
/**
*
* @returns
*/
export function useIsSessionExpired(): boolean {
const [expiresAt] = useExpiresAt();
return expiresAt.isBefore(dayjs());
}
/**
*
* @returns
*/
export function useAuthenticated() {
const isLoggedIn = useIsLoggedIn();
const isExpired = useIsSessionExpired();
return isLoggedIn && not(isExpired);
}

View File

@ -0,0 +1,63 @@
//@ts-nocheck
import { difference, equals, length, lt } from 'ramda';
import { useMemo } from 'react';
import { useSession } from './useSession';
/**
* LocalStorage中保存的会话信息判断当前用户是否是管理单位
* @returns
*/
export function useIsEnterprise() {
const [user] = useSession();
return equals(user.type, 0);
}
/**
* LocalStorage中保存的会话信息判断当用户是否是监管部门
* @returns
*/
export function useIsSupervision() {
const [user] = useSession();
return equals(user.type, 1);
}
/**
* LocalStorage中保存的会话信息判断当用户是否是运维人员
* @returns
*/
export function useIsOPS() {
const [user] = useSession();
return equals(user.type, 2);
}
/**
* Hook
* @returns hasAll()hasAny()
*/
export function useAuthorization() {
const [user] = useSession();
const ownedPrivileges = useMemo(() => [user?.type ?? -1], [user]);
/**
*
* @returns
* @param requires
*/
function hasAll(...requires: number[]): boolean {
const differs = difference(requires, ownedPrivileges);
return equals(length(differs), 0);
}
/**
*
* @param requires
* @returns
*/
function hasAny(...requires: number[]): boolean {
const originalPrivilegesSize = length(requires);
const differs = difference(requires, ownedPrivileges);
return lt(length(differs), originalPrivilegesSize);
}
return { hasAll, hasAny };
}

View File

@ -0,0 +1,55 @@
//@ts-nocheck
import { equals } from 'ramda';
import { useCallback, useRef } from 'react';
import { useMountedState } from 'react-use';
import { FunctionReturningPromise, PromiseType } from 'react-use/lib/misc/types';
import { AsyncFnReturn, AsyncState } from 'react-use/lib/useAsyncFn';
import { useImmer } from 'use-immer';
import { useAnswer403Forbidden } from './useAnswer403Forbidden';
type StateFromFunctionReturningPromise<T extends FunctionReturningPromise> = AsyncState<
PromiseType<ReturnType<T>>
>;
export function useAutoAsyncFn<T extends FunctionReturningPromise>(
fn: T,
deps: DependencyList = [],
initialState: StateFromFunctionReturningPromise<T> = { loading: false }
): AsyncFnReturn<T> {
const [state, updateState] = useImmer<StateFromFunctionReturningPromise<T>>(initialState);
const isMounted = useMountedState();
const lastCallId = useRef(0);
const { check: check403 } = useAnswer403Forbidden();
const newCallback = useCallback(async (...args: Parameters<T>): Promise<ReturnType<T>> => {
const callId = ++lastCallId.current;
if (!state.loading) {
updateState(draft => {
draft.loading = true;
});
}
try {
const result = await fn(...args);
if (isMounted() && equals(callId, lastCallId.current)) {
updateState(draft => {
draft.value = result;
draft.loading = false;
});
}
return result;
} catch (e) {
if (isMounted() && equals(callId, lastCallId.current)) {
updateState(draft => {
draft.error = e;
draft.loading = false;
});
}
check403(e);
throw e;
}
}, deps);
return [state, newCallback as unknown as T];
}

View File

@ -0,0 +1,19 @@
import { useBreadcrumbStore } from '@st/breadcrumb_store';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export function useBreadcrumb(label: string, replace = false) {
// const link = useHref();
const location = useLocation();
const pushBreadcrumb = useBreadcrumbStore.use.push();
const popBreadcrumb = useBreadcrumbStore.use.pop();
const replaceBreadcrumb = useBreadcrumbStore.use.replace();
useEffect(() => {
if (replace) replaceBreadcrumb({ label, link: location.pathname + location.search });
}, []);
useEffect(() => {
if (!replace) pushBreadcrumb({ label, link: location.pathname + location.search });
return () => popBreadcrumb();
}, []);
}

View File

@ -0,0 +1,46 @@
//@ts-nocheck
import { SyncObjectCallback } from '@/shared/foundation';
import { reduceIndexed } from '@u/funcs';
import { append, defaultTo, gte, isEmpty, match, min, reduce, reduced, sum } from 'ramda';
import { useCallback } from 'react';
const detectRules = ['[a-z]', '[A-Z]', '[0-9]'];
const lengthStrengthRules = [0, 6, 8, 10];
const defaultAllowedSymbols = '!"#$%&\'()*+,-./:;<=>?@[\\\\\\]^_`{|}~';
/**
* 004
*
* @param allowedSymbols 使
* @returns
*/
export function useCalculatePasswordStrength(
allowedSymbols?: string
): SyncObjectCallback<string, number> {
const finalAllowedSymbols = defaultTo(defaultAllowedSymbols)(allowedSymbols);
const detector = useCallback(
(pwd: string) => {
const finalDetectRules = append(`[${finalAllowedSymbols}]`, detectRules);
const rulesDetectResult = reduce(
(acc, elem) => {
const matches = match(elem, pwd);
return append(isEmpty(matches) ? 0 : 1, acc);
},
[] as number[],
finalDetectRules
);
const symbolStrengthLevel = sum(rulesDetectResult);
const pwdLength = pwd.length;
const lengthStrengthLevel = reduceIndexed<number, number>(
(acc, elem, index) => (gte(pwdLength, elem) ? index : reduced(acc)),
0,
lengthStrengthRules
);
return min(symbolStrengthLevel, lengthStrengthLevel);
},
[finalAllowedSymbols]
);
return detector;
}

View File

@ -0,0 +1,46 @@
import { MapFactory, SyncCallback, SyncObjectCallback } from '@/shared/foundation';
import { isNilOrEmpty, notNil } from '@u/funcs';
import { equals } from 'ramda';
import { useCallback } from 'react';
type LocalStorageOperates<T> = [T, SyncObjectCallback<T>, SyncCallback];
type useLocalStorageOption<T> = {
raw?: boolean;
serializer?: MapFactory<string, T>;
deserializer?: MapFactory<T, string>;
};
/**
* LocalStorage键的Hook
* @returns
*/
export function useLocalStorage<T>(
key: string,
defaultValue?: T,
option?: useLocalStorageOption<T>
): LocalStorageOperates<T> {
const rawValue = localStorage.getItem(key);
const value = isNilOrEmpty(rawValue)
? defaultValue
: equals(option?.raw ?? false, true)
? //@ts-ignore
(rawValue as T)
: notNil(option?.deserializer)
? option.deserializer(rawValue)
: (JSON.parse(rawValue) as T);
const setValue = useCallback((newValue: T) => {
if (equals(option?.raw ?? false, true)) {
//@ts-ignore
localStorage.setItem(key, newValue);
} else {
const serialiedValue = notNil(option?.serializer)
? option.serializer(newValue)
: JSON.stringify(newValue);
//@ts-ignore
localStorage.setItem(key, serialiedValue);
}
}, []);
const removeStorage = useCallback(() => localStorage.removeItem(key), []);
//@ts-ignore
return [value, setValue, removeStorage];
}

View File

@ -0,0 +1,23 @@
//@ts-nocheck
import { notNil } from '@u/funcs';
import { clone, defaultTo, is, isNil, mergeLeft } from 'ramda';
import { useLocation } from 'react-router-dom';
/**
* React Router传递的location的State属性中获取指定的内容
*
* @param extractor State中获取或者计算获取指定形式的值的函数
* @param defaultValue 使
* @returns State中所需要的内容
*/
export function useLocationState<T, R = T>(extractor?: (T) => R, defaultValue?: R): R {
const location = useLocation();
const state = location.state as T;
const extracted = notNil(extractor) && is(Function, extractor) ? extractor(state) : clone(state);
return isNil(defaultValue)
? extracted
: is(Object, defaultValue)
? mergeLeft(extracted, defaultValue)
: defaultTo(defaultValue, extracted);
}

View File

@ -0,0 +1,7 @@
import { SyncCallback } from '@/shared/foundation';
import { useUnmount } from 'react-use';
//在组件卸载的时候调用指定的方法主要用来重置组件所使用到的Store。
export function useResetStore(action?: SyncCallback) {
useUnmount(() => action?.());
}

View File

@ -0,0 +1,14 @@
import { useState } from 'react';
import { useDebounce } from 'react-use';
/**
*
*/
export function useSelectWithSearch() {
// 用户要搜索的内容
const [searchTemp, setSearchTemp] = useState('');
const [search, setSearch] = useState('');
useDebounce(() => setSearch(searchTemp), 800, [searchTemp]);
return [search, setSearchTemp] as [string, typeof setSearchTemp];
}

99
src/hooks/useSession.ts Normal file
View File

@ -0,0 +1,99 @@
//@ts-nocheck
import {SyncCallback, SyncObjectCallback} from '@/shared/foundation';
import {UserSession} from '@/shared/model-user';
import {isNilOrEmpty} from '@u/funcs';
import dayjs, {Dayjs} from 'dayjs';
import {head, is, isNil, toUpper} from 'ramda';
import {useCallback, useMemo} from 'react';
import {useLocalStorage} from './useLocalStorage';
type SessionOperation<T, O = T> = [T | undefined, SyncObjectCallback<O>, SyncCallback];
/**
* Token的一系列方法
* @returns
*/
export function useToken(): SessionOperation<string | null> {
const [value, set, remove] = useLocalStorage<string | null>('token', null, { raw: true });
return [value, set, remove];
}
/**
*
* @returns
*/
export function useExpiresAt(): SessionOperation<Dayjs, string | Dayjs> {
const [value, set, remove] = useLocalStorage<Dayjs>('expires', dayjs('2000-01-01 00:00:00'), {
serializer: value => value.toISOString(),
deserializer: value => dayjs(value)
});
const unifiedSet = useCallback((value: string | Dayjs) => {
if (is(String, value)) {
set(dayjs(value));
} else if (dayjs.isDayjs(value)) {
set(value);
} else {
set(null);
}
}, []);
return [value, unifiedSet, remove];
}
/**
* LocalStorage中
* @returns LocalStorage中的用户信息的一系列操作方法
*/
export function useSession(): SessionOperation<UserSession | null> {
const [uid, setUserId, removeUserId] = useLocalStorage<number | null>('uid', null, {
raw: true
});
const [userName, setUserName, removeUserName] = useLocalStorage<string | null>('name', null, {
raw: true
});
const [userType, setUserType, removeUserType] = useLocalStorage<boolean>('type', -1);
const [userMenu, setUserMenu, removeUserMenu] = useLocalStorage<string[]>('menu', [])
const sessionInfo = useMemo((): UserSession | null => {
const info: UserSession = {
uid: uid,
name: userName,
type: userType,
menu: userMenu
};
return isNilOrEmpty(uid) ? null : info;
}, [uid, userName, userType, userMenu]);
const updateSession = useCallback((value: UserSession) => {
setUserId(value.code);
setUserName(value.name);
setUserType(value.type);
setUserMenu(value.menu);
}, []);
const removeSession = useCallback(() => {
removeUserId();
removeUserName();
removeUserType();
removeUserMenu()
}, []);
return [sessionInfo, updateSession, removeSession];
}
/**
*
* @returns
*/
export function useFirstLetterOfUserName(): string {
const [user] = useSession();
return useMemo(() => {
if (isNil(user)) {
return 'A';
} else {
return toUpper(head(user.name));
}
}, [user]);
}

View File

@ -0,0 +1,12 @@
import 'twin.macro'
export default function(props) {
return <div tw='' style={{marginTop: "20px", marginBottom: "20px",
border: '1px solid #ccc', borderRadius: '5px', display: "inline-block", width: '138px', height: '80px', ...(props.style || {}) }}>
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%'}}>
<div>
{ props.children }
</div>
</div>
</div>
}

View File

@ -0,0 +1,3 @@
export default function(props) {
return <div style={{fontSize: 14, marginTop: "6px", wordBreak: 'break-all'}}> { props.children } </div>
}

View File

@ -0,0 +1,3 @@
export default function(props) {
return <div style={{fontSize: 14, color: "#595959"}}> { props.children } </div>
}

View File

@ -0,0 +1,7 @@
import 'twin.macro'
export default function(props) {
return <div tw='pl-3 pr-3 text-center' style={{fontSize: '20px'}}>
{ props.children }
</div>
}

251
src/pages/report/index.tsx Normal file
View File

@ -0,0 +1,251 @@
import { ContentArea } from '@c/ui/ContentArea';
import { useQuery, } from '@tanstack/react-query';
import { Button, Col, Divider, Row, message } from 'antd';
import React, {FC, useCallback, useRef, useState} from 'react';
import 'twin.macro';
import Logo from 'assets/logo.png'
import { reportTenementDetail } from '@q/publicity';
import { isCorrectResult } from '@u/asyncs';
import Card from './components/Card';
import Unit from './components/Unit';
import { outputWithPrecision } from '@u/funcs';
import html2pdf from "html2pdf.js";
import ReactToPrint from 'react-to-print';
import dayjs from "dayjs";
import TopTitle from './components/TopTitle';
import TopContent from './components/TopContent';
// 核心代码
import { useSearchParams } from 'react-router-dom'
export const UserReport: FC = () => {
const [searchParams] = useSearchParams()
const tid = searchParams.get("tenement");
const rid = searchParams.get("report");
const { data } = useQuery(
['report-tenement-detail', rid, tid],
() => reportTenementDetail(rid, tid),
{
select: res => {
if (isCorrectResult(res)) return res.detail;
message.error(res.message || '获取商户电费详情失败');
return null;
}
}
);
const [downloadLoading, setDownloadLoading] = useState(false);
const printRef = useRef()
const handlePrint = async () => {
const opt = {
margin: 0,
filename: `${data?.tenement?.fullName || '报表'}.pdf`,
image: { type: "jpeg", quality: 0.98, },
enableLinks: true,
html2canvas: { scale: 3, useCORS: true, allowTaint: false, },
jsPDF: {
unit: 'pt', // pt、mm、cm、in
format: 'a4',
orientation: 'p' // 纵向p横向l
},
};
setDownloadLoading(true)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
await html2pdf(printRef.current, opt);
setDownloadLoading(false);
};
const renderTable = useCallback(() => {
return data?.meters?.map((item, index) => {
const finalAmount = Number(item?.overall?.amount || 0) + Number(item?.loss?.amount || 0) + Number(item?.publicAmount || 0)
return (
<tr key={index}>
<td>{item?.code}</td>
<td>{item?.address}</td>
<td>{outputWithPrecision(item?.startNumber, 2, '-')}</td>
<td>{outputWithPrecision(item?.endNumber, 2, '-')}</td>
<td>{outputWithPrecision(item?.displayRatio, 2, '-')}</td>
<td>{outputWithPrecision(item?.overall?.amount, 2, '-')}</td>
{/* <td>{outputWithPrecision(item?.finalTotal ? (1 / (1 - Number(item?.loss?.amount) / Number(item?.finalTotal)) - 1) * 100 : undefined, 2, '-')} %</td> */}
<td>{outputWithPrecision(item?.loss?.amount, 2, '-')}</td>
<td>{outputWithPrecision(item?.publicAmount, 2, '-')}</td>
<td>{outputWithPrecision(finalAmount, 2, '-')}</td>
</tr>
)
})
}, [data?.meters])
return (
<>
<style>
{
`
td {
padding: 0.2cm
}
tr td:nth-child(1) {
width: 3.5cm;
}
tr td:nth-child(2) {
width: 4.5cm;
}
tr td:nth-child(3), tr td:nth-child(4), tr td:nth-child(5) {
width: 1.8cm;
}
tr td:nth-child(6), tr td:nth-child(7), tr td:nth-child(8), tr td:nth-child(9) {
width: 2cm;
}
#content, .ant-col {
font-size: 0.34cm;
}
`
}
</style>
<div style={{position: 'fixed',width: '100%', height: '100%', left: 0, right: 0, top: 0, bottom: 0, margin: 'auto', zIndex: 99, background: '#fff' }}>
<div>
<Button loading={downloadLoading} type='primary' tw='' onClick={() => handlePrint()}> </Button>
</div>
</div>
<div ref={printRef}>
<ContentArea>
<div id="content">
<div>
<img src={Logo} alt="" width={60} height={60}/>
</div>
<div tw='mt-4' style={{fontSize: "18px", fontWeight: 600}}>
{data?.tenement?.fullName} &nbsp;
{dayjs(data?.comprehensive?.startDate).format("MM")}
</div>
<div tw='flex mt-6 flex-row' style={{backgroundColor: "#f0f0f0", padding: '18px 20px'}}>
<div tw='flex flex-1 flex-col'>
<Row gutter={16}>
<Col span={5} style={{display: "flex", flexDirection: "column"}}>
<div></div>
<div style={{fontSize: "20px", fontWeight: 600, flex: 1, display: "flex", alignItems: "center"}}>
{data?.comprehensive?.startDate ? dayjs(data?.comprehensive?.startDate).format("YYYY年MM月") : null}
</div>
{/* <div> {data?.comprehensive?.startDate} - {data?.comprehensive?.endDate} </div> */}
</Col>
<Col span={19}>
<Row gutter={16}>
<Col span={6}> <TopTitle> </TopTitle> <TopContent> {data?.tenement?.id} </TopContent> </Col>
<Col span={6}> <TopTitle></TopTitle><TopContent>{data?.tenement?.fullName}</TopContent> </Col>
<Col span={6}> <TopTitle></TopTitle><TopContent></TopContent> </Col>
<Col span={6}> <TopTitle></TopTitle><TopContent>0.4kv</TopContent> </Col>
</Row>
<Divider dashed tw='mt-2 mb-2 flex flex-col w-full' />
<Row gutter={16}>
<div tw='mt-3 flex-1 flex flex-row'>
<Col span={6}> <TopTitle></TopTitle> <TopContent> {data?.park?.name} </TopContent> </Col>
<Col span={6}> <TopTitle></TopTitle> <TopContent> {data?.tenement?.address} </TopContent> </Col>
<Col span={6}> <TopTitle></TopTitle><TopContent>{data?.comprehensive?.endDate}</TopContent> </Col>
<Col span={6}> <TopTitle></TopTitle><TopContent></TopContent> </Col>
</div>
</Row>
</Col>
</Row>
</div>
</div>
<div>
<div tw='mt-6'>
<Divider tw='mt-2 mb-2 flex flex-col w-full' />
</div>
<div tw='mt-2'>
<div tw='flex flex-row'>
<Card>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.consumption, 2, '-')} &nbsp;&nbsp; </div>
</Card>
</div>
<div tw='mt-3'>
<Divider tw='mt-2 mb-4 flex flex-col w-full' />
</div>
<div tw='flex flex-row items-center' style={{height: 'fit-content', alignItems: "stretch", flexGrow: "stratch" }}>
<div tw='flex flex-row items-center flex-wrap'>
<Unit> </Unit>
<Card>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.consumption, 2, '-')} &nbsp;&nbsp; </div>
</Card>
<Unit> + </Unit>
<Card>
<h4 tw='mt-1 mb-1'> 线 </h4>
<div> {outputWithPrecision(data?.comprehensive?.lossAmount, 2, '-')} &nbsp;&nbsp; </div>
</Card>
<Unit> + </Unit>
<Card>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.publicAmount, 2, '-')} &nbsp;&nbsp; </div>
</Card>
<Unit> </Unit><Unit> * </Unit>
<Card>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.price, 6, '-')} &nbsp;&nbsp;/ </div>
</Card>
<Unit> + </Unit>
<Card>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.basicPooled, 2, '-')} </div>
</Card>
<Unit> + </Unit>
<Card>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.adjustPooled, 2, '-')} </div>
</Card>
</div>
<div tw='flex flex-row items-center' style={{height: '100%', flex: 1}}>
<Unit> = </Unit>
<Card style={{height: 200}}>
<h4 tw='mt-1 mb-1'> </h4>
<div> {outputWithPrecision(data?.comprehensive?.total, 2, '-')} </div>
</Card>
</div>
</div>
</div>
<div>
<div tw='mt-4'>
<Divider tw='mt-2 mb-4 flex flex-col w-full' />
</div>
<div tw="mt-3" style={{fontSize: 12}}>
<table border={1} width={"100%"} cellSpacing="0" >
<thead>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
{/* <td>线损占比</td> */}
<td>线</td>
<td></td>
<td></td>
</tr>
</thead>
<tbody>
{renderTable()}
</tbody>
</table>
</div>
</div>
</div>
</div>
</ContentArea>
</div>
</>
);
};

159
src/queries/accounting.ts Normal file
View File

@ -0,0 +1,159 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import { Record, Related, EditAccountingData, Settlement, BalanceOverall } from '@/shared/model-accounting';
import { downloadFile } from './common';
import { stringify } from 'qs';
import qs from 'qs';
import { UploadExcelError } from '@/shared/model-park';
/**
*
* @param park id
* @param page
* @param keyword
* @param
* @param periodEnd
* @param tenement id
*/
export async function accountingList({
page,
keyword,
periodStart,
periodEnd,
tenement,
type,
medium,
park
}: {
page: number,
keyword?: string,
periodStart?: string,
periodEnd?: string,
tenement?: string,
type?:string,
medium?: string,
park?: string
}): Promise<BaseResponse & Pageable & { records: Record[], amount: number }> {
const response = await ajaxEngine().get(`/accounting?${qs.stringify({
page,
keyword,
periodStart,
periodEnd,
tenement,
type,
medium,
park,
})}`);
return unwrap(response);
}
/**
*
* @param data
*/
export async function createAccounting(data: EditAccountingData): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/accounting`, data);
return unwrap(response);
}
/**
*
* @param id
* @param data
*/
export async function updateAccounting(id: string, data: EditAccountingData): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/accounting/${id}`, data);
return unwrap(response);
}
/**
*
* @param id
*/
export async function deleteAccounting(id: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/accounting/${id}`);
return unwrap(response);
}
/** 下载模板 */
export function download(pid: string) {
downloadFile(`/meter/${pid}/template`);
}
/**
*
*/
export async function approve(data: { username: string, password: string, required: string [] }) {
const response = await ajaxEngine().post(`/authorize`, data);
return unwrap(response);
}
/** 查询结算记录 */
export async function settlementList(
park: string,
page: number,
keyword?: string,
settleStart?: string,
settleEnd?: string,
tenement?: string,
status?: string
): Promise<BaseResponse & Pageable & { settlements: Settlement[] } > {
const response = await ajaxEngine().get(`/settlement/${park}?${stringify({ page, keyword, settleStart, settleEnd, tenement, status } )}`, {
});
return unwrap(response);
}
/** 查询账务余额 */
export async function getAccountingBalance({
parkId,
tenement,
shortName,
page,
code
}: {
parkId: string,
tenement?: string,
shortName?: string,
periodStart?: string,
periodEnd?: string,
page?: number,
code?: string,
}) :Promise<BaseResponse & { related: Related[], overall: BalanceOverall }>{
const response = await ajaxEngine().get(`/accounting/balancing`, {
params: { page, tenement, code, parkId, shortName }
});
return unwrap(response);
}
// 获取商户下所有的表计
export async function getMetersByTenement(tenement: string) {
const response = await ajaxEngine().get(`/accounting/Meter`, {
params: { tenement, }
});
return unwrap(response);
}
/**
*
* @param pid Id
* @param file
* @param onProgress
*/
export async function uploadAccountingBalanceFile(
pid: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/accounting/batch?pid=${pid}`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/** 余额导入模板 */
export function templateAccountingDownload(pid: string) {
return downloadFile(`/accounting/template?park=${pid}`);
}

View File

@ -0,0 +1,55 @@
import { message } from 'antd';
import axios, { AxiosInstance } from 'axios';
import decamelizeKeys from 'decamelize-keys';
import qs from 'qs';
import { isNil } from 'ramda';
//@ts-nocheck
import { MaybeMapFactory } from '@/shared/foundation';
let engineCache: AxiosInstance | null = null;
export const baseUrl = '/api'
/**
* 使AJAX引擎
* @param forceUnauthorized
* @returns AJAX引擎实例
*/
export const ajaxEngine: MaybeMapFactory<AxiosInstance, boolean> = (forceUnauthorized?: boolean) => {
const token = localStorage.getItem('token');
if (isNil(engineCache)) {
engineCache = axios.create({
baseURL: baseUrl,
paramsSerializer: params =>
qs.stringify(decamelizeKeys(params), { indices: false, filter: (prefix, value) => value ?? '' })
});
engineCache.interceptors.response.use(undefined, error => {
switch(error?.response?.status) {
case 403:
// alertError("登录信息已失效")
// history.go(-1)
// window.location.replace("/login")
return
default:
}
if (error?.response?.data?.message || error?.response?.data) {
message.error(error?.response?.data?.message || error?.response?.data);
}
return Promise.reject(error?.response?.data ?? error)}
);
}
if ((forceUnauthorized ?? false) || isNil(token)) {
delete engineCache.defaults.headers.common['Authorization'];
} else {
engineCache.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
return engineCache;
};
export const OK = 200
export function getToken(): string {
return `Bearer ${localStorage.getItem('token')}`
}

336
src/queries/calculate.ts Normal file
View File

@ -0,0 +1,336 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import type {
ReportInfo,
ReportSteps,
SummaryCaculateResult,
ReportMaintenance,
MeterRecord
} from '@/shared/model-calculate';
import type { MaintenanceFormData } from '@q/park';
import { downloadFile } from '@q/common';
import type { UploadExcelError } from '@/shared/model-park';
import { ReportFilled } from '@/shared/model-calculate';
/**
*
* @returns
*/
export async function reportList(): Promise<BaseResponse & { parks: ReportInfo[] }> {
const response = await ajaxEngine().get(`/reports/with/drafts`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function withdraw(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/withdraw/${rid}`);
return unwrap(response);
}
/**
*
* @param pid Id
* @param period
*/
export async function initReport(pid: string, period: string): Promise<BaseResponse & { reportId: string }> {
const response = await ajaxEngine().post(`/park/${pid}/report?period=${period}`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function stepState(rid: string): Promise<BaseResponse & { steps: ReportSteps }> {
const response = await ajaxEngine().get(`/report/${rid}/step/state`);
return unwrap(response);
}
export interface SaveElectricBody {
/** 园区ID */
parkId: string;
/** 核算起始时间 */
periodBegin: string;
/** 核算结束时间 */
periodEnd: string;
/** 线损率 */
authorizedLossRate: string;
/** 调整电费 */
adjustFee: string;
/** 基本电费 */
basicFee: string;
/** 有功(尖峰)电量 */
critical: string;
/** 有功(尖峰)电费 */
criticalFee: string;
/** 有功(总)电量,总电量 */
overall: string;
/** 有功(总)电费,总电费 */
overallFee: string;
/** 有功(峰)电量 */
peak: string;
/** 有功(峰)电费 */
peakFee: string;
/** 有功(谷)电量 */
valley: string;
/** 有功(谷)电费 */
valleyFee: string;
/** 光伏发电电量 */
generateAmount: number;
}
/**
*
* @param data
* @return
*/
export async function calculateElectric(
data: SaveElectricBody
): Promise<BaseResponse & { summary?: SummaryCaculateResult }> {
const response = await ajaxEngine().post(`/report/calculate`, data);
return unwrap(response);
}
/**
*
* @param data
*/
export async function saveElectric(data: SaveElectricBody): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/report`, data);
return unwrap(response);
}
/**
*
* @param rid Id
* @param data
*/
export async function updateElectric(rid: string, data: SaveElectricBody): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/report/${rid}`, data);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function reportMaintenance(rid: string): Promise<BaseResponse & { fees: ReportMaintenance[] }> {
const response = await ajaxEngine().get(`/report/${rid}/maintenance`);
return unwrap(response);
}
/**
*
* @param rid Id
* @param mid ID
*/
export async function deleteMaintenance(rid: string, mid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/report/${rid}/maintenance/${mid}`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function importMaintenance(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/report/${rid}/maintenance/import`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function saveMaintenance(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/report/${rid}/step/diluted/fees`);
return unwrap(response);
}
/**
*
* @param rid Id
* @param data
*/
export async function createTempMaintenance(rid: string, data: MaintenanceFormData): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/report/${rid}/maintenance`, {
...data,
period: data.period.format('YYYY')
});
return unwrap(response);
}
/**
*
* @param rid Id
* @param mid Id
* @param data
*/
export async function editMaintenance(
rid: string,
mid: string,
data: { fee?: string; memo?: string; name?: string }
): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/report/${rid}/maintenance/${mid}`, data);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export function downloadMeterTemplate(rid: string) {
downloadFile(`/report/${rid}/meter/template`);
}
/**
*
* @param rid Id
* @param pid
* @param periodBegin
* @param periodEnd
*/
export async function downloadCalculate(rid: string, pid: string, periodBegin, periodEnd) {
return downloadFile(`/report/export/${rid}/${pid}?periodBegin=${periodBegin}&periodEnd=${periodEnd}`);
}
/**
* Excel
* @param rid Id
* @param file
* @param onProgress
*/
export async function uploadMeterRecordFile(
rid: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/report/${rid}/meter/batch`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/**
*
* @param rid Id
* @param page
* @param keyword
*/
export async function meterRecords(
rid: string,
page: number,
keyword: string
): Promise<BaseResponse & Pageable & { meters: MeterRecord[] }> {
const response = await ajaxEngine().get(`/report/${rid}/submeter`, { params: { page, keyword } });
return unwrap(response);
}
/**
*
* @param rid Id
* @param pid Id
* @param mid
* @param data
*/
export async function editMeterRecords(
rid: string,
pid: string,
mid: string,
data: Partial<MeterRecord>
): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/report/${rid}/submeter/${pid}/${mid}`, data);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function saveMeterStatus(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/report/${rid}/step/meter/register`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function tryCalculate(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/report/${rid}/calculate`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function publishReport(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/report/${rid}/publish`);
return unwrap(response);
}
/**
*
*/
export async function reportsDraft(
user: string
): Promise<BaseResponse & Pageable & { reports: (Omit<ReportInfo, 'user'> & { message?: string })[] }> {
const response = await ajaxEngine().get(`/report/draft`, { params: { user } });
return unwrap(response);
}
/**
*
* @param user
* @param park
* @param page
* @param keyword
* @param periodStart yyyy-MM-dd
* @param periodEnd yyyy-MM-dd
* @param all
*/
export async function reports(
user: string,
park: string,
page: number,
keyword?: string,
periodStart?: string,
periodEnd?: string,
all = 0
): Promise<BaseResponse & Pageable & { reports: ReportInfo[] }> {
const response = await ajaxEngine().get(`/reports`, {
params: { user, park, page, keyword, periodStart, periodEnd, all }
});
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function calculate(rid: string) {
return unwrap(await ajaxEngine().put(`/report/${rid}/calculate`));
}
/**
*
* @param rid Id
*/
export async function publish(rid: string) {
return unwrap(await ajaxEngine().post(`/report/${rid}/publish`));
}
/**
*
*/
export async function getReportFilled(rid: string): Promise<BaseResponse & { summary: ReportFilled }> {
const response = await ajaxEngine().get(`/report/${rid}/summary/filled`);
return unwrap(response);
}

114
src/queries/charge.ts Normal file
View File

@ -0,0 +1,114 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import { ChargeInfo } from '@/shared/model-charge';
import qs from 'qs';
/**
*
* @param pid id
* @param page
* @param keyword
* @param startDate
* @param endDate
* @param typeNumber
*/
export async function chargeList(
pid: string,
page: number,
keyword?: string,
startDate?: string,
endDate?: string,
typeNumber?: number
): Promise<BaseResponse & Pageable & { topUps: ChargeInfo[], amount: number }> {
const response = await ajaxEngine().get(`/topup/${pid}?${qs.stringify({ page, keyword, startDate, endDate, type: typeNumber })}`);
return unwrap(response);
}
/**
*
* @param pid Id
* @param tid Id
*/
export async function cancelCharge(pid: string, tid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/topup/${pid}/${tid}`);
return unwrap(response);
}
/**
*
* @param park Id
* @param keyword
*/
export async function tenementSearchChoice(
park: string,
keyword: string
): Promise<BaseResponse & { tenements: { fullName: string; id: string; park: string; shortName: string; parkName: string }[] }> {
const response = await ajaxEngine().get('/tenement/choice', { params: { park, keyword, limit: 10 } });
return unwrap(response);
}
/**
*
* @param keyword
*/
export async function tenementSearchChoiceByName(
keyword: string
): Promise<BaseResponse & { tenements: { fullName: string; id: string; park: string; shortName: string; parkName: string }[] }> {
const response = await ajaxEngine().get('/tenement/choice', { params: { keyword, limit: 10 } });
return unwrap(response);
}
/**
*
* @param parkid
* @param name
*/
export async function tenementSearchChoiceByShortName(
parkid: string,
name: string,
): Promise<BaseResponse & { tenements: { fullName: string; id: string; park: string; shortName: string }[] }> {
const response = await ajaxEngine().get('/tenement/name', { params: { parkid, name, limit: 10 } });
return unwrap(response);
}
/**
*
* @param pid id
* @param data
*/
export async function createCharge(pid: string, data: {
amount: string;
meter: string;
tenement: string;
paymentType: number,
voucherNo: string,
type: "0" | "1" | "2",
flowType: number,
fileList?: string[]
}) {
const response = await ajaxEngine().post(`/topup/${pid}`, data);
return unwrap(response);
}
/**
*
* @param pid id
* @param tid id
* @param data
*/
export async function updateCharge(tid: string, data: {
amount: string;
meter: string;
tenement: string;
paymentType: number,
voucherNo: string,
type: "0" | "1" | "2",
flowType: number,
fileList?: string[]
}) {
const response = await ajaxEngine().put(`/topup/${tid}`, data);
return unwrap(response);
}

119
src/queries/common.ts Normal file
View File

@ -0,0 +1,119 @@
import type { BaseResponse } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import {alertError} from "@/utils";
/**
*
*/
export interface Region {
/** 行政区划编号 */
code: string;
/** 行政区划级别 [0-3] */
level: number;
/** 行政区划名称 */
name: string;
/** 父级行政区划编号 */
parent: string;
}
/**
*
* @param rid 0
*/
export async function regions(rid: string | 0): Promise<BaseResponse & { regions: Region[] }> {
const response = await ajaxEngine(true).get(`/region/${rid || 0}`);
return unwrap(response);
}
/**
*
* @param rid
*/
export async function regionsDetail(rid: string): Promise<BaseResponse & { regions: Region[] }> {
const response = await ajaxEngine(true).get(`/regions/${rid}`);
return unwrap(response);
}
/**
*
* @param url
*/
export function downloadFile(url: string) {
return fetch(ajaxEngine().defaults.baseURL + url, {
headers: { ...ajaxEngine().defaults.headers.common, Accept: 'application/octet-stream' }
})
.then(async response => {
const content = response.headers.get('content-disposition')
if (content == null) {
alertError("服务错误")
return;
}
const fileName = content.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1];
return { file: await response.blob(), name: decodeURI(fileName) };
})
.then(res => {
const link = document.createElement('a');
link.href = URL.createObjectURL(res.file);
link.download = res.name;
link.click();
URL.revokeObjectURL(link.href);
})
.catch(error => {
console.error('Error downloading file:', error);
});
}
/**
*
*/
export async function expiration(): Promise<BaseResponse & { expiration: string }> {
const response = await ajaxEngine().get(`/expiration`);
return unwrap(response);
}
/**
*
*/
export async function audits(): Promise<BaseResponse & { amounts: { withdraw: number } }> {
const response = await ajaxEngine().get(`/audits`);
return unwrap(response);
}
export interface StatisticResponse {
// 企业用户数量
enterprises: number;
// 园区数量
parks: number;
reports: {
// 园区ID
id: string;
// 园区名称
name: string;
// 最新期数
period: null | string;
}[];
}
/**
*
*/
export async function statistic(): Promise<BaseResponse & { statistics: StatisticResponse }> {
const response = await ajaxEngine().get(`/stat/reports`);
return unwrap(response);
}
/**
* 线
*/
export async function normLossRate(): Promise<BaseResponse & { normAuthorizedLossRate: string }> {
const response = await ajaxEngine().get(`/norm/authorized/loss/rate`);
return unwrap(response);
}
/**
*
*/
export async function normLossTax(): Promise<BaseResponse & { normAuthorizedTaxRate: string }> {
const response = await ajaxEngine().get(`/norm/authorized/tax`);
return unwrap(response);
}

385
src/queries/equipment.ts Normal file
View File

@ -0,0 +1,385 @@
import qs from 'qs';
import {
BatchMeterResult,
BatchModeForm,
BatchParamsForm,
BatchSwitchForm,
BatchTroubleForm,
BatchWarningForm,
BindCardForm,
BindProtocolForm,
CardForm,
CardListItem,
CardListQuery,
ChangeMeterEnabledForm,
FactoryForm,
FactoryListItem,
FactoryListQuery,
MeterOperateListItem,
MeterOperateListQuery,
MeterSettingForm,
MeterSettingListItem,
MeterSettingListQuery,
NestedMeterForm,
NestedMeterListItem,
ProtocolForm,
ProtocolListItem,
ProtocolListQuery
} from '@/shared/equipment';
import type {
BaseResponse,
Pageable
} from '@/shared/model-components';
import { UploadExcelError } from '@/shared/model-park';
import { ajaxEngine } from '@q/axios_instance';
import { downloadFile } from '@q/common';
import { unwrap } from '@u/asyncs';
import { Key } from 'react';
/**
*
* @param query CardListQuery
*/
export async function getCardList(query: CardListQuery): Promise<BaseResponse & Pageable & { data: CardListItem[], amount: number }> {
const response = await ajaxEngine().get(`/equipment/getCardList?${qs.stringify(query)}`, {});
return unwrap(response);
}
/**
*
* @param data CardForm
*/
export async function createCard(data: CardForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/equipment/createCard`, data);
return unwrap(response);
}
/**
*
* @param data CardForm
* @param id string
*/
export async function updateCard(data: CardForm, id: string): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/equipment/updateCard/${id}`, data);
return unwrap(response);
}
/**
*
* @param id string
*/
export async function deleteCard(id: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/equipment/deleteCard/${id}`);
return unwrap(response);
}
/**
*
* @param park id
* @param file
* @param onProgress
*/
export async function uploadCard(
park: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/equipment/uploadCard/${park}`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/**
*
*/
export async function downloadCardTemplate() {
return downloadFile(`/equipment/exportCardTemplate`);
}
/**
*
*/
export async function downloadCard(park: string, query: any) {
return downloadFile(`/equipment/exportCard/${park}?${qs.stringify(query)}`);
}
/**
*
* @param query CardListQuery
*/
export async function getProtocolList(query: ProtocolListQuery): Promise<BaseResponse & Pageable & { data: ProtocolListItem[] }> {
const response = await ajaxEngine().get(`/equipment/getProtocolList?${qs.stringify(query)}`, {});
return unwrap(response);
}
/**
*
*/
export async function downloadProtocolTemplate() {
return downloadFile(`/equipment/exportProtocolTemplate`);
}
/**
*
*/
export async function downloadProtocol(park: string, query: any) {
return downloadFile(`/equipment/exportCard/${park}?${qs.stringify(query)}`);
}
/**
*
* @param park id
* @param file
* @param onProgress
*/
export async function uploadProtocol(
park: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/equipment/uploadCard/${park}`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/**
*
* @param data CardForm
*/
export async function createProtocol(data: ProtocolForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/equipment/createProtocol`, data);
return unwrap(response);
}
/**
*
* @param data CardForm
* @param id string
*/
export async function updateProtocol(data: ProtocolForm, id: string): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/equipment/updateProtocol/${id}`, data);
return unwrap(response);
}
/**
*
* @param id string
*/
export async function deleteProtocol(id: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/equipment/deleteProtocol/${id}`);
return unwrap(response);
}
/**
*
* @param query FactoryListQuery
*/
export async function getFactoryList(query: FactoryListQuery): Promise<BaseResponse & Pageable & { data: FactoryListItem[] }> {
const response = await ajaxEngine().get(`/equipment/getManufacturersList?${qs.stringify(query)}`, {});
return unwrap(response);
}
/**
*
* @param data CardForm
*/
export async function createFactory(data: FactoryForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/equipment/createManufacturers`, data);
return unwrap(response);
}
/**
*
* @param data CardForm
* @param id string
*/
export async function updateFactory(data: FactoryForm, id: string): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/equipment/updateManufacturers/${id}`, data);
return unwrap(response);
}
/**
*
* @param id string
*/
export async function deleteFactory(id: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/equipment/deleteManufacturers/${id}`);
return unwrap(response);
}
/**
*
*/
export async function downloadFactoryTemplate() {
return downloadFile(`/equipment/exportManufacturersTemplate`);
}
/**
*
*/
export async function downloadFactory(park: string, query: any) {
return downloadFile(`/equipment/exportManufacturers/${park}?${qs.stringify(query)}`);
}
/**
*
* @param park id
* @param file
* @param onProgress
*/
export async function uploadFactory(
park: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/equipment/uploadCard/${park}`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/**
*
* @param query MeterSettingListQuery
*/
export async function getMeterSettingList(query: MeterSettingListQuery): Promise<BaseResponse & Pageable & { data: MeterSettingListItem[] }> {
const response = await ajaxEngine().get(`/equipment/getMeterSettingList?${qs.stringify(query)}`, {});
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function bindProtocol(data: BindProtocolForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/equipment/bindProtocol`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function createMeterSetting(pid: string, data: MeterSettingForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/meter/${pid}`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function updateMeterSetting(pid: string, id: string, data: MeterSettingForm): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/meter/${pid}/${id}`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function bindCard(data: BindCardForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/equipment/bindCard`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function batchSetMeterParams(data: BatchParamsForm): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/equipment/setMeterParams`, data);
return unwrap(response);
}
/**
*
* @param query MeterSettingListQuery
*/
export async function getMeterOperateList(query: MeterOperateListQuery): Promise<BaseResponse & Pageable & { data: MeterOperateListItem[] }> {
const response = await ajaxEngine().get(`/equipment/getOperateMeterList?${qs.stringify(query)}`, {});
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function batchSwtich(data: BatchSwitchForm): Promise<BaseResponse & { data: BatchMeterResult[] }> {
const response = await ajaxEngine().put(`/equipment/batchSwtich`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function batchSendMessage(data: {ids: Key[]}): Promise<BaseResponse & { data: BatchMeterResult[] }> {
const response = await ajaxEngine().put(`/equipment/batchSendMessage`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function batchMode(data: BatchModeForm): Promise<BaseResponse & { data: BatchMeterResult[] }> {
const response = await ajaxEngine().put(`/equipment/batchSetMode`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function batchSetWarning(data: BatchWarningForm): Promise<BaseResponse & { data: BatchMeterResult[] }> {
const response = await ajaxEngine().put(`/equipment/batchSetWarning`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function batchSetTrouble(data: BatchTroubleForm): Promise<BaseResponse & { data: BatchMeterResult[] }> {
const response = await ajaxEngine().put(`/equipment/setTrouble`, data);
return unwrap(response);
}
/**
*
* @param query MeterSettingListQuery
*/
export async function getNestedMeterList(park: string, query: { page: number }): Promise<BaseResponse & Pageable & { data: NestedMeterListItem[] }> {
const response = await ajaxEngine().get(`/equipment/getNestedMeterList/${park}?${qs.stringify(query)}`, {});
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function addNestedMeter(data: NestedMeterForm): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/equipment/addNestedMeter`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function changeMeterEnabled(data: ChangeMeterEnabledForm): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/equipment/changeMeterEnabled`, data);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function deleteNestedMeter(id: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/equipment/deleteNestedMeter/${id}`);
return unwrap(response);
}
/**
*
* @param data BindProtocolForm
*/
export async function updateNestedMeter(id: string, data: NestedMeterForm): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/equipment/updateNestedMeter/${id}`, data);
return unwrap(response);
}

74
src/queries/flow.ts Normal file
View File

@ -0,0 +1,74 @@
import { ajaxEngine } from "./axios_instance"
import {unwrap} from "@u/asyncs";
import qs from "qs";
import {BaseResponse} from "@/shared/model-components";
import {ApproveListQuery, DisposeFlow, FlowDetail, FlowForm, FlowListItem} from "@/shared/model-flow";
import {BaseUserInfo} from "@/shared/model-user";
/**
*
* */
export async function getFlowList() : Promise<BaseResponse & { data: FlowListItem[], total: number }> {
const response = await ajaxEngine().get(`/flow/list`)
return unwrap(response)
}
/**
*
* */
export async function createFlow(data: FlowForm) : Promise<BaseResponse> {
const response = await ajaxEngine().post(`/flow/create`, data)
return unwrap(response)
}
/**
*
* */
export async function getApproveList(data: ApproveListQuery) : Promise<BaseResponse & { data: FlowListItem[], total: number }> {
const response = await ajaxEngine().get(`/flow/getDisposeList?${qs.stringify(data)}`)
return unwrap(response)
}
/**
*
* */
export async function updateFlow(id: string, data: FlowForm) : Promise<BaseResponse> {
const response = await ajaxEngine().put(`/flow/${id}/update`, data)
return unwrap(response)
}
/**
*
* */
export async function deleteFlow(id: string) : Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/flow/${id}/delete`)
return unwrap(response)
}
/**
*
* */
export async function getApproveUserList() : Promise<BaseResponse & { data: BaseUserInfo[] }> {
const response = await ajaxEngine().get(`/flow/allUser`)
return unwrap(response)
}
/**
*
* */
export async function disposeFlow(id: string, data: DisposeFlow) : Promise<BaseResponse> {
const response = await ajaxEngine().put(`/flow/${id}/dispose`, data)
return unwrap(response)
}
/**
*
* */
export async function deleteFile(url: string) : Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/file/delete?path=${url}`)
return unwrap(response)
}
/**
*
* */
export async function getApproveDetail(id: string) : Promise<BaseResponse & { data: FlowDetail }> {
const response = await ajaxEngine().get(`/flow/${id}/detail`)
return unwrap(response)
}

182
src/queries/god_mode.ts Normal file
View File

@ -0,0 +1,182 @@
import type { BaseResponse } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
/**
*
* !
* @param rid ID
* @returns
*/
export async function resetReportSummary(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/report/${rid}/summary`);
return unwrap(response);
}
/**
*
* !
* @param rid ID
* @returns
*/
export async function removeReportMaintenanceFees(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/report/${rid}/maintenance`);
return unwrap(response);
}
/**
*
* !
* @param rid ID
* @returns
*/
export async function resetReportEndUserRegister(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/report/${rid}/meters`);
return unwrap(response);
}
/**
*
* !
* @param rid ID
* @returns
*/
export async function resynchronizeReportEndUserRegister(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/gm/report/${rid}/meters`);
return unwrap(response);
}
/**
*
* !
* @param rid ID
* @returns
*/
export async function resetReport(rid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/report/${rid}`);
return unwrap(response);
}
/**
*
* !
* @param rid ID
* @returns
*/
export async function forceRemoveReport(pid: string, rid: string[]): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/report`, { params: { park: pid, reports: rid } });
return unwrap(response);
}
/**
*
* !
* @param pid ID
* @param mid ID
* @returns
*/
export async function removeSpecificMaintenanceFeeInPark(pid: string, mid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/park/${pid}/maintenance/${mid}`);
return unwrap(response);
}
/**
*
* !
* @param id ID
* @returns
*/
export async function removeParkMaintenance(pid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/park/${pid}/maintenance`);
return unwrap(response);
}
/**
*
* !
* @param id ID
* @returns
*/
export async function removeParkMeters(pid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/park/${pid}/meters`);
return unwrap(response);
}
/**
*
* !
* @param pid ID
* @returns
*/
export async function forceRemovePark(pid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/park/${pid}/force`);
return unwrap(response);
}
/**
*
* !
* @param uid ID
* @returns
*/
export async function forceRemoveEnterprise(uid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/enterprise/${uid}/force`);
return unwrap(response);
}
/**
*
* !
*/
export async function forceDelPoolMeters(park: string, meters: string[] = []): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/meter/pooling`, { params: { park, meters } });
return unwrap(response);
}
/**
*
* !
*/
export async function forceDelTenementMeters(
park: string,
tenements: string[] = [],
meters: string[] = []
): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/tenement/meter`, { params: { park, meters, tenements } });
return unwrap(response);
}
/**
*
* !
*/
export async function forceDelReading(
park: string,
meters: string[] = [],
read_at: string[] = []
): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/reading`, { params: { park, meters, read_at } });
return unwrap(response);
}
/**
*
* !
*/
export async function forceDelTenements(park: string, tenements: string[] = []): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/tenement`, { params: { park, tenements } });
return unwrap(response);
}
/**
*
* !
*/
export async function forceDelInvoices(
park: string,
invoices: string[] = [],
tenements: string[] = []
): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/gm/invoice`, { params: { park, invoices, tenements } });
return unwrap(response);
}

162
src/queries/invoice.ts Normal file
View File

@ -0,0 +1,162 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import {cover, InvoiceDetail, InvoiceInfo, TaxMethod, UninvoiceData} from '@/shared/model-invoice';
import qs from 'qs';
import { downloadFile } from './common';
/**
*
* @param park
* @param page
* @param keyword
* @param startDate
* @param endDate
* @param tenement
*/
export async function invoiceList(
park: string,
page: number,
keyword?: string,
startDate?: string,
endDate?: string,
tenement?: string,
): Promise<BaseResponse & Pageable & { invoices: InvoiceInfo[] }> {
const response = await ajaxEngine().get(`/invoice`, { params: { park, page, keyword, startDate, endDate, tenement } });
return unwrap(response);
}
/**
*
* @param code
*/
export async function deleteInvoice(code: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/invoice/${code}`);
return unwrap(response);
}
/**
*
* @param code
*/
export async function invoiceDetail(code: string): Promise<BaseResponse & { detail: InvoiceDetail }> {
const response = await ajaxEngine().get(`/invoice/${code}`);
return unwrap(response);
}
/**
*
* @param tid
*/
export async function uninvoicedTenement({
page,
code,
parkId,
startDate,
endDate,
shortName,
tenement
}: {
page: number,
code: string,
parkId?: string,
startDate: string,
endDate: string,
shortName: string,
tenement: string,
}): Promise<BaseResponse & { records: UninvoiceData[], total: number }> {
const response = await ajaxEngine().get(`/uninvoiced/tenement/report?${qs.stringify({ page, code, parkId, startDate, endDate, shortName, tenement })}`, );
return unwrap(response);
}
export interface PrecalculateInvoiceData {
/** 覆盖核算报表列举已选择的核算报表ID */
covers: cover[];
/** 园区ID */
park: string;
/** 计税方式0核算电费含税1核算电费未税 */
// taxMethod: TaxMethod;
taxMethod: '0' | '1' | '';
/** 税率 */
// taxRate: number;
taxRate: string;
/** 商户ID */
tenement: string;
/** 要核算的表号 */
// codes: string [];
}
/**
*
* @param data
*/
export async function precalculateInvoice(
data: PrecalculateInvoiceData
): Promise<BaseResponse & { cargos: InvoiceDetail['cargos']; total: string }> {
const response = await ajaxEngine().post(`/invoice/precalculate`, data);
return unwrap(response);
}
export interface CreateInvoiceData {
/** 覆盖核算报表列举已选择的核算报表ID */
covers: cover[];
/** 发票号码 */
invoiceNo: string;
/** 发票类型 */
invoiceType: string;
/** 园区ID */
park: string;
/** 计税方式0核算电费含税1核算电费未税 */
taxMethod: TaxMethod;
/** 税率 */
taxRate: number;
/** 商户ID */
tenement: string;
}
interface coverItem {
rid: string,
code: string,
}
export interface CreateInvoice {
/** 覆盖核算报表列举已选择的核算报表ID */
covers: coverItem[];
/** 发票号码 */
invoiceNo: string;
/** 发票类型 */
invoiceType: string;
/** 园区ID */
park: string;
/** 计税方式0核算电费含税1核算电费未税 */
taxMethod: '0' | '1';
/** 税率 */
taxRate: string;
/** 商户ID */
tenement: string;
}
/**
*
* @param data
*/
export async function createInvoice(data: CreateInvoice): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/invoice`, data);
return unwrap(response);
}
/**
*
*/
export async function updateInvoiceNo(id: string, invoiceNo: string): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/invoice`, { "invoice_id": id, "invoice_no": invoiceNo});
return unwrap(response);
}
/**
*
*/
export async function downloadInvoice(query) {
return downloadFile(`/invoice/export?${qs.stringify(query)}`);
}

16
src/queries/log.ts Normal file
View File

@ -0,0 +1,16 @@
import { ajaxEngine } from "./axios_instance"
import {unwrap} from "@u/asyncs";
import qs from "qs";
import type {BaseResponse} from "@/shared/model-components";
import {Log} from "@/shared/model-log";
/**
* 退
*
* */
export async function getLogList(page: number, startDate: string, endDate: string, type: number): Promise<BaseResponse & { data: Log[], total: number }> {
const response = await ajaxEngine().get(`/logs/list?${qs.stringify({
page, startDate, endDate, type
})}`)
return unwrap(response)
}

634
src/queries/park.ts Normal file
View File

@ -0,0 +1,634 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import type {
ElectrCategory,
MaintenanceInfo,
MaintenanceSummary,
Meter04KvInfo,
Meter04KvType, MeterBox,
ParkInfo,
UploadExcelError
} from '@/shared/model-park';
import {
ExchangeFormData,
MeterChoiceData,
MeterPooled,
MeterReading,
MeterType,
ParkBuilding,
TenementInfo
} from '@/shared/model-park';
import { ajaxEngine } from '@q/axios_instance';
import { downloadFile } from '@q/common';
import { unwrap } from '@u/asyncs';
import { Dayjs } from 'dayjs';
import qs from 'qs';
/**
*
* @param keyword
* @returns
*/
export async function parkList(keyword: string): Promise<BaseResponse & { parks: ParkInfo[] }> {
const response = await ajaxEngine().get(`/park`, {
params: { keyword }
});
return unwrap(response);
}
/**
*
* @param uid id
* @returns
*/
export async function userParkList(uid: string): Promise<BaseResponse & { parks: ParkInfo[] }> {
const response = await ajaxEngine().get(`/park/belongs/${uid}`);
return unwrap(response);
}
/**
*
* @param pid Id
* @param enabled
*/
export async function changeParkStatus(pid: string, enabled: boolean): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/park/${pid}/enabled`, { enabled });
return unwrap(response);
}
/**
*
* @param pid Id
*/
export async function deletePark(pid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/park/${pid}`);
return unwrap(response);
}
/**
*
* @param pid Id
*/
export async function parkDetail(pid: string): Promise<BaseResponse & { park: ParkInfo }> {
const response = await ajaxEngine().get(`/park/${pid}`);
return unwrap(response);
}
/**
*
* @param uid Id
*/
export async function enterpriseParkList(uid: string): Promise<BaseResponse & { parks: ParkInfo[] }> {
const response = await ajaxEngine().get(`/park/belongs/${uid}`);
return unwrap(response);
}
export interface ChangeParkInfoParams {
address?: string;
area?: string;
capacity?: string;
category: ElectrCategory;
contact?: string;
name: string;
phone?: string;
region?: string;
submeter: Meter04KvType;
tenement?: string;
}
/**
*
* @param data
*/
export async function createPark(data: Required<ChangeParkInfoParams>): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/park`, data);
return unwrap(response);
}
/**
*
* @param pid Id
* @param data
*/
export async function changeParkInfo(pid: string, data: ChangeParkInfoParams): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/park/${pid}`, data);
return unwrap(response);
}
/**
* 0.4kv表计
* @param pid id
* @param page
* @param keyword
* @param meterType
* @returns
*/
export async function meter04List(
pid: string,
page: number,
keyword: string,
meterType?: MeterType
): Promise<BaseResponse & Pageable & { meters: Meter04KvInfo[] }> {
const response = await ajaxEngine().get(`/meter/${pid}`, { params: { page, keyword, type: meterType } });
return unwrap(response);
}
/**
* 0.4KV表计档案模板
* @param pid id
*/
export function template04Download(pid: string) {
return downloadFile(`/meter/${pid}/template`);
}
/**
* 0.4Kv表计
* @param pid Id
* @param data 0.4Kv表计信息
*/
export async function create04KvMeter(pid: string, data: Meter04KvInfo): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/meter/${pid}`, data);
return unwrap(response);
}
/**
* 0.4Kv表计信息
* @param pid Id
* @param data 0.4Kv表计信息
*/
export async function change04KvMeterInfo(pid: string, data: Meter04KvInfo): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/meter/${pid}/${data.code}`, data);
return unwrap(response);
}
/**
*
* @param pid Id
* @param code
* @param data
*/
export async function exchangeMeter(pid: string, code: string, data: ExchangeFormData): Promise<BaseResponse> {
const response = await ajaxEngine().patch(`/meter/${pid}/${code}`, data);
return unwrap(response);
}
/**
* 0.4kv表计的详细信息
* @param pid Id
* @param code
*/
export async function getMeterInfo(pid: string, code: string): Promise<BaseResponse & { meter?: Meter04KvInfo }> {
const response = await ajaxEngine().get(`/meter/${pid}/${code}`);
return unwrap(response);
}
/**
* Excel文件
* @param pid Id
* @param file
* @param onProgress
*/
export async function upload04MeterFile(
pid: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/meter/${pid}/batch`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/**
*
* @param page
* @param pid id
* @param period
* @returns
*/
export async function maintenanceList(
page: number,
pid?: string,
period?: string
): Promise<BaseResponse & Pageable & { fees: MaintenanceInfo[] }> {
const response = await ajaxEngine().get(`/maintenance/fee`, {
params: { page, park: pid, period }
});
return unwrap(response);
}
/**
*
* @param mid id
*/
export async function deleteMaintenance(mid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/maintenance/fee/${mid}`);
return unwrap(response);
}
/**
*
* @param mid id
* @param enabled
*/
export async function changeMaintenance(mid: string, enabled: boolean): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/maintenance/fee/${mid}/enabled`, { enabled });
return unwrap(response);
}
export interface MaintenanceFormData {
fee: string;
memo: string;
name: string;
period: Dayjs;
parkId: string;
}
/**
*
* @param data
*/
export async function createMaintenance(data: MaintenanceFormData): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/maintenance/fee`, {
...data,
period: data.period.format('YYYY')
});
return unwrap(response);
}
/**
*
* @param mid id
* @param data
*/
export async function changeMaintenanceInfo(
mid: string,
data: { fee: string; memo: string; name: string }
): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/maintenance/fee/${mid}`, data);
return unwrap(response);
}
/**
*
* @param page
* @param uid ID
* @param pid ID
* @param period
* @param keyword
* @returns
*/
export async function retrieveAdditionalChargeStatistics(
page: number,
uid?: string,
pid?: string,
period?: string,
keyword?: string
): Promise<BaseResponse & Pageable & { charges?: MaintenanceSummary[] }> {
const response = await ajaxEngine().get('/additional/charges', {
params: {
page,
user: uid ?? '',
park: pid ?? '',
period: period ?? '',
keyword: keyword ?? ''
}
});
return unwrap(response);
}
/**
*
* @param pid id
* @param keyword
*/
export async function getParkBuildings(pid: string, keyword?: string): Promise<BaseResponse & { buildings?: ParkBuilding[] }> {
const response = await ajaxEngine().get(`/park/${pid}/building`, { params: { keyword } });
return unwrap(response);
}
/**
*
* @param pid id
* @param bid id
* @param enabled
*/
export async function changeBuildingStatus(pid: string, bid: string, enabled: boolean): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/park/${pid}/building/${bid}/enabled`, { enabled });
return unwrap(response);
}
/**
*
* @param pid id
* @param bid id
* @param info
*/
export async function changeBuildingInfo(
pid: string,
bid: string,
info: { name: string; floors: string }
): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/park/${pid}/building/${bid}`, info);
return unwrap(response);
}
/**
*
* @param pid id
* @param info
*/
export async function createBuilding(pid: string, info: { name: string; floors: string }): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/park/${pid}/building`, info);
return unwrap(response);
}
/**
*
* @param pid id
* @param bid id
*/
export async function deleteBuilding(pid: string, bid: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/park/${pid}/building/${bid}`);
return unwrap(response);
}
/**
*
* @param pid id
* @param page
* @param keyword
* @returns
*/
export async function meterPooledList(
pid: string,
page: number,
keyword: string
): Promise<BaseResponse & Pageable & { meters: MeterPooled[] }> {
const response = await ajaxEngine().get(`/meter/${pid}/pooled`, { params: { page, keyword } });
return unwrap(response);
}
/**
*
* @param park
* @param keyword
* @param limit
* @returns
*/
export async function pooledChoiceList(
park: string,
keyword: string,
limit?: number
): Promise<BaseResponse & { meters: MeterChoiceData[] }> {
const response = await ajaxEngine().get('/meter/choice', { params: { park, keyword, limit: limit || 6 } });
return unwrap(response);
}
/**
*
* @param pid Id
* @param mcode
* @param scode
*/
export async function unBindMeter(pid: string, mcode: string, scode: string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/meter/${pid}/${mcode}/binding/${scode}`);
return unwrap(response);
}
/**
*
* @param pid id
* @param code
* @param meters
*/
export async function bindMeters(pid: string, code: string, meters: string[]): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/meter/${pid}/${code}/binding`, meters);
return unwrap(response);
}
/**
*
* @param pid id
* @param page
* @param keyword
* @param state 01
* @param building
* @param startDate
* @param endDate
* @param code
*/
export async function tenementList(
pid: string,
page: number,
state: 0 | 1,
keyword?: string,
building?: string,
startDate?: string,
endDate?: string,
code?: string,
): Promise<BaseResponse & Pageable & { tenements: TenementInfo[] }> {
const response = await ajaxEngine().get(`/tenement/${pid}`, {
params: { page, keyword, building, startDate, endDate, state, code }
});
return unwrap(response);
}
/**
*
* @param pid id
* @param tid id
* @param code
*/
// 2023-10-26 17:15后端需要一个code参数在商户查询绑定表计的时候传一个非空字符串其他情况传空字符串
export async function tenementMeters(
pid: string,
tid: string,
code?: string
): Promise<BaseResponse & Pageable & { meters: Meter04KvInfo[] }> {
const response = await ajaxEngine().get(`/tenement/${pid}/${tid}/meter`,{ params: { code } });
return unwrap(response);
}
/**
*
* @param pid Id
* @param tid Id
* @param code
* @param data
*/
export async function unbindTenementMeter(
pid: string,
tid: string,
code: string,
data: MeterReading
): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/tenement/${pid}/${tid}/binding/${code}/unbind`, data);
return unwrap(response);
}
/**
*
*/
export interface MoveOutFormData extends MeterReading {
code: string;
readAt: string;
}
/**
*
* @param pid Id
* @param tid Id
* @param data
*/
export async function moveOutTenement(pid: string, tid: string, data: MoveOutFormData[]): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/tenement/${pid}/${tid}/move/out`, data);
return unwrap(response);
}
/**
*
* @param park Id
* @param keyword
*/
export async function unBindTenementMeters(park: string, keyword: string): Promise<BaseResponse & { meters: MeterChoiceData[] }> {
const response = await ajaxEngine().get(`/meter/choice/tenement`, { params: { keyword, park } });
return unwrap(response);
}
/**
*
* @param pid Id
* @param tid Id
* @param data
*/
export async function bindTenementMeter(pid: string, tid: string, data: MoveOutFormData): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/tenement/${pid}/${tid}/binding`, data);
return unwrap(response);
}
export interface CreateTenement {
/** 开户账号,发票用 */
account: string;
/** 联系地址 */
address: string;
/** 开户行名称,发票用 */
bank: string;
/** 所在建筑ID */
building: string;
/** 联系人 */
contact: string;
/** 注册地址,发票用 */
invoiceAddress: string;
/** 注册电话,发票用 */
invoicePhone: string;
/** 商户的名称 */
name: string;
/** 所在建筑层数 */
onFloor: string;
/** 联系电话 */
phone: string;
/** 商户简称 */
shortName: string;
/** 社会统一信用代码,发票用 */
usci: string;
/** 入住日期 */
moveIn: string;
}
/**
*
* @param pid Id
* @param data
*/
export async function createTenement(pid: string, data: CreateTenement): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/tenement/${pid}`, data);
return unwrap(response);
}
/**
*
* @param pid Id
* @param tid Id
* @param data
*/
export async function updateTenement(pid: string, tid: string, data: CreateTenement): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/tenement/${pid}/${tid}`, data);
return unwrap(response);
}
export interface TenementInvoiceInfo {
/** 开户账号 */
account: string;
/** 注册地址 */
address: string;
/** 开户行名称 */
bank: string;
/** 开票人 */
name: string;
/** 注册电话 */
phone: string;
/** 开票人社会统一信用代码 */
usci: string;
}
/**
*
* @param pid id
* @param tid id
*/
export async function tenementDetail(
pid: string,
tid: string
): Promise<BaseResponse & { tenement: TenementInfo & { invoiceInfo?: TenementInvoiceInfo } }> {
const response = await ajaxEngine().get(`/tenement/${pid}/${tid}`);
return unwrap(response);
}
/**
*
*/
export async function downloadMeterRecords(query) {
return downloadFile(`/meter/export?${qs.stringify(query)}`);
}
/**
*
*/
export async function downloadTenementRecords(query) {
return downloadFile(`/tenement/export?${qs.stringify(query)}`);
}
/**
*
*/
export async function downloadTenementMeterRecords(query) {
return downloadFile(`/tenementMeter/export?${qs.stringify(query)}`);
}
/**
*
*/
export async function createMeterBox(data: MeterBox): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/box`, data);
return unwrap(response);
}
/**
*
*/
export async function updateMeterBox(data: MeterBox): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/box/batch`, data);
return unwrap(response);
}
/**
*
*/
export async function deleteMeterBox(id:string): Promise<BaseResponse> {
const response = await ajaxEngine().delete(`/box/${id}`);
return unwrap(response);
}
/**
*
*/
export async function getMeterBoxList(pid:string, page: number, keyword: string, building: string): Promise<BaseResponse & { data: MeterBox[] }> {
const response = await ajaxEngine().get(`/box/${pid}`,{ params: {page, keyword, building} });
return unwrap(response);
}

32
src/queries/permission.ts Normal file
View File

@ -0,0 +1,32 @@
import { BaseResponse } from "@/shared/model-components";
import { ajaxEngine } from "./axios_instance";
import { unwrap } from '@u/asyncs';
/**
*
* @param role
*/
export async function getRolePermission(
role: string
): Promise<BaseResponse & { data: string[], forbid_list: string[] }> {
const response = await ajaxEngine().get(`/permission/${role}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return unwrap(response);
}
/**
*
* @param role
* @param data
*/
export async function updateRolePermission(
role: string,
data: string[],
): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/permission`, {
role,data
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return unwrap(response);
}

188
src/queries/publicity.ts Normal file
View File

@ -0,0 +1,188 @@
import { Consumption } from '@/shared/model-calculate';
import type { BaseResponse, Pageable } from '@/shared/model-components';
import type { BasePublicityInfo, PublicityEndUserPeriodStatistics, ReportTenement, errorMessageItem } from '@/shared/model-publicity';
import { ParkSummary, PublicPooled, ReportTenementDetail } from '@/shared/model-publicity';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import {downloadFile} from "@q/common";
/**
*
* @param page
* @param keyword
* @returns
*/
export async function auditList(
page: number,
keyword: string
): Promise<BaseResponse & Pageable & { records: BasePublicityInfo[] }> {
const response = await ajaxEngine().get(`/withdraw`, { params: { keyword } });
return unwrap(response);
}
/**
* /
* @param rid Id
* @param audit
*/
export async function auditReport(rid: string, audit: boolean): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/withdraw/${rid}`, { audit });
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function reportDetail(
rid: string
): Promise<BaseResponse & { detail: Omit<BasePublicityInfo, 'user'> & { enterprise: BasePublicityInfo['user'] } }> {
const response = await ajaxEngine().get(`/report/${rid}`);
return unwrap(response);
}
/**
*
* @param rid Id
*/
export async function reportParkDetail(rid: string): Promise<BaseResponse & { summary: ParkSummary }> {
const response = await ajaxEngine().get(`/report/${rid}/summary`);
return unwrap(response);
}
/**
*
* @param rid Id
* @param page
* @param keyword
*/
export async function reportPublicFee(
rid: string,
page: number,
keyword?: string
): Promise<BaseResponse & Pageable & { public: (PublicPooled & { adjustLoss: Consumption })[] }> {
const response = await ajaxEngine().get(`/report/${rid}/publics`, { params: { page, keyword } });
return unwrap(response);
}
/**
*
* @param rid Id
* @param page
* @param keyword
*/
export async function reportPublicPooled(
rid: string,
page: number,
keyword?: string
): Promise<BaseResponse & Pageable & { pooled: PublicPooled[] }> {
const response = await ajaxEngine().get(`/report/${rid}/pooled`, { params: { page, keyword } });
return unwrap(response);
}
/**
*
* @param rid Id
* @param code
*/
export async function reportPooledSubmeter(
rid: string,
code: string
): Promise<BaseResponse & { meters: PublicPooled[] }> {
const response = await ajaxEngine().get(`/report/${rid}/pooled/${code}/submeter`);
return unwrap(response);
}
/**
*
* @param rid Id
* @param tid Id
*/
export async function reportTenementDetail(
rid: string,
tid: string
): Promise<BaseResponse & { detail: ReportTenementDetail, amount: string }> {
const response = await ajaxEngine().get(`/report/${rid}/tenement/${tid}`);
return unwrap(response);
}
/**
*
* @param rid Id
* @param page
* @param keyword
*/
export async function reportTenement(
rid: string,
page: number,
keyword?: string
): Promise<BaseResponse & Pageable & { tenements: ReportTenement[] }> {
const response = await ajaxEngine().get(`/report/${rid}/tenement`, { params: { page, keyword } });
return unwrap(response);
}
/**
*
* @param page
* @param keyword
* @param period
* @param park
* @param user
*/
export async function reportList(
page: number,
keyword: string,
user: string,
park: string,
period: string
): Promise<BaseResponse & Pageable & { reports: BasePublicityInfo[] }> {
const response = await ajaxEngine().get(`/reports`, {
params: { page, keyword, user, park, periodStart: period, periodEnd: period, all: 1 }
});
return unwrap(response);
}
/**
*
* @param uid ID
* @param pid ID
* @param start YYYY-MM
* @param end YYYY-MM
*/
export async function retrieveEndUserStat(
uid?: string,
pid?: string,
start?: string,
end?: string
): Promise<BaseResponse & { details?: PublicityEndUserPeriodStatistics[] }> {
const response = await ajaxEngine().get(`/end/user/adjusts`, {
params: {
user: uid ?? '',
park: pid ?? '',
start: start ?? '',
end: end ?? ''
}
});
return unwrap(response);
}
/**
*
* @param rid id
* */
export async function getErrorList(
rid: string,
): Promise<BaseResponse & {errType: number} & { message: errorMessageItem[] }> {
const response = await ajaxEngine().get(`/report/error/list?rid=${rid}`);
return unwrap(response);
}
/**
*
* @param rid Id
* @param pid
*/
export async function downloadReport(rid: string, pid: string) {
return downloadFile(`/report/export/user/${rid}/${pid}`);
}

View File

@ -0,0 +1,13 @@
import { QueryClient } from '@tanstack/react-query';
/**
* React Query的查询客户端
*/
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: true,
refetchOnReconnect: true
}
}
});

146
src/queries/reading.ts Normal file
View File

@ -0,0 +1,146 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import {ReadingInfo, Refund, RefundForm} from '@/shared/model-reading';
import { MeterChoiceData, MeterType, UploadExcelError } from '@/shared/model-park';
import { downloadFile } from '@q/common';
import qs from "qs";
/**
*
* @param pid id
* @param page
* @param keyword
* @param building
* @param startDate
* @param endDate
* @param type
*/
export async function readingList(
pid: string,
page: number,
keyword?: string,
building?: string,
startDate?: string,
endDate?: string,
type: MeterType = undefined
): Promise<BaseResponse & Pageable & { records: ReadingInfo[] }> {
const response = await ajaxEngine().get(`/reading/${pid}`, {
params: { page, keyword, building, startDate, endDate, type }
});
return unwrap(response);
}
/**
*
* @returns
* @param park
* @param keyword
*/
export async function meterChoiceList(
park: string,
keyword: string
): Promise<BaseResponse & { meters: MeterChoiceData[] }> {
const response = await ajaxEngine().get(`/reading/${park}/choice`, { params: { keyword, limit: 10 } });
return unwrap(response);
}
export type CreateReadingData = ReadingInfo & { code: string; readAt: string };
/**
*
* @param pid id
* @param data
*/
export async function createReading(pid: string, data: CreateReadingData) {
const response = await ajaxEngine().post(`/reading/${pid}/${data.code}`, data);
return unwrap(response);
}
/**
*
* @param pid id
* @param readAtTimestamp
* @param data
*/
export async function updateReading(pid: string, readAtTimestamp: string, data: CreateReadingData) {
const response = await ajaxEngine().put(`/reading/${pid}/${data.code}/${readAtTimestamp}`, data);
return unwrap(response);
}
/**
*
* @param pid id
*/
export function downloadReadingTemplate(pid: string) {
downloadFile(`/reading/${pid}/template`);
}
/**
*
* @param pid id
* @param file
* @param onProgress
*/
export async function uploadReadingTemplateFile(
pid: string,
file: File,
onProgress?: (percent: number) => void
): Promise<BaseResponse & { errors: UploadExcelError[] | null }> {
const response = await ajaxEngine().postForm(
`/reading/${pid}/batch`,
{ data: file },
{ onUploadProgress: (e: ProgressEvent) => onProgress?.(e.loaded / e.total) }
);
return unwrap(response);
}
/**
* 退
* @param park
* @param page
* @param startDate
* @param endDate
* @param keyword
*/
export async function getRefundList(page: number, park?:string, startDate?:string, endDate?:string, keyword?: string): Promise<BaseResponse & {data: Refund[], total: number}> {
const response = await ajaxEngine().get(`/read/${park}/electric?${qs.stringify({
page,
park,
start: startDate,
end: endDate,
keyword
})}`, );
return unwrap(response);
}
/**
* 退
* @param park
* @param data
*/
export async function createRefund(park: string, data: RefundForm): Promise<BaseResponse & {data: Refund[]}> {
const response = await ajaxEngine().post(`/read/${park}/electric`, data);
return unwrap(response);
}
/**
* 退
* @param park
* @param year
*/
export async function getDisabledMonth(park: string, year: number): Promise<BaseResponse & {data: string[]}> {
const response = await ajaxEngine().get(`/read/${park}/period`, {
params: {
year
}
});
return unwrap(response);
}
/**
* 退
* */
export async function deleteRefund(id: string) {
const response = await ajaxEngine().delete(`/read/${id}`)
return unwrap(response)
}

52
src/queries/session.ts Normal file
View File

@ -0,0 +1,52 @@
import { BaseResponse } from '@/shared/model-components';
import { UserSession } from '@/shared/model-user';
import { unwrap } from '@u/asyncs';
import { ajaxEngine } from './axios_instance';
import { privilege } from '@/shared/model-sonaccount';
/**
*
*/
export interface LoginResponse extends BaseResponse {
/**
*
*/
needReset: boolean;
/**
*
*/
session?: UserSession | null;
/** 角色 */
privileges?: privilege[],
/** 权限列表 */
roles?: []
}
/**
*
* @param username
* @param password
* @param type 使01
* @returns
*/
export async function login(
username: string,
password: string,
type: number
): Promise<LoginResponse> {
const response = await ajaxEngine(true).post<LoginResponse>(`/login`, {
uname: username,
upass: password,
type
});
return unwrap(response);
}
/**
*
* @returns
*/
export async function logout(): Promise<BaseResponse> {
const response = await ajaxEngine().delete<BaseResponse>(`/login`);
return unwrap(response);
}

View File

@ -0,0 +1,83 @@
import { BaseResponse, Pageable } from '@/shared/model-components';
import { unwrap } from '@u/asyncs';
import { ajaxEngine } from './axios_instance';
import { UserType } from '@/shared/model-user';
import { SonAccountItem, SubmitSonAccount } from '@/shared/model-sonaccount';
export interface SonAccountResponse extends BaseResponse, Pageable {
accounts: SonAccountItem[];
}
export interface SonAccountListQuery {
keyword?: string, type?: UserType, page?: number, state?: boolean
}
/**
*
* @returns
* @param params
*/
export async function getSonAccountList(
params: SonAccountListQuery
): Promise<SonAccountResponse> {
const response = await ajaxEngine().get<SonAccountResponse>(`/subaccount`, { params });
return unwrap(response);
}
/**
*
* @param uid Id
* @param enabled
*/
export async function changeSonAccountStatus(uid: string, enabled: boolean): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/subaccount/${uid}/enabled`, {
enabled
});
return unwrap(response);
}
/**
*
* @param uid Id
* @return
*/
export async function resetPassword(uid: string) {
const response = await ajaxEngine().delete<BaseResponse & { verify: string }>(`/subaccount/${uid}/password`);
return unwrap(response);
}
export interface ChangeUserInfoParams {
username?: string;
contact?: string;
name?: string;
phone?: string;
region?: string;
unitServiceFee?: number;
}
/**
*
* @param uid
* @param data
*/
export async function changeUserInfo(uid: string, data: SonAccountItem): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/subaccount/${uid}`, data);
return unwrap(response);
}
/**
*
* @param data
*/
export async function createUser(data: SubmitSonAccount): Promise<BaseResponse & { verify: string }> {
const response = await ajaxEngine().post(`/subaccount`, data);
return unwrap(response);
}
/**
*
*/
export async function getPrivileges(): Promise<BaseResponse & { privileges: SonAccountItem['privileges'] }> {
const response = await ajaxEngine().get<BaseResponse & { privileges: SonAccountItem['privileges'] }>(`/privileges`);
return unwrap(response);
}

153
src/queries/statement.ts Normal file
View File

@ -0,0 +1,153 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import qs from 'qs';
import {
AccountingStatement,
DefaultQuery,
ElectricDetailStatement,
ElectricStatement, InvoiceStatement,
RechargeStatement, TenementStatement
} from '@/shared/model-statement';
import {downloadFile} from "@q/common";
/**
*
* @param park id
* @param page
* @param startDate
* @param endDate
*/
export async function rechargeStatement({
page,
startDate,
endDate,
park,
}: DefaultQuery): Promise<BaseResponse & Pageable & { data: RechargeStatement[] }> {
const response = await ajaxEngine().get(`/statement/recharge?${qs.stringify({
page,
startDate,
endDate,
park,
})}`);
return unwrap(response);
}
/**
*
* @param park id
* @param page
* @param startDate
* @param endDate
*/
export async function electricStatement({
page,
startDate,
endDate,
park,
}: DefaultQuery): Promise<BaseResponse & Pageable & { data: ElectricStatement[] }> {
const response = await ajaxEngine().get(`/statement/electric?${qs.stringify({
page,
startDate,
endDate,
park,
})}`);
return unwrap(response);
}
/**
*
* @param park id
* @param page
* @param startDate
* @param endDate
*/
export async function electricDetailStatement({
page,
startDate,
endDate,
park,
}: DefaultQuery): Promise<BaseResponse & Pageable & { data: ElectricDetailStatement[] }> {
const response = await ajaxEngine().get(`/statement/electricDetail?${qs.stringify({
page,
startDate,
endDate,
park,
})}`);
return unwrap(response);
}
/**
*
* @param park id
* @param page
* @param startDate
* @param endDate
*/
export async function accountingStatement({
page,
startDate,
endDate,
park,
}: DefaultQuery): Promise<BaseResponse & Pageable & { data: AccountingStatement[] }> {
const response = await ajaxEngine().get(`/statement/accounting?${qs.stringify({
page,
startDate,
endDate,
park,
})}`);
return unwrap(response);
}
/**
*
* @param park id
* @param page
* @param startDate
* @param endDate
*/
export async function invoiceStatement({
page,
startDate,
endDate,
park,
}: DefaultQuery): Promise<BaseResponse & Pageable & { data: InvoiceStatement[] }> {
const response = await ajaxEngine().get(`/statement/invoice?${qs.stringify({
page,
startDate,
endDate,
park,
})}`);
return unwrap(response);
}
/**
*
* @param park id
* @param page
* @param startDate
* @param endDate
*/
export async function tenementStatement({
page,
startDate,
endDate,
park,
}: DefaultQuery): Promise<BaseResponse & Pageable & { data: TenementStatement[] }> {
const response = await ajaxEngine().get(`/statement/tenement?${qs.stringify({
page,
startDate,
endDate,
park,
})}`);
return unwrap(response);
}
/**
*
* @param park Id
* @param type
*/
export function downloadStatement(park: string, type: number) {
downloadFile(`/statement/export?park=${park}&type=${type}`).then(() => {});
}

92
src/queries/sync.ts Normal file
View File

@ -0,0 +1,92 @@
import type { BaseResponse, Pageable } from '@/shared/model-components';
import { ajaxEngine } from '@q/axios_instance';
import { unwrap } from '@u/asyncs';
import {ParkSync, ReadingInterval, ReadingRetryAlgorithm, ReadingType, SyncTask} from '@/shared/model-sync';
/**
*
* @param page
* @param keyword
*/
export async function syncList(
page: number,
keyword?: string,
): Promise<BaseResponse & Pageable & { tasks: SyncTask[] }> {
const response = await ajaxEngine().get(`/synchronize/task`, { params: { keyword, page } });
return unwrap(response);
}
/**
*
*/
export async function pauseSyncTask(pid: string, tid: string): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/synchronize/task/${pid}/${tid}/pause`);
return unwrap(response);
}
/**
*
*/
export async function startSyncTask(pid: string, tid: string): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/synchronize/task/${pid}/${tid}/enqueue`);
return unwrap(response);
}
export interface SyncSettingData {
/** 企业ID */
entId: string;
/** 采集系统型号 */
imrs: string;
/** 同步登录账号 */
imrsAccount: string;
/** 同步登录私钥Base64或者私钥文件内容 */
imrsKey: string;
/** 同步登录密钥,加盐双向加密 */
imrsSecret: string;
/** 采集周期0每小时1每日2每周3每月 */
interval: ReadingInterval;
/** 最大重试次数 */
maxRetries: string;
/** 园区ID */
parkId: string;
/** 采集方式0自动+人工1自动2人工 */
readingType: ReadingType;
/** 重试间隔算法0指数退避12倍线性间隔23倍线性间隔3固定间隔 */
retryAlgorithm: ReadingRetryAlgorithm;
/** 重试间隔,基础间隔时间,根据间隔算法不同会产生不同的间隔 */
retryInterval: string;
/** 采集时间格式HH:mm */
collectAt: string;
}
/**
*
*/
export async function changeSyncSetting(uid: string, data: SyncSettingData): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/synchronize/configuration`, data);
return unwrap(response);
}
/**
*
*/
export async function getSyncSetting(pid: string, uid: string): Promise<BaseResponse & { setup: SyncSettingData }> {
const response = await ajaxEngine().get(`/synchronize/configuration`, { params: { user: uid, park: pid } });
return unwrap(response);
}
/**
*
*/
export async function getSyncRechargeRecords(pid: string, pageNum: number): Promise<BaseResponse & { data: ParkSync[], total: number }> {
const response = await ajaxEngine().get(`/synchronize/task/${pid}/getRechargeRecords?pageNum=${pageNum}`,);
return unwrap(response);
}
/**
*
*/
export async function resetSync(id: string): Promise<BaseResponse & { setup: SyncSettingData }> {
const response = await ajaxEngine().get(`/synchronize/task/${id}/resetSync`,);
return unwrap(response);
}

213
src/queries/user.ts Normal file
View File

@ -0,0 +1,213 @@
import { BaseResponse, Pageable } from '@/shared/model-components';
import { unwrap } from '@u/asyncs';
import { ajaxEngine } from './axios_instance';
import { BaseUserInfo, UserInfo, UserType } from '@/shared/model-user';
/**
*
* @param username
* @param verify
* @param password
* @returns
*/
export async function repassword(username: string, verify: string, password: string): Promise<BaseResponse> {
const response = await ajaxEngine(true).put(`/password`, {
uname: username,
verifyCode: verify,
newPass: password
});
return unwrap(response);
}
export interface AccountsResponse extends BaseResponse, Pageable {
accounts: BaseUserInfo[];
}
/**
*
* @param page
* @param query
* @param query.keyword
* @param query.type
* @param query.state
* @returns
*/
export async function userAccounts(
page: number,
query?: { keyword?: string; type?: UserType; state?: boolean }
): Promise<AccountsResponse> {
const response = await ajaxEngine().get<AccountsResponse>(`/account`, { params: { page, ...query } });
return unwrap(response);
}
/**
*
* @param uid Id
* @param enabled
*/
export async function changeAccountStatus(uid: string, enabled: boolean): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/account/enabled/state`, {
uid,
enabled
});
return unwrap(response);
}
/**
*
* @param uid Id
* @return
*/
export async function resetPassword(uid: string) {
const response = await ajaxEngine().delete<BaseResponse & { verify: string }>(`/password/${uid}`);
return unwrap(response);
}
export interface ChangeUserInfoParams {
address?: string;
contact?: string;
name?: string;
phone?: string;
region?: string;
unitServiceFee?: number;
}
/**
*
* @param uid
* @param data
*/
export async function changeUserInfo(uid: string, data: ChangeUserInfoParams): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/account/${uid}`, data);
return unwrap(response);
}
/**
*
* @param data
*/
export async function createUser(data: BaseUserInfo): Promise<BaseResponse & { verify: string }> {
const response = await ajaxEngine().post(`/account`, data);
return unwrap(response);
}
/**
*
* @param data
*/
export async function createEnterprise(
data: Required<ChangeUserInfoParams> & { username: string }
): Promise<BaseResponse & { verify: string }> {
const response = await ajaxEngine().post(`/enterprise`, data);
return unwrap(response);
}
/**
*
* @param uid Id
*/
export async function userDetail(uid: string): Promise<BaseResponse & { user: UserInfo }> {
const response = await ajaxEngine().get(`/account/${uid}`);
return unwrap(response);
}
export interface ChargeListResponse extends BaseResponse, Pageable {
records: ChargeRecord[];
}
/**
*
*/
export interface ChargeRecord {
/** 记录索引号,可用来排序 */
seq: number;
/** 最终合计费用 */
amount: null | number;
/** 计费时间,不是收到费用的时间,是费用可以维持服务到达的时间。格式 yyyy-MM-ddTHH:mm:ss.SSSSSSZ */
chargeTo: string;
/** 条目创建时间 */
createdAt: string;
/** 折扣,即减掉的百分比 */
discount: number;
/** 基本费用 */
fee: number;
/** 是否起效 */
settled: boolean;
/** 起效时间 */
settledAt: null | string;
/** 是否已取消 */
cancelled: boolean;
/** 取消时间 */
cancelledAt: null | string;
/** 所属用户ID */
userId: string;
/** 所属用户名称 */
name: string;
/** 所属用户账号 */
username: string;
}
/**
*
* @param page
* @param query
* @param query.keyword
* @param query.begin
* @param query.end
* @returns
*/
export async function chargeList(
page: number,
query?: { keyword?: string; begin: string; end: string }
): Promise<ChargeListResponse> {
const response = await ajaxEngine().get<ChargeListResponse>(`/charge`, { params: { page, ...query } });
return unwrap(response);
}
/**
*
* @param uid Id
* @param seq
*/
export async function cancelRecord(uid: string, seq: number): Promise<BaseResponse> {
const response = await ajaxEngine().put(`/charge/${uid}/${seq}`, { cancelled: true });
return unwrap(response);
}
export interface ChargeListResponse extends BaseResponse, Pageable {
records: ChargeRecord[];
}
/**
*
* @param keyword
* @returns
*/
export async function searchEnterprise(keyword: string): Promise<BaseResponse & { users: UserInfo[] }> {
const response = await ajaxEngine().get(`/enterprise/quick/search`, {
params: { keyword }
});
return unwrap(response);
}
export interface CreateRecord {
// 关联的企业用户Id
userId: string;
// 计费日期至
chargeTo: string;
// 应计金额
fee: null | string;
// 应收金额
amount: null | string;
// 折扣
discount: null | string;
}
/**
*
* @param data
*/
export async function createRecord(data: CreateRecord): Promise<BaseResponse> {
const response = await ajaxEngine().post(`/charge`, data);
return unwrap(response);
}

668
src/shared/equipment.ts Normal file
View File

@ -0,0 +1,668 @@
// noinspection JSNonASCIINames
import { Key } from "react";
export enum Operator {
"移动",
"联通",
"电信",
}
export enum MeterStatus {
"在库",
"运行",
"待校",
"故障",
}
export enum MeterLevel {
"总表",
"主表",
"从表",
}
export enum CommunicationProtocol {
"TcpServer模式",
"TcpClient模式",
"NB网关模式",
}
export enum Collector {
"低压采集器",
"采集终端",
}
export enum MessageWay {
"载波",
"无线",
"4G",
}
export enum Bps {
"1200bps",
"2400bps",
"4800bps",
"9600bps",
"19200bps",
"38400bps",
}
export enum SN {
"TcpClient模式",
"NB网关模式",
}
/**
*
* */
export interface ProtocolItem {
/**
* 0- 1200bps 1- 2400bps 2- 4800bps 3- 9600bps 4- 19200bps 5- 38400bps
*/
bps: number;
/**
* IP地址
*/
harvester_ip: string;
/**
* 0 - 1
*/
harvester_type: number;
/**
* imei
*/
imei: string;
/**
*
*/
port: number;
/**
* 0-TcpServer模式, 1-TcpClient模式, 2-NB网关模式
*/
protocol: number;
/**
* SN
*/
sn: string;
[property: string]: any;
}
/**
*
*/
export interface CardItem {
/**
* - 0 - 1 - 2
*/
operator: number;
/**
*
*/
sim_number: string;
/**
* SIN码
*/
sin: string;
/**
* - 0 - 1 - 2 - 3
*/
type: number;
[property: string]: any;
}
/**
*
*/
export interface CommunicationItem {
/**
* - 0 - 1线 - 24 G
*/
communication_mode: number;
/**
*
*/
manufacturers_name: string;
[property: string]: any;
}
export interface FactoryListItem extends CommunicationItem {
id: string,
created_at: string,
}
export interface FactoryForm extends CommunicationItem {
id: string,
created_at: string,
}
/** 卡管理列表 */
export interface CardListItem extends CardItem {
created_at: string;
delete_at: string;
id: string;
last_modify_at: string;
}
/** 卡编辑表单 */
export interface CardForm {
/**
* - 0 - 1 - 2
*/
operator: number;
/**
* id
*/
// park?: string;
/**
*
*/
sim_number: string;
/**
* SIN码
*/
sin: string;
[property: string]: any;
}
export interface CardListQuery {
/**
*
*/
endDate?: string;
page?: number;
/**
* id
*/
// park: string;
/**
*
*/
phone?: string;
/**
* sin卡号
*/
sinCard?: string;
keyword?: string;
/**
*
*/
startDate?: string;
[property: string]: any;
}
/** 通讯协议列表 */
export interface ProtocolListItem extends ProtocolItem {
id: string
}
/** 通讯协议搜索表单 */
export interface ProtocolListQuery {
/**
*
*/
keyword?: string;
page: number;
/**
* id
*/
// park: string;
/**
* 0-TcpServer模式, 1-TcpClient模式, 2-NB网关模式
*/
protocol?: number;
[property: string]: any;
}
/** 通讯协议表单 */
export interface ProtocolForm {
/**
* 0- 1200bps 1- 2400bps 2- 4800bps 3- 9600bps 4- 19200bps 5- 38400bps
*/
bps: number;
/**
* IP地址
*/
harvester_ip: string;
/**
* 0 - 1
*/
harvester_type: number;
/**
* imei
*/
imei: string;
/**
* id
*/
// park: string;
/**
*
*/
port: number;
/**
* 0-TcpServer模式, 1-TcpClient模式, 2-NB网关模式
*/
protocol: number;
/**
* SN
*/
sn: string;
[property: string]: any;
}
/** 厂家列表搜索 */
export interface FactoryListQuery {
/**
*
*/
name?: string;
/**
*
*/
page: number;
/**
* id
*/
// park: string;
/**
* - 0 - 1线 - 24 G
*/
way?: number;
[property: string]: any;
}
/** 表计设置列表 */
export interface MeterSettingListItem {
/**
*
*/
address: string;
/**
*
*/
building: string;
/**
*
*/
code: string;
/**
*
*/
enabled: boolean;
/**
*
*/
floor: string;
id: string;
/**
*
*/
index: string;
/**
* - 0 - 1 - 2
*/
level: number;
/**
*
*/
meterBox: string;
boxId: string,
boxAddress: string,
/**
*
*/
phone: string;
/**
*
*/
protocol: string;
/**
*
*/
rate: string;
/**
* - 0 - 1 - 2 - 3
*/
status: number;
/**
* - 0- 1- 2
*/
type: number;
[property: string]: any;
}
export interface MeterSettingListQuery {
/**
*
*/
code?: string;
/**
*
*/
enabled?: boolean;
/**
* - 0 - 1 - 2
*/
level?: number;
/**
*
* - 0
* - 1
* - 2
* - 3
*/
status?: number;
/**
* - 0- 1- 2
*/
type?: number;
keyword?:string;
[property: string]: any;
}
export interface BindProtocolForm {
/**
* code列表
*/
ids: string[];
protocol: string;
[property: string]: any;
}
export interface BindCardForm {
/**
* id
*/
cardId: string;
/**
*
*/
codeId: string;
[property: string]: any;
}
export interface MeterOperateListQuery {
/**
*
*/
code?: string;
/**
*
*/
park?: string;
/**
* 0-11-22-3-4-5-6-7-8-9-10-
*/
status?: number;
/**
* id
*/
tenement?: string;
/**
* 012
*/
type?: number;
[property: string]: any;
}
export interface MeterOperateListItem {
/**
*
*/
address: string;
/**
*
*/
amount: number;
/**
*
*/
canConnect: boolean;
/**
*
*/
code: string;
/**
*
*/
isOpen: boolean;
/**
* 0-1-
*/
mode: number;
/**
*
*/
money: number;
/**
* 0-1-
*/
onPosition: number;
/**
*
*/
realTimePower: string;
/**
*
*/
time: string;
/**
* 012
*/
type: number;
/**
* 1
*/
warning1: number;
/**
* 2
*/
warning2: string;
id: string;
[property: string]: any;
}
export interface BatchSwitchForm {
/**
* id列表
*/
ids: Key[];
/**
* 0-1-
*/
status: number;
[property: string]: any;
}
/**
*
*/
export interface BatchMeterResult {
/**
*
*/
code: string;
/**
* id
*/
id: string;
address: string,
/**
*
*/
succcess: boolean;
[property: string]: any;
}
export interface BatchModeForm {
/**
* id列表
*/
ids: Key[];
/**
* 0-1-
*/
mode: number;
[property: string]: any;
}
export interface BatchWarningForm {
/**
* id列表
*/
ids: Key[];
/**
* 1
*/
warning1: number;
/**
* 2
*/
warning2: number;
[property: string]: any;
}
export interface BatchTroubleForm {
/**
* id列表
*/
ids: string[];
/**
* 0-1-2-
*/
type: number;
[property: string]: any;
}
export interface BatchParamsForm {
/**
*
*/
credit: number;
/**
*
*/
critical: number;
/**
*
*/
flat: number;
/**
*
*/
peak: number;
/**
*
*/
valley: number;
/**
* 1
*/
warning1: number;
/**
* 2
*/
warning2: number;
ids: string[],
[property: string]: any;
}
export interface NestedMeterListItem {
/**
*
*/
address: string;
childrenCodes: NestedMeterListItemChild[];
/**
*
*/
code: string;
id: string;
codeId: string,
[property: string]: any;
}
export interface NestedMeterListItemChild {
/**
*
*/
address: string;
/**
*
*/
code: string;
id: string,
[property: string]: any;
}
export interface NestedMeterForm {
/**
*
*/
childrenCode: string[];
/**
*
*/
mainCode: string;
park?: string;
[property: string]: any;
}
export interface MeterSettingForm {
/**
*
*/
address: null | string;
/**
*
*/
area: null | string;
/**
*
*/
boxId: string;
/**
* ID
*/
building: null | string;
/**
*
*/
code: string;
/**
* 0 - 1 - 2
*/
level: number;
/**
*
*/
onFloor: null | string;
/**
*
*/
ratio: string;
/**
* 012
*/
type: number;
[property: string]: any;
}
export interface ChangeMeterEnabledForm {
/**
*
*/
enabled: boolean;
/**
* id
*/
ids: Key[];
/**
*
*/
time?: string;
[property: string]: any;
}

249
src/shared/foundation.ts Normal file
View File

@ -0,0 +1,249 @@
/**
*
*/
export type Factory<T> = () => T;
/**
*
*/
export type MapFactory<T, P = any> = (obj: P) => T;
/**
* 使
*/
export type MaybeMapFactory<T, P = any> = (obj?: P) => T;
/**
*
*/
export type ParamsFactory<T, P = any> = (...args: P[]) => T;
/**
*
*/
export type ObjectCallback<P = string, T = void> = (obj: P) => T | Promise<T>;
/**
*
*/
export type SyncObjectCallback<P = string, T = void> = (obj: P) => T;
/**
*
*/
export type MaybeObjectCallback<P = string, T = void> = ObjectCallback<P | undefined, T>;
/**
*
*/
export type SyncMaybeObjectCallback<P = string, T = void> = SyncObjectCallback<P | undefined, T>;
/**
*
*/
export type ObjectsCallback<P = string, T = void> = ObjectCallback<P[], T>;
/**
*
*/
export type SyncObjectsCallback<P = string, T = void> = SyncObjectCallback<P[], T>;
/**
*
*/
export type MaybeObjectsCallback<P = string, T = void> = ObjectCallback<P[] | undefined, T>;
/**
*
*/
export type SyncMaybeObjectsCallback<P = string, T = void> = SyncObjectCallback<P[] | undefined, T>;
/**
*
*/
export type ExtendParamCallback<P = unknown, T = void> = (...args: P[]) => T | Promise<T>;
/**
*
*/
export type SyncExtendParamCallback<P = unknown, T = void> = (...args: P[]) => T;
/**
*
*/
export type Callback<T = void> = () => T | Promise<T>;
/**
*
*/
export type SyncCallback<T = void> = () => T;
/**
* Store中定义状态操作Action的无参同步Action类型
*/
export type SyncAction = SyncCallback<void>;
/**
* Store中定义状态操作Action的无参可异步Action类型
*/
export type AsyncAction = Callback<void>;
/**
* Store中定义状态操作Action的单一参数同步Action类型
*/
export type SyncParamAction<T> = SyncObjectCallback<T, void>;
/**
* Store中定义状态操作Action的单一参数可异步Action类型
*/
export type AsyncParamAction<T> = ObjectCallback<T, void>;
/**
* Store中定义状态操作Action的不定参同步Action类型
*/
export type SyncMaybeAction<T> = SyncMaybeObjectCallback<T>;
/**
* Store中定义状态操作Action的不定参可异步Action类型
*/
export type AsyncMaybeAction<T> = MaybeObjectCallback<T>;
/**
* Store中定义状态操作Action的展开参同步Action类型
*/
export type SyncExtendParamAction<T> = SyncExtendParamCallback<T>;
/**
* Store中定义状态操作Action的展开参可异步Action类型
*/
export type AsyncExtendParamAction<T> = ExtendParamCallback<T>;
/**
* Ref组件
*/
export interface Resetable {
/**
*
*/
reset: Callback;
}
/**
* Ref组件
*/
export interface Openable {
/**
*
*/
open: Callback;
}
/**
* Ref组件
*/
export interface Closeable {
/**
*
*/
close: Callback;
}
/**
* Ref组件
*
*/
export interface Actionable<T = unknown> {
/**
*
*/
action?: ExtendParamCallback<T>;
}
/**
* Ref组件类型
*/
export interface SteppingEvents<T = unknown, R = void> {
/**
*
*/
onConfirm?: ExtendParamCallback<T, R>;
/**
*
*/
onCancel?: ExtendParamCallback<T, R>;
/**
*
*/
onPrevious?: ExtendParamCallback<T, R>;
/**
*
*/
onNext?: ExtendParamCallback<T, R>;
/**
*
*/
onSkip?: ExtendParamCallback<T, R>;
/**
*
*/
onMoveTo?: ExtendParamCallback<T, R>;
/**
*
*/
onReset?: ExtendParamCallback<T, R>;
}
/**
* State
*/
export interface NavigationState {
/**
*
*/
fromMainMenu?: boolean;
}
/**
* React Query查询键
*/
export type PageableQueryKey = [key: string, page: number];
/**
* Table中列需要使用的控件类型
*/
export type FormItemType = 'input' | 'password' | 'number' | 'checkbox' | 'radio' | 'select';
/**
*
*/
export interface DirtyableValue<T> {
/**
*
*/
value: T;
/**
*
*/
lastValue: T;
/**
*
*/
dirty: boolean;
}
/**
*
*/
export type Dirtyable<T> = { [P in keyof T]: DirtyableValue<T[P]> };
/**
*
*/
export interface EditableCellMeta {
/**
*
*/
meta: string;
}

View File

@ -0,0 +1,324 @@
import { Meter04KvInfo, TenementInfo } from './model-park'
import { ChargeInfo, SyncStatus } from './model-charge'
import { InvoiceInfo } from './model-invoice'
// 账务列表每一项
export interface Record {
/** 账务记录条目ID */
id: string,
/** 发生时间 */
occurredAt?: string,
/** 账目类型 */
type?: string,
/** 收入 */
income?: number,
/**支出 */
expenditure?: number,
/** 余额 */
balance?: number,
/** 发生方式 */
transfer?: string,
/** 账目元信息 */
meta?: Meta,
/** 操作员名称 */
operator?: string,
/** 审核员ID */
auditor?: string,
/** 审核员名称 */
auditorName?: string,
/** 审核时间 */
auditedAt?: string,
tenement?: string,
// 账目元信息
topUpMeta?: TopUpMeta,
/** 金额 */
money: number,
/** 同步状态 */
syncStatus: SyncStatus
}
interface TopUpMeta {
/** 备注 */
remark?: string,
/** 附加操作 */
appendOperation?: string,
/** 账目方向 */
direction?: string,
/** 账目金额 */
amount?: number,
/** 附加操作记录表号 */
code?: string,
/** 回滚id */
sid?: string,
/** 表号 */
meters: string [],
/** 凭证号 */
vouchNo: string,
}
// 账目元信息
export interface Meta {
/** 关联表计信息 */
meter?: Meter,
/** 关联重置记录ID */
chargeRecord?: ChargeInfo | null,
/** 关联重置写表任务ID */
chargeTask?: ChargeTask | null,
/** 关联商户信息 */
tenement?: TenementInfo,
/** 已开具发票记录 */
invoice?: InvoiceInfo
/** 对应code */
code ?: string
/** 备注 */
remark?: string
direction?: number
appendOperation: number
}
// 关联表计信息
export interface Meter extends Meter04KvInfo {
/** 表计表显倍率 */
displayRatio: string,
}
// 关联重置写表任务ID
export interface ChargeTask {
/** 同步任务ID */
id: string,
/** 归属企业ID */
userId: string,
/** 归属园区ID */
parkId: string,
/** 任务简报 */
briefing: string,
/** 计划执行时间 */
scheduleAt: string,
/** 上次执行时间 */
lastDispatchedAt: string | null,
/** 执行耗时 单位:毫秒 */
executionCost: number,
/** 尝试次数 */
attempts: number,
/** 当前状态 */
status: ChargeTaskStatus,
/** 锁定状态 */
lockDown: ChargeTaskLockDown,
/** 任务元信息 */
meta: ChargeTaskMeta,
}
export enum AccountDirection {
in,
out
}
export enum AppendOperate {
'充值',
'冲正',
'退费',
'电费发行',
"结算"
}
export enum GenerateWay {
}
export enum ChargeTaskStatus {
/** 队列中 */
queue,
/** 执行中 */
execution,
/** 成功 */
success,
fail,
cancel
}
export enum ChargeTaskLockDown {
/** 未锁定 */
unlocked,
/** 系统锁定 */
systemLock,
/** 人工锁定 */
artificialLock
}
export interface ChargeTaskMeta {
/** 任务目标 */
objective: string,
/** 表计编号 */
meterCode: string,
/** 远程平台代号 */
imrs: string,
/** 远程平台名称 */
imrsName: string,
/** 远程平台URL 如果是原生平台则采用自定义URL Scheme */
imrsUrl: string,
/** 最大重试次数 */
maxRetries: number,
/** 重试间隔算法 */
intervalAlgorithm: IntervalAlgorithm,
/** 重试基础间隔 */
interval: string,
}
export enum IntervalAlgorithm {
/** 指数退避 */
/** 2倍线性间隔 */
/** 3倍线性间隔 */
/** 固定间隔 */
}
export interface Related {
index?: number,
/** 汇算记录ID */
id: string,
/** 汇算起始日期 */
settlingStart: string,
/** 汇算结束日期 */
settlingEnd: string,
/** 账务主体ID */
tenement: string,
/** 账务简称 */
tenementName: string,
/** 商户全称 */
tenementFullName: string,
/** 总收入 */
totalIncome: number,
/** 总支出 */
totalExpenditure: number,
/** 汇算期初余额 */
initialBalance: number,
/** 汇算余额 */
balance: number,
/** 汇算类型 */
type: number,
}
export interface Overall {
/** 汇算记录ID */
id: string,
/** 汇算起始日期 */
settlingStart: string,
/** 汇算结束日期 */
settlingEnd: string,
/** 账务主体ID */
tenement: string,
/** 账务主体名称 */
tenementName: string,
/** 总收入 */
totalIncome: string,
/** 总支出 */
totalExpenditure: string,
/** 汇算期初余额 */
initialBalance: string,
/** 汇算余额 */
balance: string
}
interface EditAccountingDataMeta {
remark?: string;
appendOperation?: number;
code?: string;
direction?: number;
}
export interface EditAccountingData {
/** 账目类型 */
type: string,
/** 转账类型 */
paymentType: string,
/** 发生金额 */
amount: string,
/** 账务条目发生时间 */
occurredAt: string,
/** 附属元信息 */
// meta: string,
meta: EditAccountingDataMeta,
/** 账务条目主体ID */
tenement: string,
/** 园区id */
parkId: string,
/** 凭证号 */
vouchNo: string,
}
// export interface CreateAccountingData extends UpdateAccountingData {
// }
export interface Settlement {
/** 结算记录ID */
id: string,
/** 结算日期 */
settledAt: string,
/** 账务主体ID */
tenement: string,
/** 账务主体名称 */
tenementName: string,
/** 表计编号 */
meterCode: string,
/** 合计充值 */
totalIncome: number,
/** 合计消费 */
totalExpenditure: number,
/** 结算状态 */
status: string,
/** 清算余额 */
balance: string,
/** 操作员 */
operator: string,
/** 审核员 */
auditor: string | null
}
// 支付类型
export enum AccountingType {
'现金',
'银行卡',
'支付宝',
'微信',
'云闪付',
'对公转账',
"C端-微信"
}
// 发生方式
export enum HappenWay {
'充值',
'冲正',
'电费',
"退费",
"初始化余额"
}
// 汇算类型
export enum SettleAccountType {
'阶段汇算',
'结算',
'终算',
'实时汇算'
}
export interface BalanceOverall {
/** 商户全称 */
tenementFullName: string,
/** 商户简称 */
tenementName: string,
/** 商户电表数量 */
metersCount: number,
/** 商户汇算起始金额 */
initialBalance: number,
/** 商户已使用金额 */
totalExpenditure: number,
/** 商户充值总金额 */
totalIncome: number,
/** 余额 */
balance: number,
}

View File

@ -0,0 +1,192 @@
import { BasePublicityInfo } from '@/shared/model-publicity';
/**
*
*/
export type ReportInfo = BasePublicityInfo;
/**
*
*/
export interface ReportSteps {
/** 试计算步骤 */
calculate: boolean;
/** 预览报表步骤 */
preview: boolean;
/** 发布步骤 */
publish: boolean;
/** 户表抄表填写 */
submeter: boolean;
/** 园区总表填写状态 */
summary: boolean;
/** 待摊薄填报状态 */
willDiluted: boolean;
}
/**
*
*/
export interface ReportElectricSummary {
/** 核算报表Id */
reportId: string;
/** 总消耗电度 */
overall: Consumption;
/** 总建筑面积 */
area: string;
/** 基本电费 */
basicFee: string;
pooledBasicFeeByAmount: string;
pooledBasicFeeByArea: string;
/** 调整电费 */
adjustFee: string;
pooledAdjustFeeByAmount: string;
pooledAdjustFeeByArea: string;
consumption: string;
loss: string;
lossRate: string;
}
/**
*
*/
export interface SummaryCaculateResult {
/** 电度电费 */
consumptionFee: null | string;
/** 有功(尖峰)电价 */
criticalPrice: null | string;
/** 有功(平)电量 */
flat: null | string;
/** 有功(平)电费 */
flatFee: null | string;
/** 有功(平)电价 */
flatPrice: null | string;
/** 有功(总)电价,电费均价 */
overallPrice: null | string;
/** 有功(峰)电价 */
peakPrice: null | string;
/** 有功(谷)电价 */
valleyPrice: null | string;
}
/**
*
*/
export interface ReportMaintenance {
/** 待摊薄记录ID */
diluteId: string;
/** 维护费数额,单位:元/月 */
fee: string;
/** 备注 */
memo: null | string;
/** 费用项目名称 */
name: string;
/** 所属报表ID */
reportId: string;
/** 来源预置ID如果不为空表示条目是导入得来 */
sourceId: null | string;
}
/**
*
*/
export interface MeterRecord {
/** 户址 */
address: null | string;
/** 退补电量(尖峰) */
adjustCritical: string;
/** 退补电量(平) */
adjustFlat: string;
/** 退补电量 */
adjustOverall: string;
/** 退补电量(峰) */
adjustPeak: string;
/** 退补电量(谷) */
adjustValley: string;
/** 联系人名称 */
contactName: null | string;
/** 联系人电话 */
contactPhone: null | string;
/** 条目创建时间,时间格式为 yyyy-MM-dd HH:mm:ss */
createdAt: string;
/** 本期表底(尖峰) */
currentPeriodCritical: null | string;
/** 本期表底(平) */
currentPeriodFlat: null | string;
/** 本期表底(总) */
currentPeriodOverall: string;
/** 本期表底(峰) */
currentPeriodPeak: null | string;
/** 本期表底(谷) */
currentPeriodValley: null | string;
/** 用户名称 */
customerName: null | string;
/** 是否公用设备表计 */
isPublicMeter: boolean;
/** 条目最后修改时间,时间格式为 yyyy-MM-dd HH:mm:ss */
lastModifiedAt: null | string;
/** 上期表底(尖峰) */
lastPeriodCritical: null | string;
/** 上期表底(平) */
lastPeriodFlat: null | string;
/** 上期表底(总) */
lastPeriodOverall: string;
/** 上期表底(峰) */
lastPeriodPeak: null | string;
/** 上期表底(谷) */
lastPeriodValley: null | string;
/** 表计表号 */
meterId: string;
/** 表计倍率 */
ratio: string;
/** 抄表序号,使用抄表序号进行排序 */
seq: number;
/** 是否计入摊薄 */
willDilute?: boolean;
}
/**
*
*/
export interface ReportFilled extends ReportMeterUnit {
/** 调整电费 */
adjustFee: string;
/** 基本电费 */
basicFee: string;
/** 电度电费 */
consumptionFee: null | string;
/** 线损率 */
authorizedLossRate: null | string;
/** 光伏发电电量 */
generateAmount?: number
}
/**
*
*/
export interface ReportMeterUnit {
/** 有功(尖峰)电量 */
critical: Consumption;
/** 有功(平)电量,计算得到的数值,不需要填写 */
flat: Consumption;
/** 有功(总)电量,总电量 */
overall: Consumption;
/** 有功(峰)电量 */
peak: Consumption;
/** 有功(谷)电量 */
valley: Consumption;
}
/**
*
*/
export interface Consumption {
/** 电量 */
amount: string;
/** 电费 */
fee: null | string;
/** 电价 */
price: null | string;
/** 电费占比 */
proportion: null | string;
}

View File

@ -0,0 +1,60 @@
/**
*
*/
export interface ChargeInfo {
/** 充值账号 */
account: string;
/** 充值金额 */
amount: string;
/** 充值记录ID */
id: string;
/** 表计编号 */
meter: string;
/** 表计地址 */
meterAddress: string;
/** 充值方式0现金1银行卡2支付宝3微信4云闪付 */
paymentType: PaymentType;
/** 同步状态0未同步1同步成功2同步失败 */
syncStatus: SyncStatus;
/** 商户ID */
tenement: string;
/** 商户名称 */
tenementName: string;
/** 充值时间格式yyyy-MM-dd HH:mm:ss */
toppedUpAt: string;
/** 取消时间格式yyyy-MM-dd HH:mm:ss */
cancelledAt: string;
/** 付款凭证号*/
voucherNo: string;
/** 操作时间 */
paymentAt: string;
}
export enum ChargeType {
"充值",
"冲正",
"退费"
}
/**
*
*/
export const enum PaymentType {
Cash,
Card,
Alipay,
Wechat,
UnionPay,
Public_Transfer,
C_Wechat
}
/**
*
*/
export const enum SyncStatus {
UnSync,
Success,
Failure
}

View File

@ -0,0 +1,178 @@
/**
* 使code承载内容编号name承载内容名称的基本数据结构
*/
export interface BaseContent {
/**
*
*/
code: string;
/**
*
*/
name: string;
}
/**
*
*/
export interface BaseResponse {
/**
*
*/
code: number;
/**
*
*/
message: string;
}
/**
*
*/
export interface AmountResponse extends BaseResponse {
/**
*
*/
amount: number;
}
/**
*
*/
export interface ModificationRecords {
/**
*
*/
createdAt: Date | string | number | null;
/**
*
*/
createdBy: string | number | null;
/**
*
*/
modifiedAt: Date | string | number | null;
/**
*
*/
modifiedBy: string | number | null;
/**
* null
*/
deletedAt: Date | string | number | null;
/**
*
*/
deletedBy: string | number | null;
/**
*
*/
revokedAt: Date | string | number | null;
/**
*
*/
revokedBy: string | number | null;
/**
*
*/
canceledAt: Date | string | number | null;
/**
*
*/
canceledBy: string | number | null;
}
/**
*
*/
export interface Pageable {
/**
*
*/
current: number;
/**
*
*/
pageSize: number;
/**
*
*/
total: number;
}
/**
*
*/
export interface Sortable {
/**
*
*/
sort: number;
}
/**
*
*/
export interface Stateful {
/**
*
*/
state: string | boolean;
}
/**
*
*/
export interface Revision extends Partial<ModificationRecords> {
/**
*
*/
revision: number;
}
/**
*
*/
export interface Revisionary {
/**
*
*/
revision: number;
/**
*
*/
revisions: Revision[];
}
/**
*
*/
export type HasOwner = {
[Property in keyof BaseContent as `owner${Capitalize<string & Property>}`]:
| BaseContent[Property]
| null;
};
/**
*
*
*/
export type SimpleAreaDescription = {
[Property in keyof BaseContent as `area${Capitalize<string & Property>}`]:
| BaseContent[Property]
| null;
};

View File

@ -0,0 +1,178 @@
/**
* 使code承载内容编号name承载内容名称的基本数据结构
*/
export interface BaseContent {
/**
*
*/
code: string;
/**
*
*/
name: string;
}
/**
*
*/
export interface BaseResponse {
/**
*
*/
code: number;
/**
*
*/
message: string;
}
/**
*
*/
export interface AmountResponse extends BaseResponse {
/**
*
*/
amount: number;
}
/**
*
*/
export interface ModificationRecords {
/**
*
*/
createdAt: Date | string | number | null;
/**
*
*/
createdBy: string | number | null;
/**
*
*/
modifiedAt: Date | string | number | null;
/**
*
*/
modifiedBy: string | number | null;
/**
* null
*/
deletedAt: Date | string | number | null;
/**
*
*/
deletedBy: string | number | null;
/**
*
*/
revokedAt: Date | string | number | null;
/**
*
*/
revokedBy: string | number | null;
/**
*
*/
canceledAt: Date | string | number | null;
/**
*
*/
canceledBy: string | number | null;
}
/**
*
*/
export interface Pageable {
/**
*
*/
current: number;
/**
*
*/
pageSize: number;
/**
*
*/
total: number;
}
/**
*
*/
export interface Sortable {
/**
*
*/
sort: number;
}
/**
*
*/
export interface Stateful {
/**
*
*/
state: string | boolean;
}
/**
*
*/
export interface Revision extends Partial<ModificationRecords> {
/**
*
*/
revision: number;
}
/**
*
*/
export interface Revisionary {
/**
*
*/
revision: number;
/**
*
*/
revisions: Revision[];
}
/**
*
*/
export type HasOwner = {
[Property in keyof BaseContent as `owner${Capitalize<string & Property>}`]:
| BaseContent[Property]
| null;
};
/**
*
*
*/
export type SimpleAreaDescription = {
[Property in keyof BaseContent as `area${Capitalize<string & Property>}`]:
| BaseContent[Property]
| null;
};

139
src/shared/model-flow.ts Normal file
View File

@ -0,0 +1,139 @@
import { ParkInfo, TenementInfo } from "./model-park";
import { BaseUserInfo } from "./model-user";
/** 审批流程列表每一项 */
export interface FlowListItem {
/** 设置人姓名 */
setUserName: string;
/** 设置时间 */
setTime: string;
/** 流程id */
id: string;
/** 流程名字 */
name: string;
/** 审核层级 */
tier: number;
/** 是否启用 */
enable: boolean;
/** 修改时间 */
updateTime: string;
/** 需要审批的用户们 */
users: string[],
/** 是否通知全体 */
all: boolean,
/** 类型 0-退费 */
type: number,
index?: number;
}
/** 新建审批流程表单数据 */
export interface FlowForm {
/** 流程id 0-退费 */
type: 0
/** 审核层级 */
level: number;
/** 审核用户列表 */
users: string[];
/** 是否通知所有人 */
all: boolean,
/** 是否启用 */
enable: boolean
}
/** 获取当前提交审批列表 */
export interface ApproveListItem {
/** 发起时间 */
applyTime: string
/** 发起人 */
applyUserName: string
/** 发起人id */
applyUserID: string
/** 类型 0-退费申请) */
type: number
/** 操作状态 0-待处理1-同意2-拒绝 */
operateStatus: number
/** 流程状态 1-处理中2-已完成3-已退回) */
flowStatus: number
/** 审批id */
id: string,
/** 商户 */
tenement: TenementInfo,
/** 园区 */
park: ParkInfo,
}
/** 获取当前提交审批列表参数 */
export interface ApproveListQuery {
/** 商户id */
tenement?: string
/** 操作类型0-退费申请) */
type?: number
/** 流程状态1-处理中2-已完成3-已退回) */
status?: number
/** 页码 */
page?: number
}
interface FlowDetailList {
/** 时间 */
time: string,
/** 操作人 */
user: BaseUserInfo,
/** 类型 0-已发起1-同意2-拒绝*/
type: 0 | 1, 2,
/** 审批意见 */
reason: string,
/** 操作内容 */
content: string,
index?: number,
}
/** 获取当前审批的详情 */
export interface FlowDetail {
/** 商户全称 */
fullName?: string
/** 电表编号 */
code?: string
/** 账务余额 */
balance?: number
/** 退费金额 */
amount?: number
/** 上传附件 */
fileList?: string[],
/** 申请原因 */
reason?: string,
/**列表 */
list?:FlowDetailList[];
/** 商户 */
tenement?: TenementInfo,
/** 园区 */
park?: ParkInfo,
}
export interface DisposeFlow {
/** 审批意见 */
content: string,
/** 操作 0-不同意1-同意 */
status: 0 | 1,
}
export interface FlowSelectItem {
label: string,
value: number,
}
export const flowList: FlowSelectItem[] = [
{ label: "退费", value: 0 }
]
export const flowStatusList: FlowSelectItem[] = [
{ label: "处理中", value: 1 },
{ label: "已完成", value: 2 },
{ label: "已退费", value: 3 },
]
export const operateStatusList: FlowSelectItem[] = [
{ label: "待处理", value: 0 },
{ label: "同意", value: 1 },
{ label: "拒绝", value: 2 },
]

165
src/shared/model-invoice.ts Normal file
View File

@ -0,0 +1,165 @@
/**
*
*/
export interface InvoiceInfo {
id: string,
/** 开票金额,即价税合计 */
amount: string;
/** 开票时间记录时间格式yyyy-MM-dd HH:mm:ss */
issuedAt: string;
/** 发票号码 */
no: string;
/** 税率 */
taxRate: string;
/** 计税方式0核算电费含税1核算电费未税 */
taxMethod: TaxMethod;
/** 商户ID */
tenement: string;
/** 抬头 */
title: InvoiceTitle;
/** 发票类型 */
type: string;
/** 开始时间 */
periodStart: string,
/** 结束时间 */
periodEnd: string,
/** 用电量 */
quantity: number,
/** 核算周期 */
periodTime: periodTime[],
}
interface periodTime {
time: string,
codes: string[],
}
export const enum TaxMethod {
Including,
Excluding
}
export const enum PacketType {
watch,
cycle,
}
/**
*
*/
export interface InvoiceTitle {
/** 开户账号 */
account: null | string;
/** 注册地址 */
address: null | string;
/** 开户行名称 */
bank: null | string;
/** 开票人 */
name: string;
/** 注册电话 */
phone: null | string;
/** 开票人社会统一信用代码 */
usci: string;
}
/**
*
*/
export interface InvoiceDetail extends InvoiceInfo {
/** 发票货物清单 */
cargos: {
/** 货物名称 */
name: string;
/** 单价 */
price: string;
/** 数量 */
quantity: string;
/** 税额 */
tax: string;
/** 税率 */
taxRate: string;
/** 价税合计 */
total: string;
/** 单位 */
unit: string;
}[];
/** 概述信息 */
covers: {
/** 核算电费总额 */
fee: string;
/** 核算周期结束格式yyyy-MM-dd */
periodEnd: string;
/** 核算周期起始格式yyyy-MM-dd */
periodStart: string;
/** 报表ID */
report: string;
/** 表号 */
code: string
}[];
}
/**
*
*/
export interface UninvoicedRecord {
/** 电费 */
fee: string;
/** 核算结束日期格式yyyy-MM-dd */
periodEnd: string;
/** 核算开始日期格式yyyy-MM-dd */
periodStart: string;
/** 电量 */
quantity: string;
/** 报表ID */
report: string;
}
/**
*
*/
export interface UninvoiceData {
/** 商户简称 */
shortName: string;
/** 商户全称 */
fullName: string;
/** 表计编号 */
code: string;
/** 单价 */
price: number;
/** 合计电费 */
fee: number;
/** 用电量 */
quantity: number;
/** 核算结束日期 */
periodEnd: string;
/** 核算开始日期 */
periodStart: string;
/** 报表ID */
report: string;
/** 商户id */
tenementID: string;
/** index */
index: number;
}
export interface cover {
code: string,
tenementID: string,
report: string,
}
export interface CreateInvoicePrimaryData {
/** 商户id */
tenementID: string
/** 商户全称 */
fullName: string;
/** 报表id */
// reportIDs: string[],
/** 园区id */
parkId: string,
/** 表号列表 */
covers: cover[]
}

71
src/shared/model-log.ts Normal file
View File

@ -0,0 +1,71 @@
export type Log = {
/** 操作时间 */
time: string,
/** 操作账号 */
username: string,
/** 操作人姓名 */
nickName: string,
/** 操作类型0-园区新建1-删除2-修改、3-停用4-商户的新建5-修改、6-迁出、7-表计的新建、8-修改9-换表、10-绑定、11-解绑、 12-抄表记录的新建、13-修改14-电费核算、15-发布、16-导出17-账务的新增、18-余额查询、19-售电充值的新增、20-充正新增、21-商户退费、22-发票开具、23-新开发票、24-已开发票、25-录入发票号26-权限设置修改权限27-子账号的创建、28-删除、29-修改。 */
type: string,
/** 操作内容 */
content: string,
/** 操作id */
id: string,
}
export enum LogType {
'新建园区',
'删除园区',
'修改园区',
'启/停用园区',
'新建商户',
'修改商户',
'迁出商户',
'新建表计',
'修改表计',
'换表表计',
'绑定表计',
'解绑表计',
'新建抄表记录',
'修改抄表记录',
'电费核算',
'发布核算',
'导出核算',
'新增账务',
'余额查询',
'新增充值',
'新增冲正',
'商户退费',
'发票开具',
'新开发票',
'已开发票',
'录入发票号',
'权限设置',
'创建子账号',
'删除子账号',
'修改子账号',
'绑定公摊表',
"修改充值",
"查看售电记录",
"新增退补电量",
"退补电量列表",
"撤回发布账单",
"余额导入",
"报表查询",
"新增流程",
"编辑流程",
"删除流程",
"新增审批",
"审批",
"余额模板导出",
"报表导出",
"文件上传",
"文件删除",
"监管账号用户查询",
"监管运维账号-创建监管账号",
"监管运维账号-查询用户详细信息",
"监管运维账号-修改用户状态",
"监管运维账号-创建企业用户",
"监管运维账号-搜索企业用户",
"监管运维账号-更新用户凭据",
"监管运维账号-企业用户服务期限延期处理",
}

348
src/shared/model-park.ts Normal file
View File

@ -0,0 +1,348 @@
import type { BaseUserInfo } from './model-user';
/**
*
*/
export type ParkInfo = SimpleParkInfo & {
/** 条目创建时间,时间格式为 yyyy-MM-dd HH:mm:ss */
createdAt: string;
/** 条目删除时间,时间格式为 yyyy-MM-dd HH:mm:ss */
deletedAt: null | string;
/** 条目是否启用 */
enabled: boolean;
/** 发票税率0-1的小数 */
taxRate?: string;
/** 基准核定线损率0-1的小数 */
normAuthorizedLoss?: string;
/** 条目最后修改时间,时间格式为 yyyy-MM-dd HH:mm:ss */
lastModifiedAt: null | string;
/** 电价计算政策0园区平均电度电价1园区平均电价 */
pricePolicy: PricePolicyType;
/** 公摊电费分摊策略0不分摊1按电量分摊2按面积分摊 */
publicDiluted: DilutedType;
/** 线损电费分摊策略0不分摊1按电量分摊2按面积分摊 */
lossDiluted: DilutedType;
/** 基本电费分摊策略0不分摊1按电量分摊2按面积分摊 */
basisDiluted: DilutedType;
/** 调整电费分摊策略0不分摊1按电量分摊2按面积分摊 */
adjustDiluted: DilutedType;
voltageTransmissionPrice?: string,
};
/**
*
*/
export interface SimpleParkInfo {
/** 园区ID */
id: string;
/** 园区所属用户ID */
userId: string;
/** 园区名称 */
name: string;
/** 园区住户数量 */
tenement: null | string;
/** 园区面积 */
area: null | string;
/** 供电容量 */
capacity: null | string;
/** 用电分类0两部制1单一峰谷2单一单一 */
category: ElectrCategory;
/** 户表计量类型0非峰谷1峰谷 */
meter04kvType: Meter04KvType;
/** 园区所在行政区划 */
region: null | string;
/** 园区地址 */
address: null | string;
/** 园区联系人 */
contact: null | string;
/** 园区联系人电话 */
phone: null | string;
}
/**
*
*/
export const enum ElectrCategory {
/** 两部制 */
DoubleCompose,
/** 单一峰谷 */
PeakToValley,
/** 单一电价 */
Single
}
/**
*
*/
export const enum Meter04KvType {
/** 非峰谷 */
DisableValley,
/** 峰谷 */
Valley
}
/**
*
*/
export const enum PricePolicyType {
/** 平均电度电价 */
Unit,
/** 平均电价 */
Avg
}
/**
*
*/
export const enum DilutedType {
/** 不分摊 */
NotDiluted,
/** 按电量分摊 */
QuantityDiluted,
/** 按商铺面积分摊 */
AreaDiluted
}
/**
*
*/
export const enum MeterType {
/** 商户电表 */
Tenement,
/** 园区电表 */
Park,
/** 公摊电表 */
Public
}
/**
* 0.4Kv表计信息
*/
export interface Meter04KvInfo {
tableKey?: string;
/** 户址 */
address: null | string;
/** 表计表号 */
code: string;
/** 条目创建时间,时间格式为 yyyy-MM-dd HH:mm:ss */
createdAt: string;
/** 是否可用 */
enabled: boolean;
/** 条目最后修改时间,时间格式为 yyyy-MM-dd HH:mm:ss */
lastModifiedAt: null | string;
/** 所属园区ID */
parkId: string;
/** 表计倍率 */
ratio: string;
/** 抄表序号 */
seq: number;
/** 表计类型 */
type: MeterType;
/** 所辖面积 */
area: number;
/** 所在建筑 */
building: number;
/** 建筑名称 */
buildingName: string;
/** 停用日期 */
downTime?: string
}
/**
* Excel后出现错误的信息
*/
export interface UploadExcelError {
/** 出错的列号 */
col: number;
/** 出错信息 */
error: string;
/** 出错的行号 */
row: number;
}
/**
*
*/
export interface MaintenanceInfo {
/** 条目创建时间,时间格式为 yyyy-MM-dd HH:mm:ss */
createdAt: string;
/** 条目删除时间,时间格式为 yyyy-MM-dd HH:mm:ss */
deletedAt: null | string;
/** 条目是否启用 */
enabled: boolean;
/** 物业附加费所属年份 */
period: string;
/** 附加费数额,单位:元/年 */
fee: string;
/** 物业附加费记录ID */
id: string;
/** 条目最后修改时间,时间格式为 yyyy-MM-dd HH:mm:ss */
lastModifiedAt: null | string;
/** 备注 */
memo: null | string;
/** 费用项目名称 */
name: string;
/** 所属园区ID */
parkId: string;
}
/** 物业附加费列表条目信息 */
export interface MaintenanceSummary {
/** 管理单位信息 */
user: BaseUserInfo;
/** 管理单位园区信息 */
park: ParkInfo;
/** 物业附加费所属年份 */
period: string;
/** 合计附加费数额,单位:元/年 */
fee: string;
/** 附加费单价, 单位:元/平米/年 */
price: string;
/** 附加费单价,单位:元/平米/季 */
quarterPrice: string;
/** 附加费单价, 单位:元/平米/半年 */
semiAnnualPrice: string;
}
/**
*
*/
export interface ParkBuilding {
// 是否启用
enabled: boolean;
// 建筑楼层数量
floors: string;
// 建筑ID
id: string;
// 建筑名称
name: string;
}
/**
*
*/
export interface ExchangeFormData {
/** 原表计读数 */
oldReading: MeterReading;
/** 新表计资料 */
newMeter: {
/** 表计编号 */
code: string;
/** 倍率 */
ratio: string;
/** 表计读数 */
reading: MeterReading;
};
}
/**
*
*/
export interface MeterReading {
/** 有功(尖峰)非峰谷表读数设为0 */
critical: string;
/** 有功(平),非峰谷表读数设为有功(总)的数值 */
flat: string;
/** 有功(峰)非峰谷表读数设为0 */
peak: string;
/** 有功(谷)非峰谷表读数设为0 */
valley: string;
/** 有功(总) */
overall: string;
}
/**
*
*/
export interface MeterPooled extends Meter04KvInfo {
/** 关联的子级表计 */
bindedMeters: Meter04KvInfo[];
}
/**
*
*/
export interface MeterChoiceData {
/** 表计地址 */
address: string;
/** 表计编号 */
code: string;
/** 标记所属园区ID */
park: string;
}
/**
*
*/
export interface TenementInfo {
/** 联系地址 */
address: string;
/** 所在建筑ID */
building: null | string;
/** 所在建筑名称 */
buildingName: null | string;
/** 联系人姓名 */
contact: null | string;
/** 条目创建时间 */
createdAt: string;
/** 条目删除时间 */
deletedAt: null | string;
/** 全名 */
fullName: string;
/** 商户ID */
id: string;
/** 条目最后修改时间 */
lastModifiedAt: null | string;
/** 入住日期 */
movedInAt: string;
/** 迁出日期 */
movedOutAt: null | string;
/** 所在建筑层数 */
onFloor: null | string;
/** 联系电话,将作为主要关联电话使用 */
phone: null | string;
/** 简称 */
shortName: null | string;
}
export interface MeterByTenement {
/** 园区ID */
parkId: string,
/** 商户id */
tenementId: string,
/** 电表号 */
meterId: string,
}
export const enum FeeType {
HCBN,
WY,
WYDS
}
export const FeeTypeList = [
{ label: "华昌宝能收费", value: 0 },
{ label: "物业", value: 1 },
{ label: "物业代收", value: 2 },
]
export interface MeterBox {
id ?: string,
park?: string,
building?: string,
boxNo?: string,
address?: string
}
export interface MeterBoxListItem {
id: string,
boxNo: string,
park: ParkInfo,
building: ParkBuilding,
enabled: boolean,
address: string,
createdAt: string,
lastModifiedAt: string,
deletedAt: string,
}

View File

@ -0,0 +1,469 @@
import type { ParkInfo, SimpleParkInfo } from '@/shared/model-park';
import type { BaseUserInfo } from '@/shared/model-user';
import type { Consumption } from '@/shared/model-calculate';
import { Meter04KvInfo, TenementInfo } from '@/shared/model-park';
import { ReportMeterUnit } from '@/shared/model-calculate';
/**
*
*/
export interface BasePublicityInfo {
report: SimpleReportInfo & ReportStatus & { number: string };
park: SimpleParkInfo;
user: BaseUserInfo;
}
/**
*
*/
export interface SimpleReportInfo {
/** 报表Id */
id: string;
/** 最后一次申请撤回的时间,格式为 yyyy-MM-dd HH:mm:ss */
lastWithdrawAppliedAt: string;
/** 最后一次申请审核的时间,格式为 yyyy-MM-dd HH:mm:ss */
lastWithdrawAuditAt: string;
/** 所属园区Id */
parkId: string;
/** 核算起始日期,格式为 yyyy-MM-dd */
periodBegin: string;
/** 核算结束日期,格式为 yyyy-MM-dd */
periodEnd: string;
/** 是否已发布 */
published: boolean;
/** 发布时间 */
publishedAt: string;
/** 报表撤回状态 */
withdraw: WithdrawState;
}
/**
*
*/
export interface ReportStatus {
status: ReportStatusType;
}
/**
*
*/
export const enum ReportStatusType {
/** 数据丢失 */
Miss_Data,
/** 数据缺陷 */
Defects_Data,
/** 计算错误 */
Calculation_Data,
/** 储存错误 */
Storage_Data,
/** 除数为0 */
Divisor_Zero,
/** 程序错误 */
Program_Exception,
/** 成功 */
Success = 9,
}
export interface CalculateErrorMessage {
/** 所属报表ID */
reportId: string;
/** 出现错误的电表ID */
meterId: string;
/** 商户id */
ID: string;
/** 商户全称 */
fullName: string;
/** 抄表时间 */
readAt: string;
/** 电表是否开启 */
enabled: boolean;
/** 备注 */
remark: string;
}
/**
*
*/
export const enum WithdrawState {
/** 未撤回 */
Default,
/** 申请撤回中 */
Withdrawing,
/** 申请拒绝 */
Refused,
/** 申请批准 */
Passed
}
/**
*
*/
export interface ParkSummary {
/** 调整电费 */
adjustFee: string;
/** 总建筑面积 */
area: string;
/** 基本电费 */
basicFee: string;
/** 户表总电量,商户表计+物业表计,不含园区表计和公摊表计 */
consumption: string;
/** 线损电量 */
loss: string;
/** 线损率,取值为百分数 */
lossRate: string;
/** 总消耗电度 */
overall: Consumption;
/** 电度摊薄调整电费电价 */
pooledAdjustFeeByAmount: string;
/** 面积摊薄调整电费电价 */
pooledAdjustFeeByArea: string;
/** 电度摊薄基本电费电价 */
pooledBasicFeeByAmount: string;
/** 面积摊薄基本电费电价 */
pooledBasicFeeByArea: string;
/** 核算报表ID */
reportId: string;
/** 光伏发电电量 */
generateAmount?: number,
}
/**
*
*/
export interface PublicPooled extends Meter04KvInfo {
/** 接入系统时间,挂表 */
attachedAt: string;
/** 从系统移除时间,拆表 */
detachedAt: null | string;
/** 所在楼层 */
onFloor: null | string;
/** 总电量部分 */
overall: Consumption;
/** 摊薄方式0按电度摊薄1按面积摊薄 */
poolMethod: PoolMethod;
/** 表格rowkey标识 */
tableRowKey: string,
}
/**
* 摊薄方式: 0:12
*/
export const enum PoolMethod {
No,
Consumption,
Area
}
/**
*
*/
export interface ReportTenement extends TenementInfo {
consumption: string;
fee: string;
pooled: string;
final: string;
/** 类型0-查看明细1-电费账单 */
type: 0 | 1;
/** tabs的key */
key: string;
consumptionAmount: string
}
/**
*
*/
export interface ReportTenementDetail {
park: SimpleParkInfo;
tenement: TenementInfo;
comprehensive: TenementComprehensiveDetail;
meters: (Meter04KvInfo &
PooledSumDetail &
ReportMeterUnit & { /** 合计电费 */ finalTotal: string; loss: Consumption } & {
/** 首次抄表时间 */
currentTermReadings: string,
/** 最近抄表时间 */
lastTermReadings: string,
}
& {
startNumber: number,
endNumber: number,
displayRatio: number,
publicAmount: string,
refundAmount: string,
}
)[];
pooled: (Meter04KvInfo & ReportMeterUnit)[];
}
/**
*
*/
export type TenementComprehensiveDetail = PooledSumDetail & {
/** 合计电度电量 */
consumption: string;
/** 合计电度电费 */
fee: string;
/** 电费均价 */
price: string;
/** 合计电费 */
total: string;
/** 核定线损部分 */
loss: Consumption;
/** 摊薄线损电费 */
basicPooled: string;
/** 公摊电费 */
publicPooled: string;
/** 摊薄调整电费 */
adjustPooled: string;
/** 摊薄线损电量 */
lossAmount: string;
/** 开始时间 */
startDate: string;
/** 结束时间 */
endDate: string;
/** 本期公摊电量 */
publicAmount: number
};
/**
*
*/
export interface PooledSumDetail {
/** 摊薄调整电费 */
adjustPooled: string;
/** 摊薄基本电费 */
basicPooled: string;
/** 摊薄线损电费 */
lossPooled: string;
/** 公摊电费 */
publicPooled: string;
}
/**
*
*/
export interface PublicityDetail {
/** 报表所属用户的简易信息 */
enterprise: BaseUserInfo;
/** 报表关联园区信息 */
park: ParkInfo;
}
/**
*
*/
export interface PublicityEndUser {
/** 地址 */
address: null | string;
/** 是否公用也被表计 */
isPublicMeter: boolean;
/** 电量(尖峰) */
critical: string;
/** 电费(尖峰) */
criticalFee: string;
/** 用户名称 */
customerName: null | string;
/** 配电物业费 */
maintenance: string;
/** 表号 */
meterId: string;
/** 电量(总) */
overall: string;
/** 电费均价 */
overallPrice: string;
/** 电费(总) */
overallFee: string;
/** 电量(峰) */
peak: string;
/** 电费(峰) */
peakFee: string;
/** 电量(谷) */
valley: string;
/** 电费(谷) */
valleyFee: string;
}
/**
*
*
*
*/
export interface PublicityEndUserSum {
/** 总电度电费 */
consumptionFee: string;
/** 尖峰电量 */
critical: string;
/** 尖峰电费 */
criticalFee: string;
/** 尖峰电价 */
criticalPrice: string;
/** 平电量 */
flat: string;
/** 平电费 */
flatFee: string;
/** 平电价 */
flatPrice: string;
/** 总电量 */
overall: string;
/** 电量均价 */
overallPrice: string;
/** 峰电量 */
peak: string;
/** 峰电费 */
peakFee: string;
/** 峰电价 */
peakPrice: string;
/** 谷电量 */
valley: string;
/** 谷电费 */
valleyFee: string;
/** 谷电价 */
valleyPrice: string;
}
/**
*
*
*
*/
export interface PublicityLoss {
/** 电度电费 */
consumptionFee: string;
/** 电价 */
price: string;
/** 占比 */
proportion: string;
/** 电量 */
quantity: string;
/** 核定电量 */
authorizeQuantity: string;
/** 核定电费 */
authorizeConsumptionFee: string;
}
/**
*
*/
export interface PublicityMaintenance {
/** 基础电费 */
basicFees: string;
/** 调整电费 */
adjustFee: string;
/** 损失电费 */
lossFee: string;
/** 损失率 */
lossProportion: string;
/** 调整电费占比 */
adjustProportion: string;
/** 调整电费单价 */
adjustPrice: string;
}
/**
*
*
*
*/
export interface PublicityOthers {
/** 基础电费 */
basicFees: string;
/** 配电运行维护费 */
maintenanceFee: string;
}
/**
*
*
*
*/
export interface PublicityCategoryOverall {
/** 总电度电费 */
consumptionFee: string;
/** 尖峰电量 */
critical: string;
/** 尖峰电费 */
criticalFee: string;
/** 尖峰电价 */
criticalPrice: string;
/** 平电量 */
flat: string;
/** 平电费 */
flatFee: string;
/** 平电价 */
flatPrice: string;
/** 总电量 */
overall: string;
/** 总电费 */
overallFee: string;
/** 电量均价 */
overallPrice: string;
/** 峰电量 */
peak: string;
/** 峰电费 */
peakFee: string;
/** 峰电价 */
peakPrice: string;
/** 占比 */
proportion: string;
/** 谷电量 */
valley: string;
/** 谷电费 */
valleyFee: string;
/** 谷电价 */
valleyPrice: string;
}
/** 终端用户指定时间区间内的调整费用统计 */
export interface PublicityEndUserPeriodStatistics {
/** 用户名称 */
customerName?: string;
/** 户址 */
address?: string;
/** 表计编号 */
meterId: string;
/** 表计是否峰谷表计 */
pvKind?: number;
/** 表计是否公共设备表计 */
isPublicMeter?: boolean;
/** 总电量 */
overall: string | null;
/** 尖峰电量 */
critical: string | null;
/** 峰电量 */
peak: string | null;
/** 谷电量 */
valley: string | null;
/** 总电费 */
overallFee: string | null;
/** 尖峰电费 */
criticalFee: string | null;
/** 峰电费 */
peakFee: string | null;
/** 谷电费 */
valleyFee: string | null;
/** 调整费用 */
adjustFee: string | null;
/** 调整费用占比 */
adjustProportion: string | null;
}
/**获取报表核算的错误列表(如果没有返回的是空列表) */
export interface errorMessageItem {
/** 错误信息备注 */
remark: string;
/** 商户id */
id: string;
/** 商户全称 */
fullName: string;
/** 抄表时间 */
readAt: string;
/** 表号 */
code: string;
/** 表是否启用 */
enabled: string;
/** 报表id */
rid: string;
}

View File

@ -0,0 +1,75 @@
import {Meter04KvInfo, MeterReading, TenementInfo} from '@/shared/model-park';
/**
*
*/
export interface ReadingInfo extends Meter04KvInfo, MeterReading {
/**
* yyyy-MM-dd HH:mm:ss
*/
readAt: string;
/**
* readAt对应的时间戳
*/
readAtTimestamp: string;
/** 商户全名 */
fullName: string;
/** 商户联系人 */
contact?: string;
/** 商户电话 */
phone?: string;
}
/**
* 退
*/
export interface Refund {
/**
* 退
*/
number: string;
/**
*
*/
operateTime: string;
/** 商户 */
tenement: TenementInfo,
/** 表计 */
meter?: Meter04KvInfo;
/** 退补周期 */
period?: string;
/** 退补电量 */
amount?: string;
/** 备注 */
remark?: string;
}
/**
* 退
*/
export interface RefundForm {
/** 商户id */
tenementId: string,
/** 表号 */
meterId: string,
/** 周期 */
period: any,
/** 退补电量 */
overall: number,
/** 备注 */
remark?: string,
}
/**
* 退
*/
export interface RefundUpdateForm {
/** 记录id */
id: string,
/** */
meterId: string,
/** 周期 */
period: string,
/** 退补电量 */
overall: number,
/** 备注 */
remark?: string,
}

View File

@ -0,0 +1,70 @@
export interface privilege { identity: string, description: string };
export interface SonAccountItem {
/**
* ID
*/
sid: string,
/**
*
*/
name: string,
/**
*
*/
username: string,
/**
*
*/
contactName :string,
/**
*
*/
contactPhone :string,
/**
*
*/
privileges: privilege [],
/**
*
*/
enabled: boolean,
/**
*
*/
createdAt :string,
/**
*
*/
lastModifiedAt :string,
}
export interface SubmitSonAccount {
/**
* ID
*/
sid: string,
/**
*
*/
name: string,
/**
*
*/
username: string,
/**
*
*/
contactName :string,
/**
*
*/
contactPhone :string,
/**
*
*/
privileges: string [],
}

View File

@ -0,0 +1,155 @@
import {Meter04KvInfo, ParkInfo, TenementInfo} from "@/shared/model-park";
import {AccountingType, HappenWay} from "@/shared/model-accounting";
import {BaseUserInfo} from "@/shared/model-user";
/** 充值情况 **/
export interface RechargeStatement {
/** 序号 **/
index?: number,
/** 园区 **/
park: ParkInfo,
/** 操作时间 **/
operateTime?: number,
/** 商户 **/
tenement?: TenementInfo,
/** 表计 **/
meter?: Meter04KvInfo,
/** 支付金额 **/
money?: number,
/** 付款方式 **/
way?: AccountingType,
/** 售电类型0-充值1-冲正2-退费 **/
type?: 0 | 1 | 2,
/** 操作员 **/
operateUser: BaseUserInfo
}
/** 用电情况 **/
export interface ElectricStatement {
/** 序号 **/
index?: number,
/** 商户 **/
tenement?: TenementInfo,
/** 开始时间 **/
startDate?: string,
/** 结束时间 **/
endDate?: string,
/** 总电量 **/
amount: number,
/** 合计摊薄费用 **/
pooledFee?: number,
/** 合计公摊费用 **/
publicFee?: number,
/** 合计电费 **/
amountFee?: number,
/** 备注 **/
remark: string,
}
/** 用电详细查询 **/
export interface ElectricDetailStatement {
/** 序号 **/
index?: number,
/** 商户 **/
tenement?: TenementInfo,
/** 表计 **/
meter?: Meter04KvInfo,
/** 开始时间 **/
startDate?: string,
/** 结束时间 **/
endDate?: string,
/** 总电量 **/
overallAmount: number,
/** 电度电费 **/
chargeFee?: number,
/** 摊薄线损电量 **/
lossAmount?: number,
/** 摊薄调整电费 **/
adjustPooled?: number,
/** 公摊电费 **/
publicPooled: number,
/** 合计电费 **/
finalAll: number,
/** 备注 **/
remark: string,
}
/** 账务记录查询 **/
export interface AccountingStatement {
/** 序号 **/
index?: number,
/** 商户 **/
tenement?: TenementInfo,
/** 表计 **/
meter?: Meter04KvInfo,
/** 操作时间 **/
operateDate?: string,
/** 结束时间 **/
type?: HappenWay,
/** 上次余额 **/
lastBalance: number,
/** 金额 **/
money?: number,
/** 余额 **/
balance?: number,
/** 操作员 **/
operateUser: BaseUserInfo
}
/** 发票记录 **/
export interface InvoiceStatement {
/** 序号 **/
index?: number,
/** 商户 **/
tenement?: TenementInfo,
/** 表计 **/
meter?: Meter04KvInfo,
/** 初始金额 **/
initMoney?: number,
/** 累计充值金额 **/
chargeMoney?: number,
/** 已消耗金额 **/
usedMoney: number,
/** 已开票金额 **/
invoiceMoney?: number,
/** 开票后余额 **/
invoiceBalance?: number,
/** 备注 **/
remark: string
}
/** 商户情况 **/
export interface TenementStatement extends TenementInfo{
/** 序号 **/
index?: number,
/** 开票地址 **/
invoiceAddress?: string,
}
export interface DefaultQuery {
/** 开始时间 **/
startDate?: string,
/** 结束时间 **/
endDate?: string,
/** 页数 **/
page?: number,
/** 园区 **/
park?: string,
}
/**
*
*/
// export const enum MeterType {
// /** 充值查询 */
// RechargeStatement,
// /** 用电情况查询 */
// ElectricStatement,
// /** 用电详细查询 */
// ElectricDetailStatement
// }
export enum StatementType {
"充值查询",
"用电情况查询",
"用电详细查询",
"账务记录",
"发票记录",
"商户情况"
}

85
src/shared/model-sync.ts Normal file
View File

@ -0,0 +1,85 @@
import { ParkInfo } from './model-park';
/**
*
*/
export interface SyncTask {
/** 同步任务ID */
id: string;
/** 归属企业ID */
userId: string;
/** parkID */
parkId: string;
/** 企业用户ID */
entId: string;
/** 企业名称 */
entName: string;
/** 上次运行时间格式yyyy-MM-dd HH:mm:ss。最后同步时间这个时间不保证同步任务一定成功 */
lastDispatchedAt?: string;
/** 上次运行结果0同步中1同步成功2同步失败3等待中4已暂停 */
lastDispatchStatus?: SyncTaskStatus;
/** 下次运行时间格式yyyy-MM-dd HH:mm:ss */
nextDispatchAt?: string;
/** 园区名称 */
parkName: string;
/** 任务ID */
taskId?: string;
/** 任务名称 */
taskName?: string;
/** 状态 (0:队列中1:执行中2:成功3:失败4:取消) */
status: number
}
/**
*
* 01234
*/
export const enum SyncTaskStatus {
Synching,
Success,
Failure,
Waiting,
Pause
}
/**
* 0+12
*/
export const enum ReadingType {
AutoManual,
Auto,
Manual
}
/**
* 0123
*/
export const enum ReadingInterval {
Hour,
Day,
Week,
Month
}
/**
* 0退12线23线3
*/
export const enum ReadingRetryAlgorithm {
ExponentialBackOff,
DoubleInterval,
TripleInterval,
FixedInterval
}
/**
*
*/
export interface ParkSync {
time: string,
park: ParkInfo,
ownNumber: number,
targetNumber: number,
isSame: number,
id: string
}

73
src/shared/model-user.ts Normal file
View File

@ -0,0 +1,73 @@
/**
*
*/
export interface UserSession {
/** 当前会话所属的用户ID。 */
uid: string;
/** 当前会话的用户名称或姓名。 */
name: string;
/** 用户类型0企业1监管2运维 */
type: UserType;
/** 代表当前会话的令牌。 */
token: string;
/** 当前会话的过期时间。 */
expiresAt: string;
/** 菜单权限 */
menu: string[]
}
/**
*
*/
export const enum UserType {
/** 企业 */
Enterprise,
/** 监管 */
Regulatory,
/** 运维 */
Maintainer
}
/**
*
*/
export interface BaseUserInfo {
/** 用户联系人 */
contact: null | string;
/** 条目创建时间 */
createdAt: string;
/** 条目创建人保存用户ID */
createdBy: null | string;
/** 条目是否启用 */
enabled: boolean;
/** 用户ID */
id: string;
/** 条目最后修改人保存用户ID */
lastModiedBy: null | string;
/** 条目最后修改时间 */
lastModifiedAt: null | string;
/** 用户名称 */
name: string;
/** 用户联系人电话 */
phone: null | string;
/** 用户类型 */
type: UserType;
/** 用户账号 */
username: string;
}
/**
*
*/
export interface UserInfo extends BaseUserInfo {
/** 月服务费 */
unitServiceFee: number | null;
/** 服务过期时间,格式为 YYYY-MM-dd */
serviceExpiration: string;
/** 用户所在行政区划 */
region: null | string;
/** 用户地址 */
address: null | string;
/** 用户名称的拼音缩写 */
abbr: null | string;
}

47
src/shared/props copy.ts Normal file
View File

@ -0,0 +1,47 @@
import { Callback } from './foundation';
/**
*
*/
export interface ActivedProps {
/**
*
*/
actived?: boolean;
}
/**
*
* !
*/
export interface CommonModalProps {
/**
*
*/
onOpen?: Callback;
/**
*
*/
onClose?: Callback;
/**
*
*/
onBeforeOpen?: Callback;
/**
*
*/
onAfterOpen?: Callback;
/**
*
*/
onBeforeClose?: Callback;
/**
*
*/
onAfterClose?: Callback;
}

47
src/shared/props.ts Normal file
View File

@ -0,0 +1,47 @@
import { Callback } from './foundation';
/**
*
*/
export interface ActivedProps {
/**
*
*/
actived?: boolean;
}
/**
*
* !
*/
export interface CommonModalProps {
/**
*
*/
onOpen?: Callback;
/**
*
*/
onClose?: Callback;
/**
*
*/
onBeforeOpen?: Callback;
/**
*
*/
onAfterOpen?: Callback;
/**
*
*/
onBeforeClose?: Callback;
/**
*
*/
onAfterClose?: Callback;
}

21
src/shared/system copy.ts Normal file
View File

@ -0,0 +1,21 @@
import { ItemType } from "antd/es/menu/hooks/useItems";
export type PrivilegedItemType = {
any?: number[];
all?: number[];
link?: string;
title?: string;
specific?: string;
children?: PrivilegedItemType[];
label?: string,
key?: string,
} & ItemType;
export interface FileListItem {
uid?: string,
name?: string,
status?: 'done' | 'uploading' | 'error' | 'removed',
response?: string,
linkProps?: string,
}

21
src/shared/system.ts Normal file
View File

@ -0,0 +1,21 @@
import { ItemType } from "antd/es/menu/hooks/useItems";
export type PrivilegedItemType = {
any?: number[];
all?: number[];
link?: string;
title?: string;
specific?: string;
children?: PrivilegedItemType[];
label?: string,
key?: string,
} & ItemType;
export interface FileListItem {
uid?: string,
name?: string,
status?: 'done' | 'uploading' | 'error' | 'removed',
response?: string,
linkProps?: string,
}

28
src/utils/asyncs.ts Normal file
View File

@ -0,0 +1,28 @@
import { AxiosResponse } from 'axios';
import { lt, not } from 'ramda';
import { BaseResponse } from '@/shared/model-components';
import { alertError } from '.';
/**
* 访访
* @param response Axios访问服务端产生的访问响应
* @returns
*/
export function unwrap<T extends BaseResponse>(response: AxiosResponse<T>): T {
if (!response) {
alertError('服务异常,请稍后重试')
return null
}
if (not(lt(response.status, 300))) {
// throw new NetworkError(response.status, response.data?.message || response.statusText);
}
return response.data;
}
/**
*
* @param result
*/
export function isCorrectResult(result: BaseResponse) {
return lt(result.code, 300);
}

View File

@ -0,0 +1,4 @@
/**
*
*/
export const defaultMeterReading = { critical: '0', peak: '0', flat: '0', valley: '0' };

20
src/utils/file.ts Normal file
View File

@ -0,0 +1,20 @@
export function isImage(ext: string): boolean {
return ['.jpeg', '.jpg', '.png'].includes(ext)
}
export function isExcel(ext: string): boolean {
return [".xls", ".xlsx"].includes(ext)
}
export function isPdf(ext: string) : boolean {
return ".pdf" === ext
}
export function isWord(ext: string): boolean {
return [".doc", ".docx"].includes(ext)
}
export function getExt(filename: string): string {
const lastIndex = filename.lastIndexOf(".")
return filename.slice(lastIndex)
}

566
src/utils/funcs.ts Normal file
View File

@ -0,0 +1,566 @@
//@ts-nocheck
import { Dirtyable, DirtyableValue } from '@/shared/foundation';
import { BaseResponse, Sortable } from '@/shared/model-components';
import BigNumber from 'bignumber.js';
import dayjs from 'dayjs';
import {
addIndex,
append,
assoc,
assocPath,
clone,
compose,
curry,
defaultTo,
equals,
includes,
isEmpty,
isNil,
keys,
lens,
map,
mergeLeft,
mergeRight,
not,
or,
path,
pathEq,
pathOr,
prepend,
prop,
propEq,
reduce,
Reduced,
set,
split,
subtract,
view,
__
} from 'ramda';
/**
*
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function noOp() {}
/**
* undefined和null
*/
export const notNil = compose(not, isNil);
/**
*
*/
export const propIsNil = curry(<T>(property, obj: T) => compose(isNil, prop(property))(obj));
/**
*
*/
export const propNotNil = curry(<T>(property, obj: T) => compose(notNil, prop(property))(obj));
/**
*
*/
export const notEmpty = compose(not, isEmpty);
/**
*
*/
export const notEquals = curry(compose(not, equals));
/**
*
*/
export const notPropEq = curry(compose(not, propEq));
/**
* null
* @param value
* @returns
*/
export const isNilOrEmpty = <T>(value: T | null | undefined): boolean => or(isNil(value), isEmpty(value));
/**
* null
*/
export const norNilOrEmpty = compose(not, isNilOrEmpty);
/**
* map方法更改数组中对象的键名使`renameKeys({ oldKey: newKey })(obj)`
*/
export const renameKeys = curry((keysMap: object, obj) =>
reduce((acc, key) => assoc(keysMap[key] || key, prop(key, obj), acc), {}, keys(obj))
);
/**
*
* @param includeKeys
* @param obj
*/
export const convertToNumber = curry(<T>(includeKeys: string[], obj: T) =>
reduce(
(acc: any, key: string | number | symbol) => {
const value = prop(key, obj);
return assoc(key, includes(key, includeKeys) ? new BigNumber(value).toNumber() : value, acc);
},
{},
keys(obj)
)
);
/**
* 使BigNumber的精确数字表示
* @param includeKeys
* @param obj
*/
export const convertToPreciseness = curry(<T>(includeKeys: (string | number | symbol)[], obj: T) =>
reduce(
(acc: any, key: string | number | symbol) => {
const value = prop(key, obj);
return assoc(
key,
includes(key, includeKeys) && not(new BigNumber(value).isNaN()) ? new BigNumber(value) : value,
acc
);
},
{},
keys(obj)
)
);
/**
* `JSON.parse()`
* @param key
* @param value
* @param transformKeys
* @returns
*/
const parseBigNumber = curry((key: string | number | symbol, value: any, transformKeys: (string | number | symbol)[]) =>
includes(key, transformKeys) && not(new BigNumber(value).isNaN()) ? new BigNumber(value) : value
);
/**
* `JSON.parse()`使BigNumber表示的精确数字的方法
* @param transformKeys
* @returns BigNumber类型转换的函数
*/
export const parseWithPreciseness = (transformKeys: (string | number | symbol)[]) =>
parseBigNumber(__, __, transformKeys);
/**
* `JSON.stringify()`
* @param key
* @param value
* @param transformKeys
* @returns
*/
const stringifyBigNumber = curry(
(key: string | number | symbol, value: any, transformKeys: (string | number | symbol)[]) =>
includes(key, transformKeys) ? new BigNumber(value).toNumber() : value
);
/**
* `JSON.stringify()`
* @param transformKeys
* @returns BigNumber类型转换的函数
*/
export const stringifyWithPreciseness = (transformKeys: (string | number | symbol)[]) =>
stringifyBigNumber(__, __, transformKeys);
/**
*
* ! Acro DesignAnt Design
* @param col
* @param record 使
* @returns
*/
export const renderNumberWithUnit = (unit?: string) => (col: number | null | undefined, record: any) =>
isNil(col) ? '-' : `${col} ${pathOr('', split('.', unit ?? 'chargeUnit'), record)}`;
/**
* -
* @param precision 2
* @returns
*/
export const renderNullableNumber =
(precision = 2) =>
(col: number | null, record: any) =>
isNil(col) ? '-' : new BigNumber(col).toFixed(precision);
/**
*
* ! Acro DesignAnt Design
* @param currency 使
* @param precision 使
* @returns
*/
export const renderCurrency =
(currency: string, precision = 2) =>
(col: number, record?: any) =>
isNil(col) ? '-' : `${currency}${new BigNumber(col).toFixed(precision)}`;
/**
*
* @param amount
* @param currency
* @param precision
* @param groupped
* @returns
*/
export const renderGroupedCurrency = (
amount: number | null,
currency: string,
precision = 2,
groupped = true
): string => {
const precisedAmount = new BigNumber(amount ?? 0);
const fmt = {
prefix: `${currency} `,
decimalSeparator: '.'
};
const grouppedFmt = mergeRight(fmt, { groupSeparator: ',', groupSize: 3 });
return isNil(amount)
? '-'
: groupped
? precisedAmount.toFormat(precision, grouppedFmt)
: precisedAmount.toFormat(precision, fmt);
};
/**
*
* ! Acro DesignAnt Design
* @returns
*/
export const renderPercent =
(presicision = 2) =>
(col: number, _?: unknown) =>
isNil(col) ? '-' : `${new BigNumber(col).times(100).toFixed(presicision)} %`;
/**
*
* @param tag
* @param response
* @param error
*/
export const logError = (tag: string, response: BaseResponse, error?: Error) => {
console.error(`交互时异常[${tag}]:`, response.code, response.message);
if (notNil(error)) {
console.error(error);
}
};
/**
*
* @param tag
* @param error
*/
export const logException = (tag: string, error?: any) => {
console.error(`运行时异常[${tag}]: `, notNil(error) ? errorMessage(error) : '未知错误');
};
/**
*
* @param error
* @returns
*/
export const errorMessage = prop<'message', string>('message');
type ResponseTransformer<T extends BaseResponse> = (response: string) => T;
/**
*
*
* *
*
* @param nameMap
* @param extractKeys
* @param collection
* @returns
*/
export const responseTransformer: <T extends BaseResponse>(
nameMap: Record<string, string>,
extractKeys: string[],
collection: boolean
) => ResponseTransformer<T> = (
nameMap: Record<string, string>,
extractKeys: string[],
collection: boolean
): ResponseTransformer<T> => {
return (response: string) => {
const data = JSON.parse(response);
const baseResponse = {
code: prop('code', data),
message: prop('message', data)
};
const transformedResult = reduce(
(acc, elem) => {
const result = collection
? map(renameKeys(nameMap, __), prop(elem, data) ?? [])
: renameKeys(nameMap, prop(elem, data) ?? {});
return { ...acc, [elem]: result };
},
{},
extractKeys
);
return mergeRight(baseResponse, transformedResult);
};
};
/**
* 使
* @param original
* @returns
*/
export const convertToDirtyable = <T extends object>(original: T): Dirtyable<T> => {
return reduce(
(acc, elem) =>
mergeRight({ [elem]: { value: prop(elem, original), lastValue: prop(elem, original), dirty: false } }, acc),
{},
keys(original)
);
};
/**
* 使
* @param date1
* @param date2
* @returns
*/
export const compareDate = (date1: Date, date2: Date) => {
const date1Time = dayjs(date1);
const date2Time = dayjs(date2);
return date1Time.isBefore(date2Time) ? -1 : date1Time.isAfter(date2Time) ? 1 : 0;
};
/**
*
* @param mode
* @returns
*/
export const translateTaxMode = (mode: string): string => {
return defaultTo('未知', { '0': '未税', '1': '含税' }[mode]);
};
/**
*
* @param property
* @returns
*/
export const lensDirtyable = (property: string) => {
return [
lens(path([property, 'value']), assocPath([property, 'value'])),
lens(path([property, 'dirty']), assocPath([property, 'dirty'])),
lens(path([property, 'lastValue']), assocPath([property, 'lastValue']))
];
};
/**
* lastValue属性
* *
* @param value
* @param origin
* @returns
*/
export const setDirtyableValue = <T>(value: T, origin: DirtyableValue<T>): DirtyableValue<T> => {
return {
dirty: true,
lastValue: origin.value,
value
};
};
/**
* lastValue记录
* *
* @param property
* @param value
* @param origin
* @returns
*/
export const setDirtyableProperty = <T>(property: string, value: unknown, origin: Dirtyable<T>): Dirtyable<T> => {
const [lensValue, lensDirty, lensLastValue] = lensDirtyable(property);
let newObj = set(lensDirty, true, origin);
newObj = set(lensLastValue, view(lensValue, origin), newObj);
newObj = set(lensValue, value, newObj);
return newObj;
};
/**
*
* @param dirtyable
* @param properties
* @returns
*/
export const isDirtied = <T>(dirtyable: Dirtyable<T>, properties?: string[]): boolean => {
return reduce(
(acc, e) => acc || pathEq([e, 'dirty'], true, dirtyable),
false,
isNil(properties) ? keys(dirtyable) : properties
);
};
/**
* a对象的克隆
* b对象合并进入a对象中
* @param predicate
* @param a
* @param b
* @returns
*/
export const mergeRightBy = <T>(
predicate: (origin?: Partial<T>, target?: Partial<T>) => boolean,
a: Partial<T>,
b: Partial<T>
): Partial<T> => {
if (predicate(b, a)) {
return mergeRight(a, b);
} else {
return clone(a);
}
};
/**
* b对象的克隆
* a对象合并进入b对象中
* @param predicate
* @param a
* @param b
* @returns
*/
export const mergeLeftBy = <T>(
predicate: (origin?: Partial<T>, target?: Partial<T>) => boolean,
a: Partial<T>,
b: Partial<T>
): Partial<T> => {
if (predicate(a, b)) {
return mergeLeft(a, b);
} else {
return clone(b);
}
};
/**
*
* a对象追加进入列表b中
* @param predicate
* @param a
* @param b
* @returns
*/
export const appendBy = <T>(
predicate: (origin?: Partial<T>, target?: Partial<T>[]) => boolean,
a: Partial<T>,
b: Partial<T>[]
): Partial<T>[] => {
if (predicate(a, b)) {
return append(a, b);
} else {
return clone(b);
}
};
/**
*
* a对象添加进入列表b的头部
* @param predicate
* @param a
* @param b
* @returns
*/
export const prependBy = <T>(
predicate: (origin?: Partial<T>, target?: Partial<T>[]) => boolean,
a: Partial<T>,
b: Partial<T>[]
): Partial<T>[] => {
if (predicate(a, b)) {
return prepend(a, b);
} else {
return clone(b);
}
};
/**
*
* @param a
* @param b
* @returns
*/
export const includeByProp = <T, P extends keyof T>(a: P, b: T[P][]): boolean =>
compose<T, boolean>(includes<T[P]>(__, b), prop<T>(a));
/**
*
* @param a
* @param b
* @returns
*/
export const notIncludeByProp = <T, P extends keyof T>(a: P, b: T[P][]): boolean =>
compose<T, boolean>(not, includeByProp(a, b));
/**
*
* @param a 1
* @param b 2
* @returns 12
*/
export const subtractSortable = <T extends Sortable>(a: T, b: T): number => subtract(a.sort, b.sort);
type ReduceIndexedType = <T, R>(
fn: (acc: R, elem: T, index: number, list: readonly T[]) => R | Reduced<R>,
acc: R,
list: readonly T[]
) => R;
/**
* Reduce函数accelemindexlist
*/
export const reduceIndexed: ReduceIndexedType = addIndex<T, R>(reduce<T, R>);
/**
*
*/
export const outputWithPrecision: (
origin: string | number | null | undefined,
dp: number,
defaultValue: string
) => string = (origin, dp, defaultValue): string => {
if (isNilOrEmpty(origin)) {
return defaultValue;
}
if (not(isFinite(dp))) {
return new BigNumber(origin).toString();
}
return new BigNumber(origin).toFixed(dp, BigNumber.ROUND_HALF_EVEN);
};
/**
*
*/
export const digitUppercase = (n: number) => {
const fraction = ['角', '分'];
const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const unit = [
['元', '万', '亿'],
['', '拾', '佰', '仟']
];
n = Math.abs(n);
let s = '';
for (let i = 0; i < fraction.length; i++) {
s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
}
s = s || '';
n = Math.floor(n);
for (let i = 0; i < unit[0].length && n > 0; i++) {
let p = '';
for (let j = 0; j < unit[1].length && n > 0; j++) {
p = digit[n % 10] + unit[1][j] + p;
n = Math.floor(n / 10);
}
s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
}
return s
.replace(/(零.)*零元/, '元')
.replace(/(零.)+/g, '零')
.replace(/^整$/, '零元');
};

78
src/utils/index.ts Normal file
View File

@ -0,0 +1,78 @@
import {message} from "antd";
export const alertError = (msg: string) => {
void message.error(msg)
}
export const alertSuccess = (msg: string) => {
void message.success(msg)
}
export const alertInfo = (msg: string) => {
void message.info(msg)
}
export const renderEmptyData = (data: any, suffix = '') => {
if (data == null || data === '') {
return "-"
}
return `${data}${suffix}`
}
// 节流函数
// eslint-disable-next-line @typescript-eslint/ban-types
export function throttle(fn: Function, delay: number) {
let flag = true;
return function() {
if(!flag) return;
flag = false;
setTimeout(() => {
// eslint-disable-next-line prefer-rest-params
fn.apply(this, arguments);
flag = true;
}, delay)
}
}
// 获取列表中序号
export function getIndex(page: number, index: number) : string {
return `${(page - 1) * 20 + Number(index) + 1}`
}
interface Response {
code: number,
data: string,
message: string,
}
interface FileList {
crossOrigin: 'anonymous' | 'use-credentials' | '',
uid: string,
name: string,
status: 'done' | 'uploading' | 'error' | 'removed',
thumbUrl?: string
url: string,
response: Response
}
// 获取文件列表
export function getFileList(files: string[]): FileList[] {
return files.map((item, index) => {
const filename = item.slice(item.lastIndexOf('/') + 1)
return {
crossOrigin: 'anonymous',
uid: `${Date.now() + index}`,
name: filename,
status: 'done',
// response: '{"status": "success"}',
response: { code: 0, message: '', data: item },
url: item,
}
})
}
// 获取上传接口的文件列表
export function getFetchFileList(files: FileList[]) : string[] {
return files.filter(item => item.status === "done").map(item => {
return item?.response?.data
})
}

20
src/utils/lazyload.ts Normal file
View File

@ -0,0 +1,20 @@
//@ts-nocheck
import { lazy } from 'react';
/**
* React组件工具函数
* @param loader
* @returns
*/
export const lazyLoad = <T, U extends keyof T>(loader: (x?: string) => Promise<T>) =>
new Proxy({} as unknown as T, {
get: (target, componentName: string | symbol) => {
if (typeof componentName === 'string') {
return lazy(() =>
loader(componentName).then(x => ({
default: x[componentName as U] as any as React.ComponentType<any>
}))
);
}
}
});

985
tailwind.config.js Normal file
View File

@ -0,0 +1,985 @@
const colors = require('tailwindcss/colors');
const palettes = require('./palette');
module.exports = {
content: [],
presets: [],
darkMode: false, // or 'media' or 'class'
important: false,
corePlugins: {
preflight: false
},
theme: {
screens: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px'
},
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
gray: palettes.gray,
red: palettes.red,
orangered: palettes.orangered,
orange: palettes.orange,
gold: palettes.gold,
yellow: palettes.yellow,
lime: palettes.lime,
green: palettes.green,
sggreen: palettes.sggreen,
cyan: palettes.cyan,
blue: palettes.blue,
arcoblue: palettes.arcoblue,
indigo: colors.indigo,
purple: palettes.purple,
magenta: palettes.magenta,
pink: colors.pink
},
spacing: {
px: '1px',
0: '0px',
0.5: '0.125rem',
1: '0.25rem',
1.5: '0.375rem',
2: '0.5rem',
2.5: '0.625rem',
3: '0.75rem',
3.5: '0.875rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
11: '2.75rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
20: '5rem',
24: '6rem',
28: '7rem',
32: '8rem',
36: '9rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
56: '14rem',
60: '15rem',
64: '16rem',
72: '18rem',
80: '20rem',
96: '24rem'
},
animation: {
none: 'none',
spin: 'spin 1s linear infinite',
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
bounce: 'bounce 1s infinite'
},
backdropBlur: theme => theme('blur'),
backdropBrightness: theme => theme('brightness'),
backdropContrast: theme => theme('contrast'),
backdropGrayscale: theme => theme('grayscale'),
backdropHueRotate: theme => theme('hueRotate'),
backdropInvert: theme => theme('invert'),
backdropOpacity: theme => theme('opacity'),
backdropSaturate: theme => theme('saturate'),
backdropSepia: theme => theme('sepia'),
backgroundColor: theme => theme('colors'),
backgroundImage: {
none: 'none',
'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',
'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))',
'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',
'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))',
'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',
'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))',
'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',
'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))',
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))'
},
backgroundOpacity: theme => theme('opacity'),
backgroundPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top'
},
backgroundSize: {
auto: 'auto',
cover: 'cover',
contain: 'contain'
},
blur: {
0: '0',
none: '0',
sm: '4px',
DEFAULT: '8px',
md: '12px',
lg: '16px',
xl: '24px',
'2xl': '40px',
'3xl': '64px'
},
brightness: {
0: '0',
50: '.5',
75: '.75',
90: '.9',
95: '.95',
100: '1',
105: '1.05',
110: '1.1',
125: '1.25',
150: '1.5',
200: '2'
},
borderColor: theme => ({
...theme('colors'),
DEFAULT: theme('colors.gray.200', 'currentColor')
}),
borderOpacity: theme => theme('opacity'),
borderRadius: {
none: '0px',
sm: '0.125rem',
DEFAULT: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
'3xl': '1.5rem',
full: '9999px'
},
borderWidth: {
DEFAULT: '1px',
0: '0px',
2: '2px',
4: '4px',
8: '8px'
},
boxShadow: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
none: 'none'
},
caretColor: theme => theme('colors'),
contrast: {
0: '0',
50: '.5',
75: '.75',
100: '1',
125: '1.25',
150: '1.5',
200: '2'
},
container: {},
content: {
none: 'none'
},
cursor: {
auto: 'auto',
default: 'default',
pointer: 'pointer',
wait: 'wait',
text: 'text',
move: 'move',
help: 'help',
'not-allowed': 'not-allowed'
},
divideColor: theme => theme('borderColor'),
divideOpacity: theme => theme('borderOpacity'),
divideWidth: theme => theme('borderWidth'),
dropShadow: {
sm: '0 1px 1px rgba(0,0,0,0.05)',
DEFAULT: ['0 1px 2px rgba(0, 0, 0, 0.1)', '0 1px 1px rgba(0, 0, 0, 0.06)'],
md: ['0 4px 3px rgba(0, 0, 0, 0.07)', '0 2px 2px rgba(0, 0, 0, 0.06)'],
lg: ['0 10px 8px rgba(0, 0, 0, 0.04)', '0 4px 3px rgba(0, 0, 0, 0.1)'],
xl: ['0 20px 13px rgba(0, 0, 0, 0.03)', '0 8px 5px rgba(0, 0, 0, 0.08)'],
'2xl': '0 25px 25px rgba(0, 0, 0, 0.15)',
none: '0 0 #0000'
},
fill: { current: 'currentColor' },
grayscale: {
0: '0',
DEFAULT: '100%'
},
hueRotate: {
'-180': '-180deg',
'-90': '-90deg',
'-60': '-60deg',
'-30': '-30deg',
'-15': '-15deg',
0: '0deg',
15: '15deg',
30: '30deg',
60: '60deg',
90: '90deg',
180: '180deg'
},
invert: {
0: '0',
DEFAULT: '100%'
},
flex: {
1: '1 1 0%',
auto: '1 1 auto',
initial: '0 1 auto',
none: 'none'
},
flexGrow: {
0: '0',
DEFAULT: '1'
},
flexShrink: {
0: '0',
DEFAULT: '1'
},
fontFamily: {
sans: [
'ui-sans-serif',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'"Noto Sans"',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"'
],
serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
mono: [
'ui-monospace',
'SFMono-Regular',
'Menlo',
'Monaco',
'Consolas',
'"Liberation Mono"',
'"Courier New"',
'monospace'
]
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
'5xl': ['3rem', { lineHeight: '1' }],
'6xl': ['3.75rem', { lineHeight: '1' }],
'7xl': ['4.5rem', { lineHeight: '1' }],
'8xl': ['6rem', { lineHeight: '1' }],
'9xl': ['8rem', { lineHeight: '1' }]
},
fontWeight: {
thin: '100',
extralight: '200',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900'
},
gap: theme => theme('spacing'),
gradientColorStops: theme => theme('colors'),
gridAutoColumns: {
auto: 'auto',
min: 'min-content',
max: 'max-content',
fr: 'minmax(0, 1fr)'
},
gridAutoRows: {
auto: 'auto',
min: 'min-content',
max: 'max-content',
fr: 'minmax(0, 1fr)'
},
gridColumn: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-7': 'span 7 / span 7',
'span-8': 'span 8 / span 8',
'span-9': 'span 9 / span 9',
'span-10': 'span 10 / span 10',
'span-11': 'span 11 / span 11',
'span-12': 'span 12 / span 12',
'span-full': '1 / -1'
},
gridColumnEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13'
},
gridColumnStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13'
},
gridRow: {
auto: 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-full': '1 / -1'
},
gridRowStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7'
},
gridRowEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7'
},
gridTemplateColumns: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
7: 'repeat(7, minmax(0, 1fr))',
8: 'repeat(8, minmax(0, 1fr))',
9: 'repeat(9, minmax(0, 1fr))',
10: 'repeat(10, minmax(0, 1fr))',
11: 'repeat(11, minmax(0, 1fr))',
12: 'repeat(12, minmax(0, 1fr))'
},
gridTemplateRows: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))'
},
height: theme => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
full: '100%',
screen: '100vh'
}),
inset: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
full: '100%',
'-1/2': '-50%',
'-1/3': '-33.333333%',
'-2/3': '-66.666667%',
'-1/4': '-25%',
'-2/4': '-50%',
'-3/4': '-75%',
'-full': '-100%'
}),
keyframes: {
spin: {
to: {
transform: 'rotate(360deg)'
}
},
ping: {
'75%, 100%': {
transform: 'scale(2)',
opacity: '0'
}
},
pulse: {
'50%': {
opacity: '.5'
}
},
bounce: {
'0%, 100%': {
transform: 'translateY(-25%)',
animationTimingFunction: 'cubic-bezier(0.8,0,1,1)'
},
'50%': {
transform: 'none',
animationTimingFunction: 'cubic-bezier(0,0,0.2,1)'
}
}
},
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em'
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
3: '.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem'
},
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal'
},
margin: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing'))
}),
maxHeight: theme => ({
...theme('spacing'),
full: '100%',
screen: '100vh'
}),
maxWidth: (theme, { breakpoints }) => ({
none: 'none',
0: '0rem',
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
full: '100%',
min: 'min-content',
max: 'max-content',
prose: '65ch',
...breakpoints(theme('screens'))
}),
minHeight: {
0: '0px',
full: '100%',
screen: '100vh'
},
minWidth: {
0: '0px',
full: '100%',
min: 'min-content',
max: 'max-content'
},
objectPosition: {
bottom: 'bottom',
center: 'center',
left: 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
right: 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
top: 'top'
},
opacity: {
0: '0',
5: '0.05',
10: '0.1',
20: '0.2',
25: '0.25',
30: '0.3',
40: '0.4',
50: '0.5',
60: '0.6',
70: '0.7',
75: '0.75',
80: '0.8',
90: '0.9',
95: '0.95',
100: '1'
},
order: {
first: '-9999',
last: '9999',
none: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12'
},
outline: {
none: ['2px solid transparent', '2px'],
white: ['2px dotted white', '2px'],
black: ['2px dotted black', '2px']
},
padding: theme => theme('spacing'),
placeholderColor: theme => theme('colors'),
placeholderOpacity: theme => theme('opacity'),
ringColor: theme => ({
DEFAULT: theme('colors.blue.500', '#3b82f6'),
...theme('colors')
}),
ringOffsetColor: theme => theme('colors'),
ringOffsetWidth: {
0: '0px',
1: '1px',
2: '2px',
4: '4px',
8: '8px'
},
ringOpacity: theme => ({
DEFAULT: '0.5',
...theme('opacity')
}),
ringWidth: {
DEFAULT: '3px',
0: '0px',
1: '1px',
2: '2px',
4: '4px',
8: '8px'
},
rotate: {
'-180': '-180deg',
'-90': '-90deg',
'-45': '-45deg',
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
'-2': '-2deg',
'-1': '-1deg',
0: '0deg',
1: '1deg',
2: '2deg',
3: '3deg',
6: '6deg',
12: '12deg',
45: '45deg',
90: '90deg',
180: '180deg'
},
saturate: {
0: '0',
50: '.5',
100: '1',
150: '1.5',
200: '2'
},
scale: {
0: '0',
50: '.5',
75: '.75',
90: '.9',
95: '.95',
100: '1',
105: '1.05',
110: '1.1',
125: '1.25',
150: '1.5'
},
sepia: {
0: '0',
DEFAULT: '100%'
},
skew: {
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
'-2': '-2deg',
'-1': '-1deg',
0: '0deg',
1: '1deg',
2: '2deg',
3: '3deg',
6: '6deg',
12: '12deg'
},
space: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing'))
}),
stroke: {
current: 'currentColor'
},
strokeWidth: {
0: '0',
1: '1',
2: '2'
},
textColor: theme => theme('colors'),
textOpacity: theme => theme('opacity'),
transformOrigin: {
center: 'center',
top: 'top',
'top-right': 'top right',
right: 'right',
'bottom-right': 'bottom right',
bottom: 'bottom',
'bottom-left': 'bottom left',
left: 'left',
'top-left': 'top left'
},
transitionDelay: {
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms'
},
transitionDuration: {
DEFAULT: '150ms',
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms'
},
transitionProperty: {
none: 'none',
all: 'all',
DEFAULT:
'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
colors: 'background-color, border-color, color, fill, stroke',
opacity: 'opacity',
shadow: 'box-shadow',
transform: 'transform'
},
transitionTimingFunction: {
DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)',
linear: 'linear',
in: 'cubic-bezier(0.4, 0, 1, 1)',
out: 'cubic-bezier(0, 0, 0.2, 1)',
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)'
},
translate: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
full: '100%',
'-1/2': '-50%',
'-1/3': '-33.333333%',
'-2/3': '-66.666667%',
'-1/4': '-25%',
'-2/4': '-50%',
'-3/4': '-75%',
'-full': '-100%'
}),
width: theme => ({
auto: 'auto',
...theme('spacing'),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
'1/12': '8.333333%',
'2/12': '16.666667%',
'3/12': '25%',
'4/12': '33.333333%',
'5/12': '41.666667%',
'6/12': '50%',
'7/12': '58.333333%',
'8/12': '66.666667%',
'9/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
full: '100%',
screen: '100vw',
min: 'min-content',
max: 'max-content'
}),
zIndex: {
auto: 'auto',
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50'
}
},
variantOrder: [
'first',
'last',
'odd',
'even',
'visited',
'checked',
'empty',
'read-only',
'group-hover',
'group-focus',
'focus-within',
'hover',
'focus',
'focus-visible',
'active',
'disabled'
],
variants: {
accessibility: ['responsive', 'focus-within', 'focus'],
alignContent: ['responsive'],
alignItems: ['responsive'],
alignSelf: ['responsive'],
animation: ['responsive'],
appearance: ['responsive'],
backdropBlur: ['responsive'],
backdropBrightness: ['responsive'],
backdropContrast: ['responsive'],
backdropFilter: ['responsive'],
backdropGrayscale: ['responsive'],
backdropHueRotate: ['responsive'],
backdropInvert: ['responsive'],
backdropOpacity: ['responsive'],
backdropSaturate: ['responsive'],
backdropSepia: ['responsive'],
backgroundAttachment: ['responsive'],
backgroundBlendMode: ['responsive'],
backgroundClip: ['responsive'],
backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
backgroundImage: ['responsive'],
backgroundOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
backgroundPosition: ['responsive'],
backgroundRepeat: ['responsive'],
backgroundSize: ['responsive'],
backgroundOrigin: ['responsive'],
blur: ['responsive'],
borderCollapse: ['responsive'],
borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
borderOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
borderRadius: ['responsive'],
borderStyle: ['responsive'],
borderWidth: ['responsive'],
boxDecorationBreak: ['responsive'],
boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
boxSizing: ['responsive'],
brightness: ['responsive'],
clear: ['responsive'],
container: ['responsive'],
contrast: ['responsive'],
cursor: ['responsive'],
display: ['responsive'],
divideColor: ['responsive', 'dark'],
divideOpacity: ['responsive', 'dark'],
divideStyle: ['responsive'],
divideWidth: ['responsive'],
dropShadow: ['responsive'],
fill: ['responsive'],
filter: ['responsive'],
flex: ['responsive'],
flexDirection: ['responsive'],
flexGrow: ['responsive'],
flexShrink: ['responsive'],
flexWrap: ['responsive'],
float: ['responsive'],
fontFamily: ['responsive'],
fontSize: ['responsive'],
fontSmoothing: ['responsive'],
fontStyle: ['responsive'],
fontVariantNumeric: ['responsive'],
fontWeight: ['responsive'],
gap: ['responsive'],
gradientColorStops: ['responsive', 'dark', 'hover', 'focus'],
grayscale: ['responsive'],
gridAutoColumns: ['responsive'],
gridAutoFlow: ['responsive'],
gridAutoRows: ['responsive'],
gridColumn: ['responsive'],
gridColumnEnd: ['responsive'],
gridColumnStart: ['responsive'],
gridRow: ['responsive'],
gridRowEnd: ['responsive'],
gridRowStart: ['responsive'],
gridTemplateColumns: ['responsive'],
gridTemplateRows: ['responsive'],
height: ['responsive'],
hueRotate: ['responsive'],
inset: ['responsive'],
invert: ['responsive'],
isolation: ['responsive'],
justifyContent: ['responsive'],
justifyItems: ['responsive'],
justifySelf: ['responsive'],
letterSpacing: ['responsive'],
lineHeight: ['responsive'],
listStylePosition: ['responsive'],
listStyleType: ['responsive'],
margin: ['responsive'],
maxHeight: ['responsive'],
maxWidth: ['responsive'],
minHeight: ['responsive'],
minWidth: ['responsive'],
mixBlendMode: ['responsive'],
objectFit: ['responsive'],
objectPosition: ['responsive'],
opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
order: ['responsive'],
outline: ['responsive', 'focus-within', 'focus'],
overflow: ['responsive'],
overscrollBehavior: ['responsive'],
padding: ['responsive'],
placeContent: ['responsive'],
placeItems: ['responsive'],
placeSelf: ['responsive'],
placeholderColor: ['responsive', 'dark', 'focus'],
placeholderOpacity: ['responsive', 'dark', 'focus'],
pointerEvents: ['responsive'],
position: ['responsive'],
resize: ['responsive'],
ringColor: ['responsive', 'dark', 'focus-within', 'focus'],
ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'],
ringOffsetWidth: ['responsive', 'focus-within', 'focus'],
ringOpacity: ['responsive', 'dark', 'focus-within', 'focus'],
ringWidth: ['responsive', 'focus-within', 'focus'],
rotate: ['responsive', 'hover', 'focus'],
saturate: ['responsive'],
scale: ['responsive', 'hover', 'focus'],
sepia: ['responsive'],
skew: ['responsive', 'hover', 'focus'],
space: ['responsive'],
stroke: ['responsive'],
strokeWidth: ['responsive'],
tableLayout: ['responsive'],
textAlign: ['responsive'],
textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'],
textOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'],
textOverflow: ['responsive'],
textTransform: ['responsive'],
transform: ['responsive'],
transformOrigin: ['responsive'],
transitionDelay: ['responsive'],
transitionDuration: ['responsive'],
transitionProperty: ['responsive'],
transitionTimingFunction: ['responsive'],
translate: ['responsive', 'hover', 'focus'],
userSelect: ['responsive'],
verticalAlign: ['responsive'],
visibility: ['responsive'],
whitespace: ['responsive'],
width: ['responsive'],
wordBreak: ['responsive'],
zIndex: ['responsive', 'focus-within', 'focus']
},
plugins: []
};