小程序可以加载账单

This commit is contained in:
qiaomu 2024-04-29 17:28:31 +08:00
parent 9065c2dda3
commit f1185057b8
37 changed files with 8813 additions and 1347 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
spaces_around_operators = true
max_line_length = 120

13
.eslintignore Normal file
View File

@ -0,0 +1,13 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
build
# don't lint nyc coverage output
coverage
.eslintrc.js
vite.config.ts
palette.js
tailwind.config.js

38
.eslintrc.js Normal file
View File

@ -0,0 +1,38 @@
module.exports = {
root: true,
env: {
node: true,
browser: true,
commonjs: true,
es6: true,
amd: true
},
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json']
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
overrides: [
{
files: ['**/*.tsx', '**/*.ts'],
rules: {
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'off',
}
}
]
};

420
.gitignore vendored
View File

@ -1,24 +1,418 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.pnpm-debug.log*
node_modules
# Debug Files
*.sql
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# General
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Local History for Visual Studio Code
.history/
# Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all
# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
# End of https://www.toptal.com/developers/gitignore/api/jetbrains+all
.vscode/*
!.vscode/settings.json
.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# pnpm lock file
pnpm-lock.yaml

18
.prettierignore Normal file
View File

@ -0,0 +1,18 @@
*.min.js
/node_modules
/dist
# OS
.DS_Store
.idea
.editorconfig
.npmrc
package-lock.json
# Ignored suffix
*.log
*.md
*.svg
*.png
*ignore
## Built-files
.cache
dist

20
.prettierrc.js Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
printWidth: 120,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
trailingComma: 'none',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'avoid',
rangeStart: 0,
rangeEnd: Infinity,
requirePragma: false,
insertPragma: false,
proseWrap: 'preserve',
htmlWhitespaceSensitivity: 'css',
endOfLine: 'lf'
};

View File

@ -1,30 +1,18 @@
# React + TypeScript + Vite
# 用电管理服务项目(前端项目)
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
该项目为华昌宝能发布的采用众包模式承接的小型Web服务系统项目。
Currently, two official plugins are available:
项目采用以下技术框架:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
- React 18
- React Router 6
- Emotion
- Tailwind CSS 3
- Twin.macro
- Ant Design 5
## Expanding the ESLint configuration
项目采用Vite 3编译打包使用Typescript编写。
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
项目详细设计方案见[详细设计方案](https://kdocs.cn/l/cawe22YUV3bJ),该设计方案未经许可,禁止私自修改。
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
项目任务分配与状态概览表见[任务概况](https://kdocs.cn/l/camrXvBMlCNs)。

View File

@ -1,13 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>华昌宝能</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>用电管理服务系统</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="pragram" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate"/>
<meta http-equiv="expires" content="0"/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

8060
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +1,84 @@
{
"name": "test",
"private": true,
"version": "0.0.0",
"type": "module",
"name": "trans_power_manage",
"version": "0.1.0",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"local-mock": "PROXY_TARGET=local_mock vite --host",
"window-local-mock": "set PROXY_TARGET=local_mock && vite --host",
"remote": "PROXY_TARGET=remote vite --host",
"window-remote": "set PROXY_TARGET=remote && vite --host",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"serve": "vite preview"
},
"dependencies": {
"antd-mobile": "^5.34.0",
"antd-mobile-icons": "^0.3.0",
"ramda": "^0.29.1",
"@ant-design/compatible": "^5.1.1",
"@ant-design/icons": "^5.0.1",
"@ant-design/pro-components": "^2.6.43",
"@antv/g2": "^4.2.7",
"@emotion/css": "^11.9.0",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@tanstack/react-query": "^4.3.9",
"@tanstack/react-query-devtools": "^4.3.9",
"@uiw/react-md-editor": "^3.14.5",
"antd": "^5.1.6",
"axios": "^0.27.2",
"babel-polyfill": "^6.26.0",
"bignumber.js": "^9.0.2",
"clipboard": "^2.0.11",
"dayjs": "^1.11.2",
"debounce-promise": "^3.1.2",
"decamelize-keys": "^2.0.1",
"decimal.js": "^10.3.1",
"formik": "^2.2.9",
"immer": "^9.0.14",
"jspdf": "^2.5.1",
"qs": "^6.10.3",
"ramda": "^0.28.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.2"
"react-router": "^6.10.0",
"react-router-dom": "^6.3.0",
"react-to-print": "^2.14.15",
"react-use": "^17.4.0",
"resize-observer-polyfill": "^1.5.1",
"use-animate-number": "^1.0.5",
"use-immer": "^0.7.0",
"zustand": "^4.3.6"
},
"devDependencies": {
"@types/node": "^20.11.25",
"@types/ramda": "^0.29.11",
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.4"
"@babel/core": "^7.20.12",
"@emotion/babel-plugin": "^11.9.2",
"@emotion/babel-plugin-jsx-pragmatic": "^0.1.5",
"@ricons/fluent": "^0.12.0",
"@ricons/utils": "^0.1.6",
"@types/debounce-promise": "^3.1.4",
"@types/qs": "^6.9.7",
"@types/ramda": "^0.28.13",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-router": "^5.1.18",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0",
"@vitejs/plugin-react": "^1.3.2",
"autoprefixer": "^10.4.7",
"clsx": "^1.1.1",
"esbuild": "^0.14.44",
"eslint": "^8.16.0",
"html2pdf.js": "^0.10.1",
"less": "^4.1.2",
"postcss": "^8.4.14",
"prettier": "^2.7.1",
"tailwindcss": "^3.1.6",
"twin.macro": "^3.4.1",
"typescript": "^4.7.2",
"vite": "^4.4.0",
"vite-plugin-imp": "^2.2.0"
},
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "49",
"ios": "10"
}
}
]
]
}
"babelMacros": {
"twin": {
"preset": "emotion"
}
}
}

View File

@ -1,20 +1,17 @@
import { Route, Routes } from 'react-router-dom';
//@ts-nocheck
import { suspense } from '@c/hoc/suspense';
import { CentralSpin } from '@c/ui/CentralSpin';
import { FC, Suspense } from 'react';
import React from 'react';
import { Route, Routes } from 'react-router-dom';
const UserReport = suspense('UserReport', () => import('@p/report'));
const Test = suspense('Test', () => import('@p/test'));
const Recharge = React.lazy(() => import("@p/Recharge/index"))
const RechargeRecords = React.lazy(() => import("@p/RechargeRecords/index"))
const Return = React.lazy(() => import("@p/Return/index"))
const App: FC = () => {
/**
*
* @returns
*/
export const App: FC = () => {
return (
<Suspense>
<Routes>
<Route path="/recharge" element={<Recharge />} />
<Route path="/rechargeRecords" element={<RechargeRecords />} />
<Route path="/return" element={<Return />} />
</Routes>
</Suspense>
)
}
export default App;
<UserReport />
);
};

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -1,12 +0,0 @@
import { Card } from "antd-mobile";
import { FC } from "react";
const MeterInfo: FC = () => {
return <Card title='256160646' >
<div> xxxxx </div>
<div> 500 </div>
<div> xxxxx </div>
</Card>
}
export default MeterInfo;

