初始化(仅供参考后续会做大更改)

This commit is contained in:
wenhua 2023-08-02 15:34:56 +08:00
commit 74d5b13f98
70 changed files with 34228 additions and 0 deletions

41
.dockerignore Normal file
View File

@ -0,0 +1,41 @@
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Maven template
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
### Example user template template
### Example user template
# IntelliJ project files
.idea
*.iml
out
gen
!build
!default.conf
!nginx.conf

101
.drone.yml Normal file
View File

@ -0,0 +1,101 @@
kind: pipeline
type: docker
name: security-vue
steps:
- name: build-package-react
image: node:16.18.0
volumes:
- name: cache
path: /drone/src/node_modules
- name: build
path: /app/build
commands:
- export CI=false
- rm -rf /app/build/react/*
- cp deployment.yml /app/build/react/
- cp Dockerfile /app/build/react/
- cp .dockerignore /app/build/react/
- cp default.conf /app/build/react/
- cp docker.sh /app/build/react/
- cp nginx.conf /app/build/react/
- npm -v
- mkdir -p ./node_modules
- export NODE_MODULES_PATH=`pwd`/node_modules
# - npm config set registry https://registry.npm.taobao.org
# - set NODE_OPTIONS=--openssl-legacy-provider
- npm install
- npm run build
- ls /app/build/react/
- echo $NODE_MODULES_PATH
- mkdir -p /app/build/react
- cp -r build /app/build/react
- name: build-docker # 制作docker镜像
image: docker # 使用官方docker镜像
volumes: # 将容器内目录挂载到宿主机
- name: build
path: /app/build
- name: docker
path: /var/run/docker.sock # 挂载宿主机的docker
- name: config
path: /config
environment: # 获取到密文的docker用户名和密码
DOCKER_USERNAME:
from_secret: docker_username
DOCKER_PASSWORD:
from_secret: docker_password
REGISTRY:
from_secret: registry
REGISTRY_NAMESPACE:
from_secret: registry_namespace
commands: # 定义在Docker容器中执行的shell命令
- cd /app/build/react/
- cat Dockerfile
- sed -i 's/$REGISTRY/'"$REGISTRY"'/' deployment.yml
- sed -i 's/$REGISTRY_NAMESPACE/'"$REGISTRY_NAMESPACE"'/' deployment.yml
- sed -i 's/$DRONE_REPO_NAME/'"$DRONE_REPO_NAME"'/' deployment.yml
- sed -i 's/$DRONE_COMMIT/'"$DRONE_COMMIT"'/' deployment.yml
# - sed -i 's/$DRONE_COMMIT/'"$DRONE_COMMIT"'/' docker.sh
# - sed -i 's/$DRONE_REPO_NAME/'"$DRONE_REPO_NAME"'/' docker.sh
# docker登录,不能在脚本中登录,并且不能使用docker login -u -p
- echo $DOCKER_PASSWORD | docker login $REGISTRY --username $DOCKER_USERNAME --password-stdin
- chmod +x docker.sh
- cat docker.sh
- sh docker.sh
# 执行完脚本删除本次制作的docker镜像,避免多次后当前runner空间不足
- docker rmi -f $(docker images | grep $DRONE_REPO_NAME | awk '{print $3}')
- name: drone-rancher # rancher运行
image: registry.cn-hangzhou.aliyuncs.com/claywang/kubectl #阿里云的kubectl镜像,里面包含kubectl命令行工具
volumes: # 将容器内目录挂载到宿主机
- name: build
path: /app/build # 将应用打包好的Jar和执行脚本挂载出来
- name: config
path: /app/config # 将kubectl 配置文件挂载出来
commands: # 定义在Docker容器中执行的shell命令
- cd /app/build/react/
# 将deployment中定义的变量替换为drone中的内置变量
- cat deployment.yml
# 通过kubectl指令运行deployment.yml,并指定授权文件kubectl_conf.yml
# - kubectl apply -f deployment.yml -n $DRONE_COMMIT_BRANCH --kubeconfig=/app/config/kubectl_conf.yml
- kubectl apply -f deployment.yml -n dev --kubeconfig=/app/config/kubectl_conf.yml
volumes:
- name: build
host:
path: /home/build
- name: cache
host:
path: /var/lib/npm/cache
- name: config # k8s对接的配置文件
host:
path: /.kube/config
- name: maven-cache # maven的缓存文件
host:
path: /home/data/maven/cache
- name: docker # 宿主机中的docker
host:
path: /var/run/docker.sock

6
.env Normal file
View File

@ -0,0 +1,6 @@
## 储存的用户数据
REACT_APP_USERINFO = userInfo
REACT_APP_LOGINPATH = '/'
REACT_APP_HOMEPATH = '/home'

3
.env.dev Normal file
View File

@ -0,0 +1,3 @@
## 开发环境
REACT_APP_APIPATH = http://gateway.odliken.cn/

3
.env.prod Normal file
View File

@ -0,0 +1,3 @@
## 生产环境
REACT_APP_APIPATH = /api

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM nginx
RUN rm -rf /etc/nginx/conf.d/default.conf
RUN rm -rf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d
COPY nginx.conf /etc/nginx/
#RUN useradd -b /home/clay -m -s /bin/bash clay
#RUN chmod a+xr -R /home/clay && chown clay:clay -R /home/clay
#USER clay
COPY ./build /home/clay
WORKDIR /home/clay
EXPOSE 80

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

19
default.conf Normal file
View File

@ -0,0 +1,19 @@
server {
listen 80;
listen [::]:80;
location /api {
proxy_pass http://gateway.dev.svc.cluster.local:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
#proxy_set_header Host $host;
rewrite "^/api/(.*)$" /$1 break;
}
location / {
root /home/clay;
index index.html index.htm;
}
}

49
deployment.yml Normal file
View File

@ -0,0 +1,49 @@
apiVersion: v1
kind: Service
metadata:
name: $DRONE_REPO_NAME
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 80
selector:
app: $DRONE_REPO_NAME
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: $DRONE_REPO_NAME
spec:
replicas: 1
selector:
matchLabels:
app: $DRONE_REPO_NAME
template:
metadata:
labels:
app: $DRONE_REPO_NAME
spec:
imagePullSecrets:
- name: harbor
containers:
- image: $REGISTRY/$REGISTRY_NAMESPACE/$DRONE_REPO_NAME:$DRONE_COMMIT
name: $DRONE_REPO_NAME
imagePullPolicy: Always
env:
- name: TIME_ZONE
value: Asia/Shanghai
- name: REF_NAME
value: dev
resources:
requests:
memory: 0.1Gi
cpu: 0.1
limits:
memory: 2Gi
cpu: 2
ports:
- containerPort: 8080
name: app-port

15
docker.sh Normal file
View File

@ -0,0 +1,15 @@
#!/bin/sh
# 定义应用组名
group_name='clay'
# 定义应用名称
app_name=$DRONE_REPO_NAME
# 定义应用版本
app_version=$DRONE_COMMIT
echo ${app_version}
# 打包编译docker镜像
echo '----build image start----'
docker build -t ${group_name}/${app_name} .
echo '----build image success----'
docker tag ${group_name}/${app_name} $REGISTRY/$REGISTRY_NAMESPACE/${app_name}:${app_version}
docker push $REGISTRY/$REGISTRY_NAMESPACE/${app_name}:${app_version}
echo 'push success'

47
nginx.conf Normal file
View File

@ -0,0 +1,47 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Methods' '*';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
listen [::]:80;
location /api {
proxy_pass http://gateway.dev.svc.cluster.local:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Host $host;
rewrite "^/api/(.*)$" /$1 break;
}
location / {
root /home/clay;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
}

30533
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

56
package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "wenhua-manager-admin",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.0",
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"antd": "^4.23.6",
"axios": "^1.1.3",
"http-proxy-middleware": "^2.0.6",
"js-cookie": "^3.0.1",
"less": "^4.1.3",
"mobx": "^6.6.2",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.2",
"react-scripts": "5.0.1",
"scss": "^0.2.4",
"typescript": "^4.8.4",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "dotenv -e .env.dev react-scripts start",
"build": "dotenv -e .env.prod react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/js-cookie": "^3.0.2",
"dotenv-cli": "^6.0.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

43
public/index.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
public/manifest.json Normal file
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
src/App.css Normal file
View File

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

9
src/App.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

45
src/App.tsx Normal file
View File

@ -0,0 +1,45 @@
import React, { Suspense, useEffect } from 'react';
import routers, { router } from './router';
import { Route, BrowserRouter, Routes, Navigate } from 'react-router-dom';
import { Spin } from 'antd';
function App() {
const recursiveRouter = (router: any) => {
return router.map((item: any, index: any) => {
if (item && item.children) {
return (
<Route key={index} path={item.path} element={<item.component />}>
{recursiveRouter(item.children)}
</Route>
)
} else {
return (
<Route
key={index} path={item.path}
element={
<Suspense fallback={
<Spin size='large' tip="正在加载中"></Spin>
}>
<item.component />
</Suspense>} />
)
}
})
}
const getRouter = () => {
const arr = recursiveRouter(routers)
}
useEffect(() => {
getRouter()
}, [])
return (
<BrowserRouter>
<Routes>
{
recursiveRouter(routers)
}
</Routes>
</BrowserRouter>
);
}
export default App;

60
src/api/dept/index.ts Normal file
View File

@ -0,0 +1,60 @@
import request from "../../utils/http";
// 请求部门list
export const getDeptList = (params:object|undefined) => {
return request({
url: '/admin/dept',
method: 'get',
params
})
}
// 获取部门详情
export const getDeptDetails = (deptId: number) => {
return request({
url: '/admin/dept/' + deptId,
method: 'get'
})
}
// 获取修改部门列表
export const getEditOptions = (deptId: number) => {
return request({
url: '/admin/dept/option/exclude/' + deptId,
method: 'get'
})
}
// 获取添加部门列表
export const getAddOptions = () => {
return request({
url: '/admin/dept/option',
method: 'get'
})
}
// 新增部门
export const deptAdd = (data:object) => {
return request({
url: '/admin/dept',
method: 'post',
data
})
}
// 修改部门
export const deptEdit = (data: object) => {
return request({
url: '/admin/dept',
method: 'put',
data
})
}
// 删除部门
export const deptDel =(deptId: number) => {
return request({
url: '/admin/dept/' + deptId,
method: 'delete'
})
}

52
src/api/dict/dicData.ts Normal file
View File

@ -0,0 +1,52 @@
import request from "../../utils/http";
// 获取字典数据list
export const getDictList = (params: object | undefined) => {
return request({
url: '/admin/dict/data',
method: 'get',
params
})
}
// 获取字典数据详情
export const getDictDataDetails = (dictCode: number) => {
return request({
url: '/admin/dict/data/'+dictCode,
method: 'get'
})
}
// 获取字典类型options
export const getDictType = () => {
return request({
url: '/admin/dict/type/option',
method: 'get'
})
}
// 新增字典数据
export const dictDataAdd = (data: object) => {
return request({
url: '/admin/dict/data',
method: 'post',
data
})
}
// 修改字典数据
export const dictDataEdit = (data: object) => {
return request({
url: '/admin/dict/data',
method: 'put',
data
})
}
// 删除字典数据
export const dictDataDel = (dictCode:number) => {
return request({
url: '/admin/dict/data/'+dictCode,
method: 'delete'
})
}

44
src/api/dict/index.ts Normal file
View File

@ -0,0 +1,44 @@
import request from "../../utils/http";
//获取字典类型list
export const getDictTypeList = (params: object | undefined) => {
return request({
url: '/admin/dict/type',
method: 'get',
params
})
}
//获取字典类型详情
export const getDictTypeDetails = (dictId: number) => {
return request({
url: '/admin/dict/type/'+dictId,
method: 'get'
})
}
//新增字典类型
export const dictTypeAdd = (data:object) => {
return request({
url: '/admin/dict/type',
method: 'post',
data
})
}
//修改字典类型
export const dictTypeEdit = (data:object) => {
return request({
url: '/admin/dict/type',
method: 'put',
data
})
}
//删除字典类型
export const dictTypeDel = (dictId:number) => {
return request({
url: '/admin/dict/type/'+dictId,
method: 'delete'
})
}

17
src/api/global/index.ts Normal file
View File

@ -0,0 +1,17 @@
import request from "../../utils/http";
// 字典缓存数据(select数据)
export const getCacheKeyOptions = (cacheKey:string) => {
return request({
url: '/admin/dict/data/option/'+ cacheKey,
method: 'get',
})
}
// 字典缓存数据(完整数据)
export const getCacheKeyType = (cacheKey:string) => {
return request({
url: '/admin/dict/data/type/'+ cacheKey,
method: 'get',
})
}

45
src/api/goods/index.ts Normal file
View File

@ -0,0 +1,45 @@
import request from "../../utils/http";
// 请求商品信息表list
export const getDemoGoodsList = (params:object|undefined) => {
return request({
url: '/code-gen-test/code/goods',
method: 'get',
params
})
}
// 获取商品信息表详情
export const getDemoGoodsDetails = (demoGoodsId: number) => {
return request({
url: '/code-gen-test/code/goods/' + demoGoodsId,
method: 'get'
})
}
// 新增商品信息表
export const addDemoGoods = (data:object) => {
return request({
url: '/code-gen-test/code/goods',
method: 'post',
data
})
}
// 修改商品信息表
export const editDemoGoods = (data: object) => {
return request({
url: '/code-gen-test/code/goods',
method: 'put',
data
})
}
// 删除商品信息表
export const delDemoGoods =(demoGoodsId: number) => {
return request({
url: '/code-gen-test/code/goods/' + demoGoodsId,
method: 'delete'
})
}

23
src/api/login/index.ts Normal file
View File

@ -0,0 +1,23 @@
import request from "../../utils/http";
export const getCode = () => {
return request({
url: '/auth/captchaImage',
method: 'get'
})
}
export const login = (data:object) => {
return request({
url: '/auth/login',
method: 'post',
data
})
}
export const getInfo = () => {
return request({
url:'/auth/info',
method: 'get',
})
}

10
src/api/menu/index.ts Normal file
View File

@ -0,0 +1,10 @@
import service from "../../utils/http";
// 获取路由
export const getRouter = (params:any) => {
return service({
url: '/auth/router',
method: 'get',
params
})
}

52
src/api/role/index.ts Normal file
View File

@ -0,0 +1,52 @@
import request from "../../utils/http";
//获取角色list
export const getRoleList = (params: object | undefined) => {
return request({
url: '/admin/role',
method: 'get',
params
})
}
//获取角色详情
export const getRoleDetails = (roleId: number) => {
return request({
url: '/admin/role/' + roleId,
method: 'get',
})
}
//获取角色信息
export const getRoleInfo = () => {
return request({
url: '/admin/role/option',
method: 'get'
})
}
//新增角色
export const roleAdd = (data: object) => {
return request({
url: '/admin/role',
method: 'post',
data
})
}
//修改角色
export const roleEdit = (data: object) => {
return request({
url: '/admin/role',
method: 'put',
data
})
}
//删除角色
export const roleDel = (roleId: number) => {
return request({
url: '/admin/role/' + roleId,
method: 'delete'
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 KiB

BIN
src/assets/img/OIP-A.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
src/assets/img/OIP-B.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/assets/img/OIP-C.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,3 @@
.aside {
height: calc(100% - 64px) !important;
}

View File

@ -0,0 +1,3 @@
.aside {
height: calc(100% - 64px) !important;
}

View File

@ -0,0 +1,30 @@
.ant-layout-header {
padding: 0 15px;
line-height: 64px;
background-color: #5588ee;
}
.main-content {
padding: 20px 15px;
height: calc(100vh - 64px);
overflow: auto;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: #514c4c;
}
.table-headbtn-box {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 15px 0;
}
.table-headbtn-box > .table-headbtn {
margin-right: 8px;
border-radius: 4px;
}

View File

@ -0,0 +1,34 @@
// 顶部样式
.ant-layout-header {
padding: 0 15px;
line-height: 64px;
background-color: #5588ee;
}
// content容器样式
.main-content{
padding: 20px 15px;
height: calc(100vh - 64px);
overflow: auto;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: rgb(81, 76, 76);
}
// 表格上操作按钮样式
.table-headbtn-box {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 15px 0;
>.table-headbtn {
margin-right: 8px;
border-radius: 4px;
}
}

View File

@ -0,0 +1,27 @@
.login {
width: 100vw;
height: 100%;
background: url('../img/536f994fb3.jpg') no-repeat;
background-position: center;
background-size: cover;
position: relative;
}
.login .login-form {
padding: 30px 15px 6px;
border-radius: 6px;
width: 25%;
height: fit-content;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
background-color: rgba(178, 192, 212, 0.8);
}
.login .login-btn {
width: 100%;
}
.login .ant-form-item-label > label {
font-weight: bold;
}

View File

@ -0,0 +1,28 @@
.login {
width: 100vw;
height: 100%;
background: url('../img/536f994fb3.jpg') no-repeat;
background-position: center;
background-size: cover;
position: relative;
.login-form {
padding: 30px 15px 6px;
border-radius: 6px;
width: 25%;
height: fit-content;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
background-color: rgba(178, 192, 212, 0.8);
}
.login-btn {
width: 100%;
// background-color: deepskyblue;
}
.ant-form-item-label > label {
font-weight: bold;
}
}

View File

@ -0,0 +1,30 @@
import { Tag } from "antd";
import React, { useEffect, useState } from "react";
import { getTag } from "../../utils/cache";
/**
* @author
* @param prop {options, state}
* @returns <Tag />
*/
export function DTag(prop:any) {
const [label,setLabel] = useState<string | undefined>('')
const [color,setColor] = useState<string | undefined>('')
const {options,state} = prop
const dealwith = () => {
const res = getTag(options,state)
setLabel(res?.label)
setColor(res?.listClass)
}
useEffect(() => {
setTimeout(()=>{
dealwith()
})
}, [state])
return(
<Tag color={color}>
{label}
</Tag>
)
}

