初始化(仅供参考后续会做大更改)
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
## 储存的用户数据
|
||||
REACT_APP_USERINFO = userInfo
|
||||
|
||||
REACT_APP_LOGINPATH = '/'
|
||||
|
||||
REACT_APP_HOMEPATH = '/home'
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
## 开发环境
|
||||
|
||||
REACT_APP_APIPATH = http://gateway.odliken.cn/
|
||||
|
|
@ -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*
|
||||
|
|
@ -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
|
||||
|
|
@ -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 can’t go back!**
|
||||
|
||||
If you aren’t 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 you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t 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/).
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import service from "../../utils/http";
|
||||
|
||||
// 获取路由
|
||||
export const getRouter = (params:any) => {
|
||||
return service({
|
||||
url: '/auth/router',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
After Width: | Height: | Size: 861 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
.aside {
|
||||
height: calc(100% - 64px) !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.aside {
|
||||
height: calc(100% - 64px) !important;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
||||
}))
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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
|
||||
|
|
@ -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>,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { IBaseDataType } from "..";
|
||||
|
||||
export interface DataType extends IBaseDataType{
|
||||
roleId: number | string,
|
||||
roleName: string,
|
||||
roleKey: string,
|
||||
roleSort: string,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||