7
src/config/index.tsx Normal file
View File

@ -0,0 +1,7 @@
export const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 } };
export const disabledPark = [
'/calculate/list', '/park/management', "/son-account", "/flow",
"/park/registry", "/publicity/retrieval",
"/sync/status", "/sync/setting", "/log"
]

208
src/config/menu.tsx Normal file
View File

@ -0,0 +1,208 @@
import React from 'react';
import ArchiveSettings20Regular from '@ricons/fluent/lib/ArchiveSettings20Regular';
import DataArea24Regular from '@ricons/fluent/lib/DataArea24Regular';
import DocumentEdit24Regular from '@ricons/fluent/lib/DocumentEdit24Regular';
import Glance24Regular from '@ricons/fluent/lib/Glance24Regular';
import Pulse32Regular from '@ricons/fluent/lib/Pulse32Regular';
import Settings32Regular from '@ricons/fluent/lib/Settings32Regular';
import SignOut24Regular from '@ricons/fluent/lib/SignOut24Regular';
import ClipboardDataBar32Regular from '@ricons/fluent/lib/ClipboardDataBar32Regular';
import ContentView32Regular from '@ricons/fluent/lib/ContentView32Regular';
import ArrowSyncCheckmark24Regular from '@ricons/fluent/lib/ArrowSyncCheckmark24Regular';
import People16Regular from '@ricons/fluent/lib/People16Regular';
import Account from "@ricons/fluent/PersonAccounts24Regular"
import Sale from '@ricons/fluent/ShoppingBagTag24Regular'
import 'twin.macro';
import { PrivilegedItemType } from '@/shared/system';
import Permission from '@ricons/fluent/lib/LockClosed12Regular'
import Statement from '@ricons/fluent/lib/FormNew24Regular'
import ReadingManagement from '@ricons/fluent/lib/Board16Filled'
import Refund from '@ricons/fluent/lib/GroupReturn24Regular'
import { ProjectOutlined } from '@ant-design/icons'
const MainMenuItems: PrivilegedItemType[] = [
{
label: '概要信息',
icon: <Glance24Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'glance',
link: '/',
any: [0, 1, 2]
},
{
label: '子账号管理',
icon: <People16Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'son',
link: '/son-account',
any: [0]
},
{
label: '设备管理',
icon: <ProjectOutlined />,
key: 'equipment',
all: [0],
children: [
{ label: '卡管理', key: 'card', link: '/equipment/card', all: [0] },
{ label: '通讯协议管理', key: 'message', link: '/equipment/message', all: [0] },
{ label: '厂家管理', key: 'factory', link: '/equipment/factory', all: [0] },
{ label: '电表设置', key: 'meter-setting', link: '/equipment/meter-setting', all: [0] },
{ label: '电表操作', key: 'meter-operate', link: '/equipment/meter-operate', all: [0] },
]
},
{
label: '用户管理',
icon: <ArchiveSettings20Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'setup',
all: [0],
children: [
{ label: '园区管理', key: 'setup-park', link: '/park/management', all: [0] },
{ label: '建筑管理', key: 'building', link: '/park/building', all: [0] },
{ label: '商户管理', key: 'setup-tenement', link: '/park/tenement', all: [0] },
{ label: '园区表计管理', key: 'setup-kv04', link: '/park/04kv', all: [0] },
{ label: '电表箱管理', key: 'meter-box', link: '/park/meter-box', all: [0] },
{ label: '表计分摊管理', key: 'setup-pooled', link: '/park/pooled', all: [0] }
]
},
{
label: "抄表管理", icon: <ReadingManagement tw="w-4 h-4" onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined}/>, key: 'readingManagement', all: [0], children: [
{ label: '抄表记录', icon: <ClipboardDataBar32Regular tw="w-4 h-4" onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined} />, key: 'reading', link: '/reading', all: [0] },
{ label: '退补电量', icon: <Refund tw="w-4 h-4" onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined} />, key: 'refund', link: '/refund', all: [0] },
]
},
{
label: '售电管理',
icon: <Sale tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'electric',
any: [0,],
children: [
{ label: '商户充值', key: 'electric-recharge', link: '/electric/recharge', any: [0] },
{ label: '商户冲正', key: 'electric-reversal', link: '/electric/reversal', any: [0] },
{ label: '商户退费', key: 'electric-return', link: '/electric/return', all: [0] }
]
},
// { label: '商户充值', icon: <Payment28Regular tw="w-4 h-4" />, key: 'charge', link: '/charge', all: [0] },
{
label: '电费核算',
icon: <DocumentEdit24Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'calculate',
link: '/calculate/list',
all: [0]
},
{
label: '企业电费核算',
icon: <DocumentEdit24Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'calculate-all',
link: '/calculate/list-all',
all: [2]
},
{
label: '历史电费核算',
icon: <DataArea24Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'publicity',
any: [0, 1, 2],
children: [
{ label: '往期电费核算检索', key: 'publicity-history', link: '/publicity/retrieval', any: [0, 1, 2] },
{ label: '电费核算撤回审核', key: 'publicity-audit', link: '/publicity/audit', all: [2] }
]
},
{
label: '账务管理',
icon: <Account tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'accounting',
any: [0,],
children: [
{ label: '账务记录', key: 'accountingRecord', link: '/accounting/record', any: [0] },
{ label: '账务余额查询', key: 'accountingBalance', link: '/accounting/balance', any: [0] },
{ label: '余额导入', key: 'balanceExport', link: '/accounting/balanceExport', any: [0] },
// { label: '结算记录检索', key: 'accountingFinish', link: '/accounting/finish', all: [0] }
]
},
{ label: '发票管理', icon: <ContentView32Regular tw="w-4 h-4" onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined} />, key: 'invoice', all: [0],children: [
{ label: '发票开具', key: 'invoice-notyet', link: '/invoice/notyet', any: [0] },
{ label: '已开发票', key: 'invoice-already', link: '/invoice/already', any: [0] },
] },
{
label: '同步管理',
icon: <ArrowSyncCheckmark24Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'sync',
any: [0],
children: [
{ label: '近期同步任务状态', key: 'syncStatus', link: '/sync/status', any: [0, 2] },
{ label: '同步设置', key: 'syncSetting', link: '/sync/setting', all: [0, 2] }
]
},
{
label: "流程设置",
icon: <Settings32Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'flow',
link: '/flow',
any: [0],
},
{
label: "报表查询",
icon: <Statement tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'statement',
link: '/statement',
any: [0],
},
{
label: "日志查询",
icon: <Statement tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'log',
link: '/log',
any: [0],
},
{
label: '系统管理',
icon: <Settings32Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'setting',
all: [2],
children: [
{ label: '系统账号管理', key: 'setting-accounts', link: '/account/management', all: [2] },
{ label: '管理单位开户', key: 'setting-openAccount', link: '/account/open', all: [2] },
{ label: '服务期限管理', key: 'setting-charge', link: '/account/charge', all: [2] }
]
},
{
label: '天神模式',
icon: <Pulse32Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'godMode',
specific: '000',
children: [
{ label: '用户管理', key: 'gmUsers', link: '/god/user', specific: '000' },
{ label: '园区管理', key: 'gmParks', link: '/god/park', specific: '000' },
{ label: '报表管理', key: 'gmReport', link: '/god/report', specific: '000' },
{
key: 'gmSetup',
specific: '000',
label: '物业信息管理',
children: [
{ label: '商户管理', key: 'gmReport-tenement', link: '/god/specific/tenement', specific: '000' },
{ label: '园区表计管理', key: 'gmReport-kv04', link: '/god/specific/04kv', specific: '000' },
{ label: '抄表记录', key: 'gmReport-reading', link: '/god/specific/reading', specific: '000' }
]
}
]
},
{
label: '权限设置',
icon: <Permission tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'permission',
any: [0],
link: '/permission'
},
{
label: '退出系统',
icon: <SignOut24Regular tw="w-4 h-4" onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />,
key: 'quit',
any: [0, 1, 2],
link: '/logout'
}
];
export default MainMenuItems;