View File

@ -0,0 +1,117 @@
import React, { Fragment, useState } from "react";
import {Button, DatePicker, Form, Input, Select} from 'antd'
import { useForm } from "antd/lib/form/Form";
const {RangePicker} = DatePicker
// 渲染表单项数据类型接口
interface IItemConfig {
name: string,
type: string,
options?: Array<any>,
placeholder?: string,
key: string,
picker?: any,
showTime?: boolean,
showHour?: boolean,
showMinute?: boolean,
showSecond?: boolean,
showNow?: boolean
}
function SearchForm(prop:any) {
const [value,setVal] = useState<object>()
const [form] = useForm()
const config = prop?.config
const handleSumbit = (value: any) => {
setVal(value)
prop.submit(value)
}
const handleReset = () => {
form.resetFields()
}
return(
<Fragment>
<Form
name={config?.name}
form={form}
onFinish={handleSumbit}
layout="inline"
>
{
config?.formItem.map((item:IItemConfig) => {
if(item) {
if(item.type === 'input') {
return(
<Form.Item
key={item.key}
label={item.name}
name={item.key}
>
<Input placeholder={item?.placeholder || '请输入'}></Input>
</Form.Item>
)
}
if(item.type === 'select') {
return(
<Form.Item
key={item.key}
label={item.name}
name={item.key}
>
<Select
options={item?.options}
placeholder={item?.placeholder || '请选择'}
allowClear
/>
</Form.Item>
)
}
if(item.type === 'date') {
return(
<Form.Item
key={item.key}
label={item.name}
name={item.key}
>
<DatePicker
picker={item.picker}
showTime={item?.showTime}
showNow={item?.showNow}
format="YYYY-MM-DD HH:mm:ss"
/>
</Form.Item>
)
}
if(item.type === 'rangedate') {
return(
<Form.Item
key={item.key}
label={item.name}
name={item.key}
>
<RangePicker
showTime={item?.showTime}
showHour={item?.showHour}
showMinute={item?.showMinute}
showSecond={item?.showSecond}
showNow={item?.showNow}
picker={item.picker}
format="YYYY-MM-DD HH:mm:ss"
/>
</Form.Item>
)
}
}
})
}
<Form.Item>
<Button htmlType="submit" type="primary" ></Button>
</Form.Item>
<Form.Item>
<Button onClick={handleReset} htmlType="submit"></Button>
</Form.Item>
</Form>
</Fragment>
)
};
export default SearchForm;

16
src/index.css Normal file
View File

@ -0,0 +1,16 @@
@import '~antd/dist/antd.css';
body,html,.App,#root {
margin: 0;
padding: 0;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

20
src/index.tsx Normal file
View File

@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import './assets/styles/index.css'
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
// <React.StrictMode>
<App />
// </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1,58 @@
import React, { useState } from "react";
import { Menu } from "antd";
import type {MenuProps} from 'antd';
import { useNavigate } from 'react-router-dom'
import '../../assets/styles/Aside.less'
type MenuItem = Required<MenuProps>['items'][number]
interface menuList{
label: any,
key: string,
icon: any,
children?: Array<menuList>
}
function Aside() {
const navigate = useNavigate()
//定义初始菜单数据
const [menuData,setMenuData] = useState([
{
label: '系统管理',
key: '/system',
icon: '',
children: [
{
label: '部门管理',
key: '/home/dept',
icon: '',
// children:[]
},
{
label: '字典管理',
key: '/home/dict',
icon: '',
// children:[]
},
{
label: '角色管理',
key: '/home/role',
icon: '',
// children:[]
},
{
label: 'demogoods',
key: '/home/goods',
icon: '',
// children:[]
},
]
},
]);
const toPage = (v:any) =>{
navigate(v.key)
}
// setMenuData(menuList)
return (
<Menu items={menuData} mode="inline" style={{height: 'calc(100% - 64px)'}} onSelect={toPage}></Menu>
)
}
export default Aside

View File

@ -0,0 +1,37 @@
import React, { useEffect } from "react";
import { Breadcrumb, PageHeader } from 'antd'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import routers, { router } from '../../router';
function PageHead(prop: any) {
const navigate = useNavigate()
const back = () => {
navigate(-1)
}
const itemRender = (route:any, params:any, routes:any, paths:any) => {
const last = routes.indexOf(route) === routes.length - 1;
return last ? (
<span>{route.breadcrumbName}</span>
) : (
<Link to={paths}>{route.breadcrumbName}</Link>
);
}
useEffect(()=>{
})
return(
<PageHeader
className="site-page-header"
title={prop.title}
onBack={back}
style={{height: '100%', lineHeight: '100%'}}
></PageHeader>
// <Breadcrumb
// itemRender={itemRender}
// routes={routers as any}
// separator=">"
// ></Breadcrumb>
// <div></div>
)
}
export default PageHead

39
src/layout/index.tsx Normal file
View File

@ -0,0 +1,39 @@
import { Layout } from "antd";
import Aside from "./components/Aside";
import React, { useEffect } from "react";
import {Outlet} from 'react-router-dom'
import PageHead from "./components/head";
import { getInfo } from "../api/login";
import store from "../store";
const { Header, Sider, Content } = Layout;
function Home(){
const getUserInfo = () => {
getInfo().then((res:any)=>{
store.prototype.setUserInfo(res.data.user)
})
}
useEffect(()=>{
// initCacheOptions()
getUserInfo();
},[])
return (
<Layout style={{height: '100%'}}>
<Sider>
<div style={{height: '64px',textAlign: "center",lineHeight:'64px',color:'#fff',fontSize:'18px', backgroundColor: '#2c5fc4'}}>
SECURITY-REACT
</div>
<Aside></Aside>
</Sider>
<Layout>
<Header>
<PageHead title="初始页"></PageHead>
</Header>
<Content className="main-content">
<Outlet></Outlet>
</Content>
</Layout>
</Layout>
);
}
export default Home

1
src/logo.svg Normal file
View File

@ -0,0 +1 @@
<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

1
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

15
src/reportWebVitals.ts Normal file
View File

@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

51
src/router/index.ts Normal file
View File

@ -0,0 +1,51 @@
import React,{Suspense,lazy} from "react";
import Home from "../layout";
import Login from "../view/login";
export interface router {
path: string,
component: any,
breadcrumbName?: string,
redirect?: string,
children?: Array<router>
}
const routers: Array<router> = [
{
path: '/',
component: Login
},
{
path: '/home',
component: Home,
breadcrumbName: '首页',
redirect: '/home/dept',
children: [
{
path: '/home/dept',
breadcrumbName: '部门管理',
component: lazy(()=>import('../view/dept'))
},
{
path: '/home/dict',
breadcrumbName: '字典管理',
component: lazy(()=>import('../view/dict'))
},
{
path: '/home/dictdata',
breadcrumbName: '字典数据管理',
component: lazy(()=>import('../view/dict/dictData'))
},
{
path: '/home/role',
breadcrumbName: '角色管理',
component: lazy(()=>import('../view/role'))
},
{
path: '/home/goods',
component: lazy(()=>import('../view/demogoods'))
}
]
}
]
export default routers

14
src/setupProxy.ts Normal file
View File

@ -0,0 +1,14 @@
import {createProxyMiddleware} from 'http-proxy-middleware';
// const {createProxyMiddleware} = require('html-minifier-terser')
module.exports= function (app:any){
app.use(createProxyMiddleware('/api',{
// target: 'http://wenhua.com',
target: 'http://gateway.odliken.cn/',
secure: false,
pathRewrite: {
'^/api': '',
},
changeOrigin: true,
}))
}

5
src/setupTests.ts Normal file
View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

103
src/store/index.ts Normal file
View File

@ -0,0 +1,103 @@
import { makeAutoObservable,action,runInAction } from 'mobx'
import { getCacheKeyOptions ,getCacheKeyType} from '../api/global'
import {cacheKey, cacheKeyOptions, IOptions, user} from '../type'
class store {
// 用户信息
public userInfo: user = {
userName: ''
}
// 全局cacheKeyOption
public cacheKeyOptions: cacheKeyOptions = {
data: new Map()
}
// cacheKeytype
public cacheKeyType: cacheKeyOptions = {
data: new Map()
}
constructor() {
makeAutoObservable(this)
}
public getUserInfo(): object{
return this.userInfo
}
public setUserInfo(data:user) {
this.userInfo = data
}
/**
* @author
* @param cacheKey
* @param type
* @returns res
*/
// 获取cache缓存
public async actionCache(cacheKey: string,type:string) {
let getCachekeyOptionsData
if("1" === type){
getCachekeyOptionsData = new Promise((resolve,reject)=>{
getCacheKeyOptions(cacheKey).then((res:any)=>{
resolve(res.data)
})
})
}else{
getCachekeyOptionsData = new Promise((resolve,reject)=>{
getCacheKeyType(cacheKey).then((res:any)=>{
resolve(res.data)
})
})
}
const res = await getCachekeyOptionsData.then((cacheKeyOptions:any)=>{
this.setCacheData(cacheKey,cacheKeyOptions,type)
const data = this.getCacheData(cacheKey,type)
return data
}) as any
return res
}
// 添加缓存
public setCacheData(cacheKey:string ,cacheData:IOptions,type:string) {
if("1"===type){
this.cacheKeyOptions.data.set(cacheKey,cacheData)
}else{
this.cacheKeyType.data.set(cacheKey,cacheData)
}
}
// 外部联通返回缓存数据
public getCacheData(cacheKey:string,type:string):IOptions | undefined {
if("1"===type){
if(undefined === this.cacheKeyOptions){
this.cacheKeyOptions={
data:new Map()
}
}
return this.cacheKeyOptions.data.get(cacheKey)
}else{
if(undefined === this.cacheKeyType){
this.cacheKeyType={
data:new Map()
}
}
return this.cacheKeyType.data.get(cacheKey)
}
}
private initCacheOptionsData(){
this.cacheKeyOptions={
data:new Map()
}
this.cacheKeyType={
data:new Map()
}
}
}
export default store

12
src/type/dept/index.ts Normal file
View File

@ -0,0 +1,12 @@
import React from "react";
import { IBaseDataType } from "..";
// 表格数据类型规范
export interface DataType extends IBaseDataType {
deptId: number,
deptName: string,
email: string,
orderNum: string,
leader: string,
children?: Array<DataType>,
}

18
src/type/dict/index.ts Normal file
View File

@ -0,0 +1,18 @@
import React from "react";
import { IBaseDataType } from "..";
// 表格数据类型规范
export interface DataType extends IBaseDataType {
dictId: number,
dictName: string,
dictType: string,
}
// dictdata表格数据类型规范
export interface DictDataType extends IBaseDataType {
dictCode: number,
dictLabel: string,
dictSort: number,
dictType: string,
dictValue: number,
}

12
src/type/goods/index.ts Normal file
View File

@ -0,0 +1,12 @@
import { IBaseDataType } from "..";
// 表格商品信息表数据类型规范
export interface DataType extends IBaseDataType {
id: number,
name: string,
category: string,
price: number,
sum: number,
brand: string,
// state: number,
}

46
src/type/index.ts Normal file
View File