View File

@ -0,0 +1,21 @@
/**
* `instanceof`
*
* ! `Error`
*/
export default class BaseError extends Error {
constructor(message: string) {
super(message);
// ! 以下这一段是一套继承Error类的时候的固定用法用以支持instanceof操作符所有继承Error类代表异常的异常类都必须原样复制。
this.name = new.target.name;
if (typeof (Error as any).captureStackTrace === 'function') {
(Error as any).captureStackTrace(this, new.target);
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(this, new.target.prototype);
} else {
(this as any).__proto__ = new.target.prototype;
}
}
}

View File

@ -0,0 +1,30 @@
import BaseError from './BaseError';
/**
*
*/
export default class NetworkError extends BaseError {
/**
*
*/
private code: number;
constructor(code: number, message: string) {
super(message);
this.code = code;
}
/**
*
*/
get ErrCode(): number {
return this.code;
}
/**
*
*/
get ErrMessage(): string {
return this.message;
}
}

View File

@ -0,0 +1,21 @@
import BaseError from './BaseError';
/**
* 访
*/
export default class RemoteServiceError extends BaseError {
private code: number;
constructor(code: number, message: string) {
super(message);
this.code = code;
}
get ErrCode(): number {
return this.code;
}
get ErrMessage(): string {
return this.message;
}
}

15
src/favicon.svg Normal file
View File

@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

7
src/logo.svg Normal file
View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

33
src/main.less Normal file
View File

@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
#root {
height: 100%;
width: 100%;
overflow: hidden;
}
.anticon svg {
vertical-align: baseline;
}
.ant-descriptions-item {
vertical-align: baseline;
.ant-descriptions-item-container {
align-items: baseline;
}
}
.ant-table-thead .ant-table-cell {
background-color: var(--color-primary-deep) !important;
color: #fff !important;
}
:root {
--color-primary-deep: #00694d;
}
.ant-picker-ranges {
margin-top: 0;
}

View File

@ -1,13 +1,49 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
//@ts-nocheck
import { App } from '@/App';
import GlobalStyles from '@/styles/GlobalStyles';
import { queryClient } from '@q/query_client';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { App as AntApp, ConfigProvider } from 'antd';
import zhCN from 'antd/es/locale/zh_CN';
import 'babel-polyfill';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import arraySupport from 'dayjs/plugin/arraySupport';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { enableAllPlugins } from 'immer';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './main.less';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)
// 启用Dayjs库中相应的功能。
dayjs.locale('zh-cn');
dayjs.extend(customParseFormat);
dayjs.extend(arraySupport);
// 启用Immer库中的所有功能。
enableAllPlugins();
// 我们将dayjs默认的toString方法和toJSON重写为format到指定格式
dayjs.prototype.constructor.prototype.toString = dayjs.prototype.constructor.prototype.toJSON = function () {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return this.format('YYYY-MM-DD HH:mm:ss');
};
// 用于修补不存在ResizeObserver功能的代码
if (!window.ResizeObserver) {
window.ResizeObserver = import('resize-observer-polyfill').default;
}
ReactDOM.createRoot(document.getElementById('root')).render(
<QueryClientProvider client={queryClient}>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#00B578' } }}>
<AntApp>
<BrowserRouter>
<GlobalStyles />
<App />
<ReactQueryDevtools initialIsOpen={false} />
</BrowserRouter>
</AntApp>
</ConfigProvider>
</QueryClientProvider>
);

View File

@ -1,68 +0,0 @@
import MeterInfo from "@c/MeterInfo";
import { Button, Input, JumboTabs, NavBar, Space, Toast } from "antd-mobile";
import { UserContactOutline } from "antd-mobile-icons";
import { FC } from "react";
import { useNavigate } from "react-router-dom";
const back = () =>
Toast.show({
content: '点击了返回区域',
duration: 1000,
})
const Recharge: FC = () => {
const navigate = useNavigate();
const jumpToRecords = () => {
console.log('跳转')
navigate('/rechargeRecords')
}
const right = (
<div>
<Space style={{ '--gap': '16px' }}>
<div onClick={jumpToRecords}></div>
<div><UserContactOutline /></div>
</Space>
</div>
)
return <div>
<NavBar right={right} onBack={back}>
</NavBar>
<div style={{padding: '2vw'}}>
<div style={{marginBottom: '2vw'}}> </div>
<div style={{display: 'flex'}}>
<Input placeholder='请输入要充值的表号或公司名称' clearable style={{flex: 1}} />
<Button color='primary' fill='none'>
</Button>
</div>
<MeterInfo />
<div style={{marginTop: '4vw'}}>
<div> </div>
<JumboTabs>
<JumboTabs.Tab title='100' description='' key='100'></JumboTabs.Tab>
<JumboTabs.Tab title='200' description='' key='200'></JumboTabs.Tab>
<JumboTabs.Tab title='500' description='' key='500'></JumboTabs.Tab>
</JumboTabs>
<JumboTabs>
<JumboTabs.Tab title='1000' description='' key='1000'></JumboTabs.Tab>
<JumboTabs.Tab title='2000' description='' key='2000'></JumboTabs.Tab>
<JumboTabs.Tab title='自定义' description='' key='自定义'></JumboTabs.Tab>
</JumboTabs>
<div style={{marginTop: '4vw'}}>
<Input placeholder='请输入自定义金额' clearable />
</div>
<div style={{marginTop: '4vw'}}>
</div>
</div>
</div>
</div>
}
export default Recharge;

View File

@ -1,47 +0,0 @@
import { NavBar } from "antd-mobile";
import { DownOutline } from "antd-mobile-icons";
import { FC } from "react";
import { useNavigate } from "react-router-dom";
const RechargeRecords: FC = () => {
const navigate = useNavigate();
const back = () => {
console.log('跳转')
navigate(-1)
}
return <div>
<NavBar onBack={back}>
</NavBar>
<div style={{padding: '4vw'}}>
<div style={{marginTop: '2w'}}>
2024 <DownOutline style={{marginLeft: '2vw'}} />
</div>
<div style={{marginTop: '3vw'}}>
<div style={{display: 'flex', alignItems: 'center', marginTop: '3vw'}}>
<div style={{flex: 1}}>
<div >5461132156</div>
<div > 2024-03-07 13:00:00 </div>
</div>
<div>500</div>
</div>
<div style={{display: 'flex', alignItems: 'center', marginTop: '3vw'}}>
<div style={{flex: 1}}>
<div >5461132156</div>
<div > 2024-03-07 13:00:00 </div>
</div>
<div>500</div>
</div>
<div style={{display: 'flex', alignItems: 'center', marginTop: '3vw'}}>
<div style={{flex: 1}}>
<div >5461132156</div>
<div > 2024-03-07 13:00:00 </div>
</div>
<div>500</div>
</div>
</div>
</div>
</div>
}
export default RechargeRecords;