@ -0,0 +1,46 @@
//接口返回数据类型规范
export interface IResponse<T = any> {
code: number,
msg: string,
data: T
}
//用户信息类型规范
export interface user {
userName: string,
sex?: string,
}
//cacheKey类型规范
export interface cacheKey {
data: object
}
export interface cacheKeyOptions {
data: Map<string,IOptions>
}
export interface IOptions {
value: string,
label: string,
isDefault?: string,
listClass?: string,
cssClass?: string
}
// 控制弹窗title,open 等属性
export interface IModalConfig {
title: string,
open: boolean,
confirmLoading?: boolean
}
// 表格list数据类型基础规范
export interface IBaseDataType {
key?:React.ReactNode | number,
remark: string,
createTime: string,
updateTime: string,
state?: string,
align?: string,
createBy?: string,
updateBy?: string
}

8
src/type/role/index.ts Normal file
View File

@ -0,0 +1,8 @@
import { IBaseDataType } from "..";
export interface DataType extends IBaseDataType{
roleId: number | string,
roleName: string,
roleKey: string,
roleSort: string,
}

44
src/utils/cache.ts Normal file
View File

@ -0,0 +1,44 @@
import store from "../store"
import { IOptions } from "../type"
// 获取缓存
export const getCache = async (cacheKey: string,type:string) => {
const cacheData = store.prototype.getCacheData(cacheKey,type)
if (cacheData === undefined) {
const getData = new Promise(async (reslove, reject) => {
store.prototype.actionCache(cacheKey,type).then((data: any) => {
reslove(data)
}) as any
})
const res = await getData.then((cacheKeyOptions: any) => {
return cacheKeyOptions
}) as Array<IOptions> | undefined
return res
} else {
return cacheData
}
}
// 获取Options缓存
export const getCacheOption = async (cacheKey: string) => {
return getCache(cacheKey,"1")
}
// 获取Type缓存
export const getCacheType = async (cacheKey: string) => {
return getCache(cacheKey,"2")
}
// 处理Tag函数, 具体在DTag.tsx文件中使用
export const getTag = (options: Array<IOptions>,state: string):IOptions | undefined => {
let result = undefined;
for(let i= 0; i<options.length;i++){
if(state == options[i].value){
result = options[i]
break;
}
}
return result
}

59
src/utils/http.ts Normal file
View File

@ -0,0 +1,59 @@
import axios, {AxiosInstance,AxiosRequestConfig,AxiosResponse} from "axios";
import { message } from "antd";
import Cookie from 'js-cookie'
import { IResponse } from "../type";
import confirm from "antd/lib/modal/confirm";
import { AlertTwoTone } from '@ant-design/icons';
const service: AxiosInstance = axios.create({
baseURL: process.env.REACT_APP_APIPATH,
headers: {
'Content-Type': 'application/json',
},
timeout: 10000
})
// 添加请求拦截
service.interceptors.request.use((config:AxiosRequestConfig)=>{
const token: string | null = Cookie.get('authorization') || '';
if(config && config.headers && token) {
config.headers.authorization = token
}
return config
},(error:any)=>{
return Promise.reject(error)
})
const request = async <T=any>(config:AxiosRequestConfig): Promise<IResponse<T>> => {
try {
const response = await service.request<IResponse<T>>(config)
return response.data
} catch(err: any) {
switch(err.response.status) {
case 401 : confirm({
title:'提示',
content: '登录过期,请重新登录',
onOk() {
Cookie.remove('authorization');
window.location.href = 'wenhua.com/';
},
onCancel() {
},
});
break;
case 403 : message.error('禁止访问');
break;
case 404 : message.error('页面或接口地址不存在');
break;
case 405 : message.error('数据传输格式错误');
break;
default : message.error('系统未知错误!');
break;
}
message.error(err.message)
return {
code: -1,
msg: 'error',
data: null as any
}
}
}
export default request;

42
src/utils/tool.ts Normal file
View File

@ -0,0 +1,42 @@
import moment, { Moment } from "moment"
/**
* @author
* @param state
* @param action
* @returns {void: {reducerPagination}}
*/
export const queryReducer = (state: any, action: any) => {
switch(action.type) {
case 'pagination':
return reducerPagination(state, action.data)
case 'search':
return action.void(state,action.data)
}
}
/**
* @author
* @description
* @param state
* @param newState
* @returns {pageSize,pageNum}
*/
export const reducerPagination = (state: any, newState: any) => {
return {
pageSize: state.pageSize = newState.pageSize,
pageNum: state.pageNum = newState.pageNum
}
}
/**
* @author
* @description moment时间格式转换为字符串
* @param dateTime
* @param format
* @returns moment(dateTime).format(format)
*/
export const parseDateTime = (dateTime: Moment, format?: string) => {
format = format || 'YYYY-MM-DD hh:mm:ss';
return moment(dateTime).format(format);
}

View File