View File

@ -1,34 +0,0 @@
import MeterInfo from "@c/MeterInfo";
import { Button, Input, NavBar } from "antd-mobile";
import { QuestionCircleOutline } from "antd-mobile-icons";
import { FC } from "react";
const Return: FC = () => {
return <div>
<NavBar>
退
</NavBar>
<div style={{padding: '2vw'}}>
<div style={{marginBottom: '2vw'}}> 退 </div>
<div style={{display: 'flex'}}>
<Input placeholder='请输入要退费的表号或公司名称' clearable style={{flex: 1}} />
<Button color='primary' fill='none'>
</Button>
</div>
<MeterInfo />
<div style={{marginTop: '4vw'}}>
<div> </div>
<div style={{marginTop: '4vw'}}>
<Input placeholder='最多可赎回100元' clearable />
</div>
<div style={{marginTop: '4vw'}}>
{2.5} <QuestionCircleOutline style={{marginLeft: '2vw'}} />
</div>
</div>
</div>
</div>
}
export default Return;

34
src/pages/errors/404.tsx Normal file
View File

@ -0,0 +1,34 @@
import Warning24Regular from '@ricons/fluent/lib/Warning24Regular';
import { FC } from 'react';
import { useNavigate } from 'react-router-dom';
import 'twin.macro';
/**
* 404
* @returns 404
*/
export const NotFoundPage: FC = () => {
const navigate = useNavigate();
return (
<div tw="w-full h-full flex flex-col justify-center items-center">
<div tw="flex flex-row justify-center items-center mb-8">
<div>
<Warning24Regular tw="w-[36px] h-[36px] pt-1.5 text-red-5" onPointerEnterCapture={undefined}
onPointerLeaveCapture={undefined} />
</div>
<h1 tw="text-3xl ml-4"></h1>
</div>
<div tw="mt-2">
<a
onClick={() => navigate(-1)}
tw="text-arcoblue-3 hover:text-arcoblue-5 active:text-arcoblue-7 cursor-pointer"
>
</a>
</div>
</div>
);
};

8
src/pages/test/index.tsx Normal file
View File

@ -0,0 +1,8 @@
function Test() {
return "test222222"
}
export { Test }

168
src/states/area_store.ts Normal file
View File

@ -0,0 +1,168 @@
//@ts-nocheck
import { SyncParamAction } from '@/shared/foundation';
import { Area } from '@/shared/models';
import { OptionProps } from '@arco-design/web-react/es/Select';
import { notNil } from '@u/funcs';
import {
filter,
find,
includes,
isEmpty,
isNil,
map,
min,
not,
pluck,
propEq,
reduce,
uniq
} from 'ramda';
import { StateSelector } from 'zustand';
import { createStoreHook } from './store_creator';
/**
*
*/
interface AreaStore {
areas: Area[];
}
type Actions = {
/**
*
*/
loadAreas: SyncParamAction<Area[]>;
};
/**
*
*/
const initialAreaStore: AreaStore = {
areas: []
};
/**
*
*/
export const useAreaStore = createStoreHook<AreaStore & Actions>(
set => ({
...initialAreaStore,
loadAreas: (areas: Area[]) =>
set(state => {
state.areas = areas;
})
}),
{ debug: false }
);
/**
* Store中计算选取所有父级行政区划编号的选择器
*/
export const allParentCodesSelector: StateSelector<AreaStore, Area[]> = state =>
uniq(pluck('parent', state.areas));
/**
* Store中计算选取所有的顶级行政区划的选择器
*/
export const topAreasSelector: StateSelector<AreaStore, Area[]> = state => {
const minLevel = reduce(
(acc, elem: number) => min(acc, elem),
Infinity,
pluck<string, Area>('lev', state.areas)
);
return filter(propEq('lev', minLevel), state.areas);
};
/**
*
*/
export const topAreaOptionsSelector: StateSelector<AreaStore, OptionProps[]> = state => {
const parentCodes = allParentCodesSelector(state);
const topAreas = topAreasSelector(state);
return map(
item => ({
label: item.name,
value: item.code,
isLeaf: not(includes(item.code, parentCodes))
}),
topAreas
);
};
/**
* Store中计算选取指定行政区划的子级行政区划的选择器
*/
export const subAreasSelector: (parentCode: string) => StateSelector<AreaStore, Area[]> =
parentCode => state => {
return filter(propEq('parent', parentCode), state.areas);
};
/**
* Store中选出的子级行政区划转换为下拉框使用的选项集的选择器
*/
export const subAreaOptionsSelector: (
parentCode: string
) => StateSelector<AreaStore, OptionProps[]> = parentCode => state => {
const parentCodes = allParentCodesSelector(state);
return map(
(item: Area) => ({
label: item.name,
value: item.code,
isLeaf: not(includes(item.code, parentCodes))
}),
filter(propEq('parent', parentCode), state.areas)
);
};
/**
*
*/
export const topAreaOptionsWithDefaultSelector: (
codes?: string[]
) => StateSelector<AreaStore, OptionProps[]> = codes => state => {
const topAreas = topAreaOptionsSelector(state);
const parentCodes = allParentCodesSelector(state);
const subAreaOptionsWithDefault = (parent: string) =>
map<Area, OptionProps>(item => {
const option: OptionProps = {
label: item.name,
value: item.code,
isLeaf: not(includes(item.code, parentCodes))
};
return includes(item.code, parentCodes)
? { ...option, children: subAreaOptionsWithDefault(item.code) }
: option;
}, filter<Area, Area[]>(propEq('parent', parent), state.areas) ?? []);
return map(item => {
return includes(item.value, codes ?? [])
? { ...item, children: subAreaOptionsWithDefault(item.value) }
: item;
}, topAreas);
};
/**
* Store中计算获取指定的行政区划信息用于级联选择的选择器
*/
export const fullCascaderValueSelector: (
code: string | null
) => StateSelector<AreaStore, string[]> = code => state => {
const result: string[] = isEmpty(code) || isNil(code) ? [] : [code];
let current = find(propEq('code', code), state.areas);
while (notNil(current)) {
const parent = find(propEq('code', current.parent), state.areas);
if (notNil(parent)) {
result.unshift(parent.code);
}
current = parent;
}
return result;
};
/**
* Store中获取指定行政区划是否被禁用的选择器
*/
export const isAreaDisabledSelector: (code: string) => StateSelector<AreaStore, boolean> =
code => state => {
const current = find(propEq('code', code), state.areas);
return current?.disabled ?? false;
};