@ -0,0 +1,381 @@
import { Button, Col, Form, Input, message, Modal, Radio, Row, Space } from "antd";
import { PlusCircleTwoTone, EditTwoTone, DeleteTwoTone, ExclamationCircleOutlined } from '@ant-design/icons'
import Table, { ColumnsType } from "antd/lib/table";
import { TableRowSelection } from "antd/lib/table/interface";
import React, { Fragment, useEffect, useReducer, useState } from "react";
import { getDemoGoodsList, getDemoGoodsDetails, addDemoGoods, editDemoGoods, delDemoGoods } from "../../api/goods";
import { DTag } from "../../components/DTag";
import { getCacheType } from "../../utils/cache";
import SearchForm from "../../components/SearchForm";
import { IModalConfig, IResponse } from "../../type";
import { DataType } from "../../type/goods";
import confirm from "antd/lib/modal/confirm";
import { parseDateTime, queryReducer, reducerPagination } from "../../utils/tool";
// 初始化搜索条件
const initQueryParams = {
name: '',
category: '',
price: undefined,
sum: undefined,
brand: '',
state: undefined,
pageSize: 10,
pageNum: 1
}
const reducerSearch = (state: any, newState: any) =>{
return {
name: state.name = newState.name,
category: state.category = newState.category,
price: state.price = newState.price,
sum: state.sum = newState.sum,
brand: state.brand = newState.brand,
state: state.state = newState.state,
pageSize: state.pageSize = newState.pageSize,
pageNum: state.pageNum = newState.pageNum,
}
}
function DemoGoods() {
//搜索条件
const [queryParams, setQueryParams] = useReducer( queryReducer ,initQueryParams)
//todo字典 下拉框options 动态判断
const [stateOptionList, setStateOptionList] = useState<any>([])
//单选options
const [stateRadioOption, setStateRadioOption] = useState<any>()
//数据总数
const [total, setTotal] = useState<number>()
//表格list
const [list, setList] = useState<Array<DataType>>()
//表格数据加载中
const [loading, setLoading] = useState<boolean>(false)
//表格多选是否勾选
const [checkStrictly, setCheckStrictly] = useState(false);
//弹窗config
const [modalConfig, setModalConfig] = useState<IModalConfig>();
//formHooks
const [form] = Form.useForm()
//所选数据id
const [id, setId] = useState<number>()
//所选数据ids
const [ids, setIds] = useState<Array<any>>([])
//筛选表单配置
const searchConfig = {
name: 'searchForm',
formItem: [
{
name: '商品名称',
type: 'input',
key: 'name',
},
{
name: '商品类型',
type: 'input',
key: 'category',
},
{
name: '商品价格',
type: 'input',
key: 'price',
},
{
name: '商品数量',
type: 'input',
key: 'sum',
},
{
name: '商品品牌',
type: 'input',
key: 'brand',
},
{
name: '状态',
type: 'select',
key: 'state',
options: stateOptionList
},
]
} as object
// 表格列数据
const colums: ColumnsType<DataType> = [
{
title: '商品名称',
key: 'name',
dataIndex: 'name',
},
{
title: '商品类型',
key: 'category',
dataIndex: 'category',
},
{
title: '商品价格',
key: 'price',
dataIndex: 'price',
},
{
title: '商品数量',
key: 'sum',
dataIndex: 'sum',
},
{
title: '商品品牌',
key: 'brand',
dataIndex: 'brand',
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render: ((_, { state }) => {
return (
<DTag options={stateOptionList} state={state} />
)
})
},
{
title: '操作',
key: 'action',
align: 'center',
render: ((_, record) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}></Button>
<Button type="text" onClick={() => handleDel(record)} danger></Button>
</Space>
))
}
]
//获取商品信息表list
const getList = () => {
setLoading(true)
getDemoGoodsList(queryParams).then((res: IResponse) => {
setList(res.data.rows)
setTotal(res.data.total)
setLoading(false)
})
}
// 点击添加
const handleAdd = () => {
setStateRadioOption(stateOptionList)
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
// 点击修改
const handleEdit = (row:any) => {
const id = row.id|| ids[0];
setId(id);
setStateRadioOption(stateOptionList)
getDemoGoodsDetails(id).then((res: IResponse) => {
form.setFieldsValue({...res.data})
setModalConfig({ title: '修改', open: true, confirmLoading: false })
})
}
// 点击删除
const handleDel = (row:any) => {
const id = row.id || ids[0];
confirm({
title: '确定删除这一项吗?',
icon: <ExclamationCircleOutlined />,
content: `详情主键为: `+ row.id,
onOk() {
delDemoGoods(id).then((res: IResponse) => {
if (res.code === 1000) {
message.success(res.msg)
getList()
} else {
message.error(res.msg)
}
})
},
onCancel() {
},
});
}
// 点击提交
const handleSubmit = () => {
const obj = form.getFieldsValue();
if(modalConfig?.title === '添加') {
addDemoGoods(obj).then((res: IResponse) => {
setModalConfig({ title: '添加', open: true, confirmLoading: true })
if(res.code === 1000) {
message.success('添加成功');
setModalConfig({ title: '添加', open: true, confirmLoading: false })
handleCancel()
getList()
}else {
message.error(res.msg);
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
})
}else {
const objE = {id, ...obj}
editDemoGoods(objE).then((res: IResponse) => {
setModalConfig({ title: '修改', open: true, confirmLoading: true })
if (res.code === 1000) {
message.success('修改成功')
setModalConfig({ title: '修改', open: true, confirmLoading: false })
handleCancel()
getList()
} else {
message.error(res.msg)
setModalConfig({ title: '修改', open: true, confirmLoading: false })
}
})
}
}
// 点击取消
const handleCancel = () => {
form.resetFields();
setModalConfig({ title: '', open: false, confirmLoading: false })
}
// 分页
const pagination = (pageNum: any,pageSize: any) => {
new Promise((resolve,reject) => {
setQueryParams({type: 'pagination', data: {pageNum,pageSize }})
resolve(true)
}).then((res: any) => {
getList()
})
}
// 行数据选择
const rowSelection: TableRowSelection<DataType> = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setIds(Array.from(selectedRowKeys))
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
// 筛选表单提交事件
const submit = (v: any) => {
const {name, category, price, sum, brand, state, } = v
const query = {
name,
category,
price,
sum,
brand,
state,
pageSize: 10,
pageNum: 1
}
new Promise( (resolve, reject) => {
setQueryParams({ type: 'search', data: query, void: reducerSearch})
resolve(true)
}).then((res)=>{
getList()
})
}
//获取筛选表单下拉框缓存数据
const getSelectOptions = async () => {
const stateOptions = await getCacheType('normal_disable').then((options: any) => {
return options
})
setStateOptionList(stateOptions)
}
useEffect(() => {
getSelectOptions();
getList()
}, [])
return (
<Fragment>
<SearchForm config={searchConfig} submit={submit}></SearchForm>
<div className="table-headbtn-box">
<Button icon={<PlusCircleTwoTone />} type="primary" size="middle" className="table-headbtn" onClick={handleAdd}></Button>
<Button icon={<EditTwoTone />} type="primary" ghost className="table-headbtn" onClick={handleEdit} disabled={ids.length == 1 ? false : true} ></Button>
<Button icon={<DeleteTwoTone />} type="primary" danger className="table-headbtn" disabled={ids.length == 1 ? false : true} ></Button>
</div>
<Table
columns={colums}
dataSource={list}
loading={loading}
rowSelection={{ ...rowSelection, checkStrictly }}
pagination={{ total, onChange: pagination }}
rowKey='id'
/>
<Modal
open={modalConfig?.open}
title={modalConfig?.title}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={modalConfig?.confirmLoading}
width={700}
>
<Form
name="form"
form={form}
labelAlign='right'
labelCol={{
span: 6,
offset: 0
}}
>
<Row>
<Col span={12}>
<Form.Item
label="商品名称"
name="name"
required
>
<Input placeholder="请输入商品名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="商品类型"
name="category"
required
>
<Input placeholder="请输入商品类型" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="商品价格"
name="price"
required
>
<Input placeholder="请输入商品价格" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="商品数量"
name="sum"
required
>
<Input placeholder="请输入商品数量" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="商品品牌"
name="brand"
required
>
<Input placeholder="请输入商品品牌" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="状态"
name="state"
required
>
<Radio.Group options={stateRadioOption} />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</Fragment>
)
}
export default DemoGoods;

370
src/view/dept/index.tsx Normal file
View File

@ -0,0 +1,370 @@
import { Button, Col, Form, Input, message, Modal, Radio, Row, Space, Table, TreeSelect } from "antd";
import { PlusCircleTwoTone, EditTwoTone, DeleteTwoTone, ExclamationCircleOutlined } from '@ant-design/icons'
import { ColumnsType } from "antd/lib/table";
import { TableRowSelection } from "antd/lib/table/interface";
import React, { Fragment, useEffect, useReducer, useRef, useState } from "react";
import { getDeptList, getDeptDetails, getEditOptions, getAddOptions, deptAdd, deptEdit, deptDel } from "../../api/dept";
import { DTag } from "../../components/DTag";
import SearchForm from "../../components/SearchForm";
import { IModalConfig, IResponse } from "../../type";
import { DataType } from "../../type/dept";
import { getCacheType } from "../../utils/cache";
import confirm from "antd/lib/modal/confirm";
import { queryReducer } from "../../utils/tool";
const initQueryParams = {
deptName: undefined,
state: undefined
}
const reducerSearch = (state:any, newState: any) => {
return {
deptName: state.deptName = newState.deptName,
state: state.state = newState.state
}
}
function Department() {
//下拉框options
const [statusList, setStatusList] = useState<any>([])
//搜索条件
const [queryParams, setQueryParams] = useReducer(queryReducer,initQueryParams)
//表格list
const [list, setList] = useState<Array<DataType>>()
//表格数据加载中
const [loading, setLoading] = useState<boolean>(false)
//表格多选是否勾选
const [checkStrictly, setCheckStrictly] = useState(false);
//弹窗config
const [modalConfig, setModalConfig] = useState<IModalConfig>();
//formHooks
const [form] = Form.useForm()
//自定义树形字段
const [fieldNames, setFieldNames] = useState<object>()
//单选options
const [radioOption, setRadioOption] = useState<any>()
//添加修改弹窗部门树
const [treeList, setTreeList] = useState<Array<object>>()
//所选部门id单个
const [deptId, setDeptId] = useState<number>()
//所选部门ids 多个
const [deptIds, setDeptIds] = useState<Array<any>>([])
// 表格列数据
const colums: ColumnsType<DataType> = [
{
title: '部门名称',
key: 'deptName',
dataIndex: 'deptName'
},
{
title: '显示排序',
key: 'deptId',
dataIndex: 'orderNum'
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render: ((_, { state }) => {
return (
<DTag options={statusList} state={state} />
)
})
},
{
title: '负责人',
key: 'leader',
dataIndex: 'leader'
},
{
title: '邮箱',
key: 'email',
dataIndex: 'email'
},
{
title: '创建时间',
key: 'createTime',
dataIndex: 'createTime'
},
{
title: '操作',
key: 'action',
align: 'center',
render: ((_, record) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}></Button>
<Button type="text" onClick={() => handleDel(record)} danger></Button>
</Space>
))
}
]
// 获取部门列表
const getList = () => {
setLoading(true)
getDeptList(queryParams).then((res: IResponse) => {
setList(res.data)
setLoading(false)
})
}
// 获取添加部门树
const getDeptTree = () => {
getAddOptions().then((res: IResponse) => {
if (res.code === 1000) {
setTreeList(res.data)
}
})
}
// 获取修改部门树
const getDeptTreeEdit = (deptId: number) => {
getEditOptions(deptId).then((res: IResponse) => {
if (res.code === 1000) {
if (res.data.length === 0) {
const option = [{
value: "0",
label: "顶级节点"
}]
setTreeList(option)
} else {
setTreeList(res.data)
}
}
})
}
// 点击修改
const handleEdit = (row: any) => {
const deptId = row.deptId || deptIds[0];
setRadioOption(statusList)
setDeptId(deptId)
getDeptTreeEdit(deptId)
getDeptDetails(deptId).then((res: IResponse) => {
form.setFieldsValue({ ...res.data })
setModalConfig({ title: '修改', open: true, confirmLoading: false })
})
}
// 点击添加
const handleAdd = () => {
getDeptTree()
setRadioOption(statusList)
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
// 点击提交
const handleSubmit = () => {
const obj = form.getFieldsValue()
if (modalConfig?.title === '添加') {
deptAdd(obj).then((res: IResponse) => {
setModalConfig({ title: '添加', open: true, confirmLoading: true })
if (res.code === 1000) {
message.success('添加成功');
setModalConfig({ title: '添加', open: true, confirmLoading: false })
handleCancel()
getList()
} else {
message.error(res.msg);
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
})
} else {
const objE = { deptId, ...obj }
deptEdit(objE).then((res: IResponse) => {
setModalConfig({ title: '修改', open: true, confirmLoading: true })
if (res.code === 1000) {
message.success('修改成功')
setModalConfig({ title: '修改', open: true, confirmLoading: false })
handleCancel()
getList()
} else {
message.error(res.msg)
setModalConfig({ title: '修改', open: true, confirmLoading: false })
}
})
}
}
// 点击删除
const handleDel = (row: any) => {
const deptId = row.deptId || deptIds[0];
confirm({
title: '确定删除这一项吗?',
icon: <ExclamationCircleOutlined />,
content: `详情: ${row.deptName}`,
onOk() {
deptDel(deptId).then((res: IResponse) => {
if (res.code === 1000) {
message.success(res.msg)
getList()
} else {
message.error(res.msg)
}
})
},
onCancel() {
},
});
}
// 点击取消
const handleCancel = () => {
form.resetFields();
setModalConfig({ title: '', open: false, confirmLoading: false })
}
// 点击展开
const handleExpand = () => {
}
// 行数据选择
const rowSelection: TableRowSelection<DataType> = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setDeptIds(Array.from(selectedRowKeys))
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
const searchConfig = {
name: 'searchForm',
formItem: [
{
name: '部门名称',
type: 'input',
key: 'deptName'
},
{
name: '部门状态',
type: 'select',
key: 'state',
options: statusList
}
]
} as object
const submit = (v: object) => {
new Promise( (resolve, reject) => {
setQueryParams({ type: 'search', data: v, void: reducerSearch })
resolve(true)
}).then((res: any) => {
getList()
})
}
const getSelectOptions = async () => {
const options = await getCacheType('normal_disable').then((options: any) => {
return options
})
setStatusList(options)
}
useEffect(() => {
getSelectOptions()
getList()
}, [])
return (
<Fragment>
<SearchForm config={searchConfig} submit={submit}></SearchForm>
<div className="table-headbtn-box">
<Button icon={<PlusCircleTwoTone />} type="primary" size="middle" className="table-headbtn" onClick={handleAdd}></Button>
<Button icon={<EditTwoTone />} type="primary" ghost className="table-headbtn" disabled={deptIds.length == 1 ? false : true} onClick={handleEdit}></Button>
<Button icon={<DeleteTwoTone />} type="primary" danger className="table-headbtn" disabled={deptIds.length == 1 ? false : true}></Button>
</div>
<Table
columns={colums}
rowSelection={{ ...rowSelection, checkStrictly }}
dataSource={list}
loading={loading}
expandable={{ defaultExpandAllRows: true, showExpandColumn: true }}
expandRowByClick
rowKey="deptId"
/>
<Modal
title={modalConfig?.title}
open={modalConfig?.open}
confirmLoading={modalConfig?.confirmLoading}
onOk={handleSubmit}
onCancel={handleCancel}
width={700}
>
<Form
name="form"
form={form}
labelAlign='right'
labelCol={{
span: 6,
offset: 0
}}
>
<Row>
<Col span={12}>
<Form.Item
label="部门名称"
name='deptName'
>
<Input placeholder="请输入部门名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="邮箱"
name='email'
>
<Input placeholder="请输入邮箱" />
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="所属上级"
name='parentId'
>
<TreeSelect
fieldNames={fieldNames}
placeholder="请选择上级"
treeData={treeList}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="显示排序"
name='orderNum'
>
<Input placeholder="请输入排序" type="number" />
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="负责人"
name='leader'
>
<Input placeholder="请输入负责人" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="电话"
name='phone'
>
<Input placeholder="请输入电话" />
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="状态"
name='state'
>
<Radio.Group
options={radioOption}
/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</Fragment>
)
};
export default Department;

416
src/view/dict/dictData.tsx Normal file
View File

@ -0,0 +1,416 @@
import { Button, Col, Form, Input, message, Modal, Radio, Row, Select, Space } from "antd";
import { PlusCircleTwoTone, EditTwoTone, DeleteTwoTone, ExclamationCircleOutlined, CloseCircleTwoTone } from '@ant-design/icons'
import React, { Fragment, useEffect, useReducer, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import SearchForm from "../../components/SearchForm";
import { IModalConfig, IResponse } from "../../type";
import { getCacheType } from "../../utils/cache";
import { queryReducer } from "../../utils/tool";
import { DictDataType } from "../../type/dict";
import Table, { ColumnsType } from "antd/lib/table";
import { DTag } from "../../components/DTag";
import { TableRowSelection } from "antd/lib/table/interface";
import { dictDataAdd, dictDataDel, dictDataEdit, getDictDataDetails, getDictList, getDictType } from "../../api/dict/dicData";
import confirm from "antd/lib/modal/confirm";
// 初始化搜索条件
const initQueryParams = {
dictType: '' as string,
dictLabel: '' as string,
state: undefined as string | undefined | number,
pageSize: 10,
pageNum: 1
}
const reducerSearch = (state: any, newState: any) =>{
return {
pageSize: state.pageSize = newState.pageSize,
pageNum: state.pageNum = newState.pageNum,
state: state.state = newState.state,
dictType: state.dictType = newState.dictType,
dictLabel: state.dictLabel = newState.dictLabel,
}
}
function DictData() {
const location = useLocation();
// 搜索条件
const [queryParams,setQueryParams] = useReducer(queryReducer, initQueryParams);
// 下拉框options
const [statusList, setStatusList] = useState<any>([]);
// 字典类型options
const [dictTypeOption, setDictTypeOptions] = useState<any>([])
// 回显样式Options
const [cssOptions,setCssOptions] = useState<any>([])
//数据总数
const [total, setTotal] = useState<number>()
//表格list
const [list, setList] = useState<Array<DictDataType>>() // 类型定义 需要
//表格数据加载中
const [loading, setLoading] = useState<boolean>(false)
//表格多选是否勾选
const [checkStrictly, setCheckStrictly] = useState(false);
//弹窗config
const [modalConfig, setModalConfig] = useState<IModalConfig>();
//单选options
const [radioOption, setRadioOption] = useState<any>()
//formHooks
const [form] = Form.useForm()
//所选数据id
const [dictCode, setDictCode] = useState<number>()
//所选数据ids
const [dictCodes, setDictCodes] = useState<Array<any>>([])
const navigate = useNavigate()
//筛选表单配置
const searchConfig = {
name: 'searchForm',
formItem: [
{
name: '字典名称',
type: 'select',
key: 'dictType',
options: dictTypeOption
},
{
name: '字典标签',
type: 'input',
key: 'dictLabel'
},
{
name: '状态',
type: 'select',
key: 'state',
options: statusList
}
]
} as object
const colums: ColumnsType<DictDataType> = [
{
title: '字典编码',
key: 'dictCode',
dataIndex: 'dictCode'
},
{
title: '字典标签',
key: 'dictLabel',
dataIndex: 'dictLabel'
},
{
title: '字典键值',
key: 'dictValue',
dataIndex: 'dictValue'
},
{
title: '字典排序',
key: 'dictSort',
dataIndex: 'dictSort'
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render: ((_, { state }) => {
return (
<DTag options={statusList} state={state} />
)
})
},
{
title: '备注',
key: 'remark',
dataIndex: 'remark'
},
{
title: '创建时间',
key: 'createTime',
dataIndex: 'createTime'
},
{
title: '操作',
key: 'action',
align: 'center',
render: ((_, record) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}></Button>
<Button type="text" onClick={() => handleDel(record)} danger></Button>
</Space>
))
}
]
const getLocationState = () => {
new Promise((resolve,reject)=>{
const data = {
pageSize: 10,
pageNum: 1,
state: undefined,
dictType: location.state,
dictLabel: undefined
}
setQueryParams({type: 'search', data,void: reducerSearch })
resolve(true)
}).then((res:any)=>{
setTimeout(()=>{ getList() },200)
})
}
//获取字典数据
const getList = () => {
setLoading(true)
getDictList(queryParams).then((res:IResponse)=>{
if(res.code === 1000) {
setList(res.data.rows)
setTotal(res.data.total)
setLoading(false)
}
})
}
// 点击添加
const handleAdd = () =>{
form.setFieldValue('dictType',location.state)
setRadioOption(statusList)
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
// 点击修改
const handleEdit = (row: any) => {
const dictCode = row.dictCode || dictCodes[0]
setDictCode(dictCode);
setRadioOption(statusList)
getDictDataDetails(dictCode).then((res:IResponse)=>{
form.setFieldsValue({...res.data})
setModalConfig({ title: '修改', open: true, confirmLoading: false })
})
}
// 点击删除
const handleDel = (row: any) => {
const dictCode = row.dictCode || dictCodes[0]
confirm({
title: '确定删除这一项吗?',
icon: <ExclamationCircleOutlined />,
content: `详情: ${row.dictLabel}`,
onOk() {
dictDataDel(dictCode).then((res:IResponse)=>{
if (res.code === 1000) {
message.success(res.msg)
getList()
} else {
message.error(res.msg)
}
})
},
onCancel() {
},
})
}
// 点击提交
const handleSubmit = () => {
const obj = form.getFieldsValue();
if(modalConfig?.title === '添加') {
dictDataAdd(obj).then((res:IResponse)=>{
setModalConfig({ title: '添加', open: true, confirmLoading: true })
if(res.code === 1000) {
message.success('添加成功');
setModalConfig({ title: '添加', open: true, confirmLoading: false })
handleCancel()
getList()
}else {
message.error(res.msg);
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
})
}else {
const objE = {dictCode, ...obj}
dictDataEdit(objE).then((res:IResponse)=>{
setModalConfig({ title: '修改', open: true, confirmLoading: true })
if (res.code === 1000) {
message.success('修改成功')
setModalConfig({ title: '修改', open: true, confirmLoading: false })
handleCancel()
getList()
} else {
message.error(res.msg)
setModalConfig({ title: '修改', open: true, confirmLoading: false })
}
})
}
}
// 点击取消
const handleCancel = () => {
form.resetFields();
setModalConfig({ title: '', open: false, confirmLoading: false })
}
// 分页
const pagination = (pageNum: any,pageSize: any) => {
new Promise((resolve,reject) => {
setQueryParams({type: 'pagination', data: {pageNum,pageSize }})
resolve(true)
}).then((res: any) => {
getList()
})
}
// 筛选表单提交事件
const submit = (v:any) => {
const {dictType, state, dictLabel} = v
const query = {
dictType,
state,
dictLabel,
pageSize: 10,
pageNum: 1
}
new Promise( (resolve, reject) => {
setQueryParams({ type: 'search', data: query, void: reducerSearch})
resolve(true)
}).then((res)=>{
getList()
})
}
// 行数据选择
const rowSelection: TableRowSelection<DictDataType> = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setDictCodes(Array.from(selectedRowKeys))
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
//获取筛选表单下拉框缓存数据
const getSelectOptions = async () => {
const options = await getCacheType('normal_disable').then((options: any) => {
return options
})
setStatusList(options)
const cssOptions = await getCacheType('list_class').then((options: any)=>{
return options
})
setCssOptions(cssOptions)
getDictType().then((res:IResponse)=>{
if(res.code === 1000) {
setDictTypeOptions(res.data)
}
})
}
const handleclose = () => {
navigate(-1)
}
useEffect(()=>{
getSelectOptions()
getLocationState()
},[])
return(
<Fragment>
<SearchForm config={searchConfig} submit={submit}></SearchForm>
<div className="table-headbtn-box">
<Button icon={<PlusCircleTwoTone />} type="primary" size="middle" className="table-headbtn" onClick={handleAdd}></Button>
<Button icon={<EditTwoTone />} type="primary" ghost className="table-headbtn" onClick={handleEdit} disabled={dictCodes.length == 1 ? false : true}></Button>
<Button icon={<DeleteTwoTone />} type="primary" danger className="table-headbtn" disabled={dictCodes.length == 1 ? false : true} ></Button>
<Button icon={<CloseCircleTwoTone />} type="primary" danger ghost onClick={handleclose} className="table-headbtn"></Button>
</div>
<Table
columns={colums}
dataSource={list}
loading={loading}
rowSelection={{ ...rowSelection, checkStrictly }}
pagination={{ total, onChange: pagination }}
rowKey='dictCode'
/>
<Modal
open={modalConfig?.open}
title={modalConfig?.title}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={modalConfig?.confirmLoading}
width={700}
>
<Form
name="form"
form={form}
labelAlign='right'
labelCol={{
span: 6,
offset: 0
}}
>
<Row>
<Col span={12}>
<Form.Item
label="字典类型"
name="dictType"
>
<Input disabled/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="数据标签"
name="dictLabel"
>
<Input placeholder="请输入"/>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="数据键值"
name="dictValue"
>
<Input placeholder="请输入"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="样式属性"
name="listClass"
>
<Input placeholder="请输入"/>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="显示排序"
name="dictSort"
>
<Input placeholder="请输入" type="number"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="回显样式"
name="cssClass"
>
<Select options={cssOptions}/>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="状态"
name="state"
>
<Radio.Group
options={radioOption}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="备注"
name="remark"
>
<Input placeholder="请输入"/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</Fragment>
)
}
export default DictData

362
src/view/dict/index.tsx Normal file
View File

@ -0,0 +1,362 @@
import { Button, Col, Form, Input, message, Modal, Radio, Row, Space } from "antd";
import { PlusCircleTwoTone, EditTwoTone, DeleteTwoTone, ExclamationCircleOutlined } from '@ant-design/icons'
import Table, { ColumnsType } from "antd/lib/table";
import { TableRowSelection } from "antd/lib/table/interface";
import React, { Fragment, useEffect, useReducer, useState } from "react";
import { dictTypeAdd, dictTypeDel, dictTypeEdit, getDictTypeDetails, getDictTypeList } from "../../api/dict";
import { DTag } from "../../components/DTag";
import SearchForm from "../../components/SearchForm";
import { IModalConfig, IResponse } from "../../type";
import { DataType } from "../../type/dict";
import { getCacheType } from "../../utils/cache";
import confirm from "antd/lib/modal/confirm";
import { parseDateTime, queryReducer, reducerPagination } from "../../utils/tool";
import { useNavigate } from "react-router-dom";
//字典管理
// 初始化搜索条件
const initQueryParams = {
pageSize: 10 as number,
pageNum: 1 as number,
dictName: undefined as string | undefined,
dictType: undefined as string | undefined,
state: undefined as string | undefined | number,
startTime: undefined as string | undefined |null,
endTime: undefined as string | undefined |null
}
// 搜索reducer方法
const reducerSearch = (state: any, newState: any) => {
return {
pageSize: state.pageSize = newState.pageSize,
pageNum: state.pageNum = newState.pageNum,
dictName: state.dictName = newState.dictName,
dictType: state.dictType = newState.dictType,
state: state.state = newState.state,
startTime: state.startTime = newState.startTime,
endTime: state.endTime = newState.endTime,
}
}
function Dict() {
const navigate = useNavigate()
//搜索条件
const [queryParams, setQueryParams] = useReducer( queryReducer ,initQueryParams)
//下拉框options
const [statusList, setStatusList] = useState<any>([])
//数据总数
const [total, setTotal] = useState<number>()
//表格list
const [list, setList] = useState<Array<DataType>>()
//表格数据加载中
const [loading, setLoading] = useState<boolean>(false)
//表格多选是否勾选
const [checkStrictly, setCheckStrictly] = useState(false);
//弹窗config
const [modalConfig, setModalConfig] = useState<IModalConfig>();
//单选options
const [radioOption, setRadioOption] = useState<any>()
//formHooks
const [form] = Form.useForm()
//所选数据id
const [dictId, setDictId] = useState<number>()
//所选数据ids
const [dictIds, setDictIds] = useState<Array<any>>([])
//筛选表单配置
const searchConfig = {
name: 'searchForm',
formItem: [
{
name: '字典名称',
type: 'input',
key: 'dictName'
},
{
name: '字典类型',
type: 'input',
key: 'dictType'
},
{
name: '日期',
type: 'rangedate',
key: 'dateRange'
},
{
name: '状态',
type: 'select',
key: 'state',
options: statusList
}
]
} as object
// 表格列数据
const colums: ColumnsType<DataType> = [
{
title: '字典名称',
key: 'dictName',
dataIndex: 'dictName'
},
{
title: '字典类型',
key: 'dictType',
dataIndex: 'dictType',
render: ((_,{dictType})=>{
return(
<Button type="link" onClick={()=>handleOpenDictDataPage(dictType)}>{dictType}</Button>
)
})
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render: ((_, { state }) => {
return (
<DTag options={statusList} state={state} />
)
})
},
{
title: '生成时间',
key: 'createTime',
dataIndex: 'createTime'
},
{
title: '更新时间',
key: 'updateTime',
dataIndex: 'updateTime'
},
{
title: '描述',
key: 'remark',
dataIndex: 'remark'
},
{
title: '操作',
key: 'action',
align: 'center',
render: ((_, record) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}></Button>
<Button type="text" onClick={() => handleDel(record)} danger></Button>
</Space>
))
}
]
//获取字典类型list
const getList = () => {
setLoading(true)
getDictTypeList(queryParams).then((res: IResponse) => {
console.log(res, 'res');
setList(res.data.rows)
setTotal(res.data.total)
setLoading(false)
})
}
// 点击添加
const handleAdd = () => {
setRadioOption(statusList)
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
// 点击修改
const handleEdit = (row:any) => {
const dictId = row.dictId || dictIds[0];
setDictId(dictId);
setRadioOption(statusList)
getDictTypeDetails(dictId).then((res: IResponse) => {
form.setFieldsValue({...res.data})
setModalConfig({ title: '修改', open: true, confirmLoading: false })
})
}
// 点击删除
const handleDel = (row:any) => {
const dictId = row.dictId || dictIds[0];
confirm({
title: '确定删除这一项吗?',
icon: <ExclamationCircleOutlined />,
content: `详情: ${row.deptName}`,
onOk() {
dictTypeDel(dictId).then((res: IResponse) => {
if (res.code === 1000) {
message.success(res.msg)
getList()
} else {
message.error(res.msg)
}
})
},
onCancel() {
},
});
}
// 点击提交
const handleSubmit = () => {
const obj = form.getFieldsValue();
if(modalConfig?.title === '添加') {
dictTypeAdd(obj).then((res: IResponse) => {
setModalConfig({ title: '添加', open: true, confirmLoading: true })
if(res.code === 1000) {
message.success('添加成功');
setModalConfig({ title: '添加', open: true, confirmLoading: false })
handleCancel()
getList()
}else {
message.error(res.msg);
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
})
}else {
const objE = {dictId, ...obj}
dictTypeEdit(objE).then((res: IResponse) => {
setModalConfig({ title: '修改', open: true, confirmLoading: true })
if (res.code === 1000) {
message.success('修改成功')
setModalConfig({ title: '修改', open: true, confirmLoading: false })
handleCancel()
getList()
} else {
message.error(res.msg)
setModalConfig({ title: '修改', open: true, confirmLoading: false })
}
})
}
}
// 点击取消
const handleCancel = () => {
form.resetFields();
setModalConfig({ title: '', open: false, confirmLoading: false })
}
// 点击打开字典数据页面
const handleOpenDictDataPage = (dictType:string) => {
navigate('/home/dictdata',{state:dictType})
}
// 分页
const pagination = (pageNum: any,pageSize: any) => {
new Promise((resolve,reject) => {
setQueryParams({type: 'pagination', data: {pageNum,pageSize }})
resolve(true)
}).then((res: any) => {
getList()
})
}
// 行数据选择
const rowSelection: TableRowSelection<DataType> = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setDictIds(Array.from(selectedRowKeys))
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
// 筛选表单提交事件
const submit = (v: any) => {
console.log(v,v);
const {dictName, dictType, state, dateRange} = v
const query = {
dictName,
dictType,
state,
startTime: dateRange !== null && dateRange!==undefined ? parseDateTime(dateRange[0]._d) : undefined,
endTime: dateRange !== null && dateRange!==undefined ? parseDateTime(dateRange[1]._d) : undefined,
pageSize: 10,
pageNum: 1
}
new Promise( (resolve, reject) => {
setQueryParams({ type: 'search', data: query, void: reducerSearch})
resolve(true)
}).then((res)=>{
getList()
})
}
//获取筛选表单下拉框缓存数据
const getSelectOptions = async () => {
const options = await getCacheType('normal_disable').then((options: any) => {
return options
})
setStatusList(options)
}
useEffect(() => {
getSelectOptions();
getList()
}, [])
return (
<Fragment>
<SearchForm config={searchConfig} submit={submit}></SearchForm>
<div className="table-headbtn-box">
<Button icon={<PlusCircleTwoTone />} type="primary" size="middle" className="table-headbtn" onClick={handleAdd}></Button>
<Button icon={<EditTwoTone />} type="primary" ghost className="table-headbtn" onClick={handleEdit} disabled={dictIds.length == 1 ? false : true} ></Button>
<Button icon={<DeleteTwoTone />} type="primary" danger className="table-headbtn" disabled={dictIds.length == 1 ? false : true} ></Button>
</div>
<Table
columns={colums}
dataSource={list}
loading={loading}
rowSelection={{ ...rowSelection, checkStrictly }}
pagination={{ total, onChange: pagination }}
rowKey='dictId'
/>
<Modal
open={modalConfig?.open}
title={modalConfig?.title}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={modalConfig?.confirmLoading}
width={700}
>
<Form
name="form"
form={form}
labelAlign='right'
labelCol={{
span: 6,
offset: 0
}}
>
<Row>
<Col span={12}>
<Form.Item
label="字典名称"
name="dictName"
required
>
<Input placeholder="请输入字典名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="字典类型"
name="dictType"
>
<Input placeholder="请输入字典类型" />
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="描述"
name="remark"
>
<Input placeholder="请输入" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="状态"
name="state"
>
<Radio.Group
options={radioOption}
/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</Fragment>
)
}
export default Dict

87
src/view/login/index.tsx Normal file
View File

@ -0,0 +1,87 @@
import React, { useEffect, useState } from "react";
import '../../assets/styles/login.css'
import { getCode, login } from '../../api/login'
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Form, Input, Image, message } from 'antd';
import { useNavigate } from "react-router-dom";
import Cookies from "js-cookie";
function Login () {
const navigate = useNavigate()
const [codeImgSrc, setCodeImgSrc] = useState<string>('')
const [uuid,setUuid] = useState<string>('')
const [loading,setLoading] = useState<boolean>(false)
// 获取验证码
const getCodeimg = () =>{
getCode().then(response =>{
setCodeImgSrc(response.data.img)
setUuid(response.data.uuid)
})
}
// 登录
const handleLogin = (value:any) => {
const data:object = {uuid,...value}
setLoading(true)
login(data).then((res)=>{
if(res?.code === 1000){
Cookies.set('authorization',res.data)
message.success('登录成功')
setLoading(false)
navigate('/home')
return
}
message.error('登录失败');
setLoading(false)
})
}
useEffect(()=>{
getCodeimg()
},[])
return(
<div className="login">
<Form
name="loginForm"
labelAlign="right"
labelCol={{
span:4,
offset:0
}}
onFinish={handleLogin}
className="login-form"
>
<Form.Item>
<h2 style={{width: '100%', textAlign: "center", marginBottom: 0, fontWeight: "bold"}}>SECURITY-REACT</h2>
</Form.Item>
<Form.Item
label="用户名"
name="username"
rules={[{required: true, message: '请输入用户名'}]}
>
<Input prefix={<UserOutlined></UserOutlined>} placeholder="请输入用户名"></Input>
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{required: true, message: '请输入密码'}]}
>
<Input prefix={<LockOutlined></LockOutlined>} type="password" placeholder="请输入密码"></Input>
</Form.Item>
<Form.Item
label="验证码"
name="code"
rules={[{required: true, message: '请输入验证码'}]}
>
<div style={{display: "flex", justifyContent: "space-between", alignItems: "center"}}>
<Input placeholder="请输入验证码" width="50%" style={{marginRight: '2%'}}></Input>
<Image src={'data:image/gif;base64,'+codeImgSrc} height="32px" width="48%" onClick={getCodeimg} preview={false}></Image>
</div>
</Form.Item>
<Form.Item>
<Button htmlType="submit" className="login-btn" type="primary" loading={loading}></Button>
</Form.Item>
</Form>
</div>
)
}
export default Login

288
src/view/role/index.tsx Normal file
View File

@ -0,0 +1,288 @@
import { Button, Col, Form, Input, message, Modal, Radio, Row, Space } from "antd";
import { PlusCircleTwoTone, EditTwoTone, DeleteTwoTone, ExclamationCircleOutlined } from '@ant-design/icons'
import Table, { ColumnsType } from "antd/lib/table";
import { TableRowSelection } from "antd/lib/table/interface";
import React, { Fragment, useEffect, useReducer, useState } from "react";
import SearchForm from "../../components/SearchForm";
import { IModalConfig, IResponse } from "../../type";
import { getCacheType } from "../../utils/cache";
import { parseDateTime, queryReducer } from "../../utils/tool";
import { DataType } from "../../type/role";
import { getRoleList } from "../../api/role";
import { DTag } from "../../components/DTag";
// 初始化搜索条件
const initQueryParams = {
pageSize: 10,
pageNum: 1,
roleName: undefined,
roleKey: undefined,
state: undefined,
startTime: undefined,
endTime: undefined
}
// 搜索reducer方法
const reducerSearch = (state: any, newState: any) => {
return {
pageSize: state.pageSize = newState.pageSize,
pageNum: state.pageNum = newState.pageNum,
roleName: state.roleName = newState.roleName,
roleKey: state.roleKey = newState.roleKey,
state: state.state = newState.state,
startTime: state.startTime = newState.startTime,
endTime: state.endTime = newState.endTime,
}
}
function Role() {
//搜索条件
const [queryParams, setQueryParams] = useReducer(queryReducer, initQueryParams);
//下拉框options
const [statusList, setStatusList] = useState<any>([])
//数据总数
const [total, setTotal] = useState<number>()
//表格list
const [list, setList] = useState<Array<DataType>>()
//表格数据加载中
const [loading, setLoading] = useState<boolean>(false)
//表格多选是否勾选
const [checkStrictly, setCheckStrictly] = useState(false);
//弹窗config
const [modalConfig, setModalConfig] = useState<IModalConfig>();
//单选options
const [radioOption, setRadioOption] = useState<any>()
//formHooks
const [form] = Form.useForm()
//筛选表单配置
const searchConfig = {
name: 'searchForm',
formItem: [
{
name: '角色名称',
type: 'input',
key: 'roleName'
},
{
name: '权限字符',
type: 'input',
key: 'roleKey'
},
{
name: '日期',
type: 'rangedate',
key: 'dateRange'
},
{
name: '状态',
type: 'select',
key: 'state',
options: statusList
}
]
} as object
// 表格列数据
const colums: ColumnsType<DataType> = [
{
title: '角色编号',
key: 'roleId',
dataIndex: 'roleId'
},
{
title: '角色名称',
key: 'roleName',
dataIndex: 'roleName',
},
{
title: '权限字符',
key: 'roleKey',
dataIndex: 'roleKey',
},
{
title: '显示顺序',
key: 'roleSort',
dataIndex: 'roleSort',
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render: ((_, { state }) => {
return (
<DTag options={statusList} state={state} />
)
})
},
{
title: '创建时间',
key: 'createTime',
dataIndex: 'createTime'
},
{
title: '操作',
key: 'action',
align: 'center',
render: ((_, record) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}></Button>
<Button type="text" onClick={() => handleDel(record)} danger></Button>
</Space>
))
}
]
// 获取角色list
const getList = () => {
setLoading(true)
getRoleList(queryParams).then((res:IResponse)=> {
setList(res.data.rows)
setTotal(res.data.total)
setLoading(false)
})
}
// 点击添加
const handleAdd = () => {
setRadioOption(statusList)
setModalConfig({ title: '添加', open: true, confirmLoading: false })
}
// 点击修改
const handleEdit = (row:any) => {
}
// 点击删除
const handleDel = (row:any) => {
}
// 点击提交
const handleSubmit = () => {}
// 点击取消
const handleCancel = () => {
form.resetFields();
setModalConfig({ title: '', open: false, confirmLoading: false })
}
// 分页
const pagination = (pageNum: any,pageSize: any) => {
// new Promise((resolve,reject) => {
// setQueryParams({type: 'pagination', data: {pageNum,pageSize }})
// resolve(true)
// }).then((res: any) => {
// getList()
// })
}
// 行数据选择
const rowSelection: TableRowSelection<DataType> = {
onChange: (selectedRowKeys, selectedRows) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
// setDictIds(Array.from(selectedRowKeys))
},
onSelect: (record, selected, selectedRows) => {
console.log(record, selected, selectedRows);
},
onSelectAll: (selected, selectedRows, changeRows) => {
console.log(selected, selectedRows, changeRows);
},
};
// 筛选表单提交事件
const submit = (v: any) => {
const {roleName, roleKey, state, dateRange} = v
const query = {
roleName,
roleKey,
state,
startTime: dateRange !== null && dateRange !== undefined ? parseDateTime(dateRange[0]._d) : undefined,
endTime: dateRange !== null && dateRange !== undefined ? parseDateTime(dateRange[1]._d) : undefined,
pageSize: 10,
pageNum: 1
}
new Promise( (resolve, reject) => {
setQueryParams({ type: 'search', data: query, void: reducerSearch})
resolve(true)
}).then((res)=>{
getList()
})
}
//获取筛选表单下拉框缓存数据
const getSelectOptions = async () => {
const options = await getCacheType('normal_disable').then((options: any) => {
return options
})
setStatusList(options)
}
useEffect(()=>{
getSelectOptions()
getList()
},[])
return(
<Fragment>
<SearchForm config={searchConfig} submit={submit}></SearchForm>
<div className="table-headbtn-box">
<Button icon={<PlusCircleTwoTone />} type="primary" size="middle" className="table-headbtn" onClick={handleAdd}></Button>
<Button icon={<EditTwoTone />} type="primary" ghost className="table-headbtn" onClick={handleEdit} ></Button>
<Button icon={<DeleteTwoTone />} type="primary" danger className="table-headbtn" ></Button>
</div>
<Table
columns={colums}
dataSource={list}
loading={loading}
rowSelection={{ ...rowSelection, checkStrictly }}
pagination={{ total, onChange: pagination }}
rowKey='roleId'
/>
<Modal
open={modalConfig?.open}
title={modalConfig?.title}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={modalConfig?.confirmLoading}
width={700}
>
<Form
name="form"
form={form}
labelAlign='right'
labelCol={{
span: 6,
offset: 0
}}
>
<Row>
<Col span={12}>
<Form.Item
label="角色名称"
name="roleName"
>
<Input placeholder="请输入角色名称"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="权限字符"
name="roleKey"
>
<Input placeholder="请输入权限字符"/>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="角色顺序"
name="roleSort"
>
<Input placeholder="请输入角色顺序" type="number"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="状态"
name="state"
>
<Radio.Group
options={radioOption}
/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</Fragment>
)
}
export default Role

26
tsconfig.json Normal file
View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}