View File

@ -0,0 +1,67 @@
import { SyncAction, SyncParamAction } from '@/shared/foundation';
import { reduceIndexed } from '@u/funcs';
import { append, dropLast, equals, length } from 'ramda';
import { createStoreHook } from './store_creator';
export interface BreadcrumbRecord {
label: string;
link?: string;
}
/**
*
*/
interface BreadcrumbStore {
/**
*
*/
breadcrumbs: BreadcrumbRecord[];
}
type Actions = {
/**
*
*/
push: SyncParamAction<BreadcrumbRecord>;
/**
*
*/
pop: SyncAction;
/**
*
*/
replace: SyncParamAction<BreadcrumbRecord>;
};
const initialState: BreadcrumbStore = {
breadcrumbs: []
};
export const useBreadcrumbStore = createStoreHook<BreadcrumbStore & Actions>(set => ({
...initialState,
push: record =>
set(st => {
st.breadcrumbs = append(record, st.breadcrumbs);
}),
pop: () =>
set(st => {
st.breadcrumbs = dropLast(1, st.breadcrumbs);
}),
replace: record => set({ breadcrumbs: [record] })
}));
/**
*
*/
export const breadcrumbsSelector: (state: BreadcrumbStore) => BreadcrumbRecord[] = state => {
return reduceIndexed(
(acc, elem, index, list) => {
if (equals(index, length(list) - 1)) {
return append({ label: elem.label }, acc);
}
return append(elem, acc);
},
[],
state.breadcrumbs
);
};

139
src/states/layout_store.ts Normal file
View File

@ -0,0 +1,139 @@
import { SyncAction, SyncParamAction } from '@/shared/foundation';
import { ReactElement, ReactFragment, ReactPortal } from 'react';
import { StateSelector } from 'zustand';
import { createStoreHook } from './store_creator';
export type WorkAreaType = ReactElement | ReactFragment | ReactPortal | HTMLElement;
/**
*
*/
interface LayoutStore {
/**
* refMainLayout中路由接口之外的部分
*/
workAreaRef: WeakRef<WorkAreaType> | null;
/**
*
*/
workAreaHeight: number;
/**
*
*/
panelHeight: number;
/**
*
*/
toolbarHeight: number;
/**
*
*/
maskVisible: boolean;
}
type Actions = {
/**
*
*/
rememberWorkArea: SyncParamAction<WorkAreaType>;
/**
*
*/
forgotWorkArea: SyncAction;
/**
*
*/
memorizeWorkAreaHeight: SyncParamAction<number>;
/**
*
*/
resetWorkAreaHeight: SyncAction;
/**
*
*/
memorizePanelHeight: SyncParamAction<number>;
/**
*
*/
resetPanelHeight: SyncAction;
/**
*
*/
memorizeToolbarHeight: SyncParamAction<number>;
/**
*
*/
resetToolbarHeight: SyncAction;
/**
*
*/
showMask: SyncAction;
/**
*
*/
hideMask: SyncAction;
};
const initialLayoutState: LayoutStore = {
workAreaRef: null,
workAreaHeight: 0,
panelHeight: 0,
toolbarHeight: 0,
maskVisible: false
};
export const useLayoutStore = createStoreHook<LayoutStore & Actions>(
set => ({
...initialLayoutState,
rememberWorkArea: (workAreaRef: WorkAreaType) =>
set(state => {
state.workAreaRef = new WeakRef<WorkAreaType>(workAreaRef);
}),
forgotWorkArea: () =>
set(state => {
state.workAreaRef = null;
}),
memorizeWorkAreaHeight: (height: number) =>
set(state => {
state.workAreaHeight = height;
}),
resetWorkAreaHeight: () =>
set(state => {
state.workAreaHeight = 0;
}),
memorizePanelHeight: (height: number) =>
set(state => {
state.panelHeight = height;
}),
resetPanelHeight: () =>
set(state => {
state.panelHeight = 0;
}),
memorizeToolbarHeight: (height: number) =>
set(state => {
state.toolbarHeight = height;
}),
resetToolbarHeight: () =>
set(state => {
state.toolbarHeight = 0;
}),
showMask: () =>
set(state => {
state.maskVisible = true;
}),
hideMask: () =>
set(state => {
state.maskVisible = false;
})
}),
{ debug: false }
);
/**
*
*/
export const tableHeightSelector: (
additionalGaps: number,
adjustHeight: number
) => StateSelector<LayoutStore, number> = (additionalGaps, adjustHeight) => state =>
state.panelHeight - state.toolbarHeight - 8 * 2 - 8 * additionalGaps - adjustHeight;

43
src/states/park_store.ts Normal file
View File

@ -0,0 +1,43 @@
import { SyncAction, SyncParamAction } from '@/shared/foundation';
import { createStoreHook } from './store_creator';
import {ParkInfo} from "@/shared/model-park";
/**
*
* ! LocalStorage中存储
*/
type Actions = {
/**
*
*/
clear: SyncAction;
/**
*
*/
setPark: SyncParamAction<ParkInfo>;
};
interface InitState {
park?: ParkInfo
}
const initialParkState: InitState = {
park: undefined
};
export const useParkStore = createStoreHook<InitState & Actions>(
set => ({
...initialParkState,
clear: () =>
set(st => {
st.park = null;
}),
setPark: response =>
set(st => {
st.park = response
}),
}),
{ debug: false }
);

View File

@ -0,0 +1,92 @@
import { SyncAction, SyncParamAction } from '@/shared/foundation';
import {LoginResponse, logout} from '@q/session';
import dayjs from 'dayjs';
import { repeat } from 'ramda';
import { createStoreHook } from './store_creator';
// import {Simulate} from "react-dom/test-utils";
// import reset = Simulate.reset;
import useAsyncFn from "react-use/lib/useAsyncFn";
/**
*
* ! LocalStorage中存储
*/
interface SessionStore {
/**
*
* 1051
* true表示已经显示过
*/
expireWarning: [boolean, boolean, boolean];
}
type Actions = {
/**
*
*/
logout: SyncAction;
/**
*
*/
login: SyncParamAction<LoginResponse>;
/**
*
*/
warningDisplayed: SyncParamAction<number>;
};
const initialOpeatorState: SessionStore = {
expireWarning: [false, false, false]
};
export const useSessionStore = createStoreHook<SessionStore & Actions>(
set => ({
...initialOpeatorState,
logout: () =>
set(st => {
localStorage.clear();
st.expireWarning = repeat(false, 3) as [boolean, boolean, boolean];
}),
login: response =>
set(st => {
localStorage.setItem('uid', response.session?.uid ?? '');
localStorage.setItem('token', response.session?.token ?? null);
const expiresAt = dayjs(response.session?.expiresAt ?? '2000-01-01 00:00:00').toISOString();
localStorage.setItem('type', String(response.session?.type ?? -1));
localStorage.setItem('expires', expiresAt);
localStorage.setItem('name', response.session?.name ?? 'unknown');
localStorage.setItem("menu", JSON.stringify(response?.roles || []))
st.expireWarning = repeat(false, 3) as [boolean, boolean, boolean];
}),
warningDisplayed: index =>
set(st => {
st.expireWarning[index] = true;
})
}),
{ debug: false }
);
type LongTimeNoOperate = {
/** 有操作,重置 */
reset: SyncAction;
/** 无操作,退出 */
logout: SyncAction
};
export const useLongTimeNoOperate = createStoreHook<{timeStamp: number} & LongTimeNoOperate>(
set => ({
timeStamp: Date.now(),
reset: () => {
set(st => {
st.timeStamp = Date.now()
})
},
logout: async () => {
localStorage.clear();
set(st => {st.timeStamp = Date.now()})
const [state, doLogout] = useAsyncFn(logout);
await doLogout()
}
}),
{ debug: false }
);

View File

@ -0,0 +1,56 @@
//@ts-nocheck
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { create, State, StateCreator, StoreApi, UseBoundStore } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface EnhancedStoreType<StoreType> {
use: {
[key in keyof StoreType]: () => StoreType[key];
};
reset: () => void;
}
type CreateStoreHookOptions = {
debug?: boolean;
};
/**
* Store Hook创建快速访问其中状态和Action的选择器
*/
function createSelectors<StoreType extends State>(
store: UseBoundStore<StoreApi<StoreType>>,
debug?: boolean
): UseBoundStore<StoreApi<StoreType>> & EnhancedStoreType<StoreType> {
const initialState = store.getState();
(store as unknown).use = {};
Object.keys(store.getState()).forEach(key => {
const selector = (state: StoreType) => state[key as keyof StoreType];
(store as unknown).use[key] = () => store(selector);
});
(store as unknown).reset = () => store.setState(initialState, true);
if (debug ?? false) {
store.subscribe((current, previous) => {
console.log('[状态调试]Action应用前: ', previous);
console.log('[状态调试]Action应用后: ', current);
});
}
return store as UseBoundStore<StoreType> & EnhancedStoreType<StoreType>;
}
/**
* 使Devtools和Immer中间件的Zustand创建Store Hook的函数
* 访Action的选择器
*/
export const createStoreHook = <
T extends State,
Mps extends [StoreMutatorIdentifier, unknown][] = [],
Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
initializer: StateCreator<T, [...Mps, ['zustand/immer', never]], Mcs>,
options?: CreateStoreHookOptions
): UseBoundStore<StoreApi<T>> & EnhancedStoreType<T> =>
createSelectors(create<T>()(immer(initializer)), options?.debug ?? false);

View File

@ -0,0 +1,22 @@
import { Global } from '@emotion/react';
import { Fragment } from 'react';
import tw, { css, GlobalStyles as BaseStyle } from 'twin.macro';
const globalBaseStyle = css`
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
${tw`m-0 p-0 w-screen h-screen overflow-hidden antialiased bg-gray-2`};
}
`;
const GlobalStyles = () => {
return (
<Fragment>
<BaseStyle />
<Global styles={globalBaseStyle} />
</Fragment>
);
};
export default GlobalStyles;

31
src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,31 @@
import { css as cssImport } from '@emotion/react';
import { CSSInterpolation } from '@emotion/serialize';
import styledImport from '@emotion/styled';
import 'twin.macro';
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport;
const css: typeof cssImport;
}
declare module 'react' {
// The css prop
interface HTMLAttributes<T> extends DOMAttributes<T> {
css?: CSSInterpolation;
tw?: string;
}
// The inline svg css prop
interface SVGProps<T> extends SVGProps<SVGSVGElement> {
css?: CSSInterpolation;
tw?: string;
}
}
declare interface ImportMetaEnv {
readonly VITE_MOCK_HOST: string;
}
declare interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -1,24 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES6",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowJs": false,
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"module": "ES6",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"noImplicitAny": false,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"jsxImportSource": "@emotion/react",
"baseUrl": "./",
"paths": {
"@c/*": [
@ -50,6 +52,7 @@
]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": [
"./src"
]
}

View File

@ -1,16 +1,33 @@
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { defaultTo, toUpper } from 'ramda';
import { theme } from 'antd/lib';
// @ts-ignore
import path from 'path';
// https://vitejs.dev/config/
import {
defaultTo,
toUpper
} from 'ramda';
import {
defineConfig,
loadEnv
} from 'vite';
import { convertLegacyToken } from '@ant-design/compatible/lib';
import react from '@vitejs/plugin-react';
const { defaultAlgorithm, defaultSeed } = theme;
const mapToken = defaultAlgorithm(defaultSeed);
const v4Token = convertLegacyToken(mapToken);
type HostMapping = {
Host: string;
Path: string;
};
const EnvHostMapping: { [key: string]: HostMapping } = {
LOCAL: {
Host: 'http://localhost:8000',
// Host: 'http://1.92.72.5/api',
// Host: 'http://1.92.72.5:8080/api',
// Host: 'https://zgd.hbhcbn.com/wxApi',
Path: ''
},
REMOTE: {
@ -19,14 +36,21 @@ const EnvHostMapping: { [key: string]: HostMapping } = {
},
LOCAL_MOCK: {
Host: 'http://127.0.0.1:4523',
Path: '/m1/1411767-0-default'
// Path: '/m1/1411767-0-default'
Path: '/m1/4143821-0-default'
}
};
export default (mode: string) => {
// https://vitejs.dev/config/
export default mode => {
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
const proxyHost: HostMapping = EnvHostMapping[toUpper(defaultTo('LOCAL')(process.env.PROXY_TARGET)).trim()];
return defineConfig({
plugins: [react()],
esbuild: {
define: {
this: 'window'
}
},
resolve: {
alias: {
'@c': path.resolve(__dirname, 'src', 'components'),
@ -41,24 +65,63 @@ export default (mode: string) => {
'assets': path.resolve(__dirname, 'src', 'assets')
}
},
envDir: './',
server: {
port: 8080,
proxy: {
'/api': {
target: `${proxyHost.Host}`,
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/api/, proxyHost.Path)
build: {
minify: 'esbuild',
terserOptions: { compress: true }
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
...v4Token,
'primary-color': '#00B578',
'menu-bg': 'transparent',
'menu-inline-submenu-bg': 'transparent',
'menu-item-color': 'white',
'table-header-bg': '#00B578',
'table-header-color': 'white'
},
'/test': {
target: `http://127.0.0.1:8081`,
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/test/, "")
}
javascriptEnabled: true
}
}
})
}
},
plugins: [
react({
babel: {
plugins: [
'babel-plugin-macros',
[
'@emotion/babel-plugin-jsx-pragmatic',
{
export: 'jsx',
import: '__cssprop',
module: '@emotion/react'
}
],
['@babel/plugin-transform-react-jsx', { pragma: '__cssprop' }, 'twin.macro']
]
}
})
],
envDir: './',
publicDir: "/h5",
base: './',
server: {
port: 3000,
proxy: {
'/api': {
target: `${proxyHost.Host}`,
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/api/, proxyHost.Path)
},
'/test': {
target: `http://127.0.0.1:8081`,
changeOrigin: true,
secure: false,
rewrite: path => path.replace(/^\/test/, "")
}
}
}
});
};