commit
e9d80733a1
89 changed files with 6127 additions and 0 deletions
@ -0,0 +1,33 @@ |
||||
HELP.md |
||||
target/ |
||||
!.mvn/wrapper/maven-wrapper.jar |
||||
!**/src/main/**/target/ |
||||
!**/src/test/**/target/ |
||||
|
||||
### STS ### |
||||
.apt_generated |
||||
.classpath |
||||
.factorypath |
||||
.project |
||||
.settings |
||||
.springBeans |
||||
.sts4-cache |
||||
|
||||
### IntelliJ IDEA ### |
||||
.idea |
||||
*.iws |
||||
*.iml |
||||
*.ipr |
||||
|
||||
### NetBeans ### |
||||
/nbproject/private/ |
||||
/nbbuild/ |
||||
/dist/ |
||||
/nbdist/ |
||||
/.nb-gradle/ |
||||
build/ |
||||
!**/src/main/**/build/ |
||||
!**/src/test/**/build/ |
||||
|
||||
### VS Code ### |
||||
.vscode/ |
Binary file not shown.
@ -0,0 +1,18 @@ |
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you under the Apache License, Version 2.0 (the |
||||
# "License"); you may not use this file except in compliance |
||||
# with the License. You may obtain a copy of the License at |
||||
# |
||||
# https://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, |
||||
# software distributed under the License is distributed on an |
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
# KIND, either express or implied. See the License for the |
||||
# specific language governing permissions and limitations |
||||
# under the License. |
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip |
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar |
@ -0,0 +1,26 @@ |
||||
DROP database IF EXISTS db_user; |
||||
|
||||
create database db_user; |
||||
|
||||
use db_user; |
||||
|
||||
DROP TABLE IF EXISTS user; |
||||
|
||||
CREATE TABLE user |
||||
( |
||||
id BIGINT(20) NOT NULL COMMENT '主键ID', |
||||
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', |
||||
age INT(11) NULL DEFAULT NULL COMMENT '年龄', |
||||
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', |
||||
PRIMARY KEY (id) |
||||
); |
||||
|
||||
|
||||
DELETE FROM user; |
||||
|
||||
INSERT INTO user (id, name, age, email) |
||||
VALUES (1, 'Jone', 18, 'test1@baomidou.com'), |
||||
(2, 'Jack', 20, 'test2@baomidou.com'), |
||||
(3, 'Tom', 28, 'test3@baomidou.com'), |
||||
(4, 'Sandy', 21, 'test4@baomidou.com'), |
||||
(5, 'Billie', 24, 'test5@baomidou.com'); |
@ -0,0 +1,26 @@ |
||||
DROP database IF EXISTS db_user; |
||||
|
||||
create database db_user; |
||||
|
||||
DROP TABLE IF EXISTS user; |
||||
|
||||
create table user |
||||
( |
||||
username varchar(256) null comment '用户昵称', |
||||
id bigint auto_increment comment 'id' |
||||
primary key, |
||||
userAccount varchar(256) null comment '账号', |
||||
avatarUrl varchar(1024) null comment '用户头像', |
||||
gender tinyint null comment '性别', |
||||
userPassword varchar(512) not null comment '密码', |
||||
phone varchar(128) null comment '电话', |
||||
email varchar(512) null comment '邮箱', |
||||
userStatus int default 0 not null comment '状态 0 - 正常', |
||||
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', |
||||
updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, |
||||
isDelete tinyint default 0 not null comment '是否删除', |
||||
userRole int default 0 not null comment '用户角色 0 - 普通用户 1 - 管理员', |
||||
planetCode varchar(512) null comment '星球编号' |
||||
) |
||||
comment '用户'; |
||||
|
@ -0,0 +1,316 @@ |
||||
#!/bin/sh |
||||
# ---------------------------------------------------------------------------- |
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you under the Apache License, Version 2.0 (the |
||||
# "License"); you may not use this file except in compliance |
||||
# with the License. You may obtain a copy of the License at |
||||
# |
||||
# https://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, |
||||
# software distributed under the License is distributed on an |
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
# KIND, either express or implied. See the License for the |
||||
# specific language governing permissions and limitations |
||||
# under the License. |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Maven Start Up Batch script |
||||
# |
||||
# Required ENV vars: |
||||
# ------------------ |
||||
# JAVA_HOME - location of a JDK home dir |
||||
# |
||||
# Optional ENV vars |
||||
# ----------------- |
||||
# M2_HOME - location of maven2's installed home dir |
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven |
||||
# e.g. to debug Maven itself, use |
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then |
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then |
||||
. /usr/local/etc/mavenrc |
||||
fi |
||||
|
||||
if [ -f /etc/mavenrc ] ; then |
||||
. /etc/mavenrc |
||||
fi |
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then |
||||
. "$HOME/.mavenrc" |
||||
fi |
||||
|
||||
fi |
||||
|
||||
# OS specific support. $var _must_ be set to either true or false. |
||||
cygwin=false; |
||||
darwin=false; |
||||
mingw=false |
||||
case "`uname`" in |
||||
CYGWIN*) cygwin=true ;; |
||||
MINGW*) mingw=true;; |
||||
Darwin*) darwin=true |
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home |
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html |
||||
if [ -z "$JAVA_HOME" ]; then |
||||
if [ -x "/usr/libexec/java_home" ]; then |
||||
export JAVA_HOME="`/usr/libexec/java_home`" |
||||
else |
||||
export JAVA_HOME="/Library/Java/Home" |
||||
fi |
||||
fi |
||||
;; |
||||
esac |
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then |
||||
if [ -r /etc/gentoo-release ] ; then |
||||
JAVA_HOME=`java-config --jre-home` |
||||
fi |
||||
fi |
||||
|
||||
if [ -z "$M2_HOME" ] ; then |
||||
## resolve links - $0 may be a link to maven's home |
||||
PRG="$0" |
||||
|
||||
# need this for relative symlinks |
||||
while [ -h "$PRG" ] ; do |
||||
ls=`ls -ld "$PRG"` |
||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||
if expr "$link" : '/.*' > /dev/null; then |
||||
PRG="$link" |
||||
else |
||||
PRG="`dirname "$PRG"`/$link" |
||||
fi |
||||
done |
||||
|
||||
saveddir=`pwd` |
||||
|
||||
M2_HOME=`dirname "$PRG"`/.. |
||||
|
||||
# make it fully qualified |
||||
M2_HOME=`cd "$M2_HOME" && pwd` |
||||
|
||||
cd "$saveddir" |
||||
# echo Using m2 at $M2_HOME |
||||
fi |
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched |
||||
if $cygwin ; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME=`cygpath --unix "$M2_HOME"` |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"` |
||||
[ -n "$CLASSPATH" ] && |
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` |
||||
fi |
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched |
||||
if $mingw ; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`" |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" |
||||
fi |
||||
|
||||
if [ -z "$JAVA_HOME" ]; then |
||||
javaExecutable="`which javac`" |
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then |
||||
# readlink(1) is not available as standard on Solaris 10. |
||||
readLink=`which readlink` |
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then |
||||
if $darwin ; then |
||||
javaHome="`dirname \"$javaExecutable\"`" |
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" |
||||
else |
||||
javaExecutable="`readlink -f \"$javaExecutable\"`" |
||||
fi |
||||
javaHome="`dirname \"$javaExecutable\"`" |
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'` |
||||
JAVA_HOME="$javaHome" |
||||
export JAVA_HOME |
||||
fi |
||||
fi |
||||
fi |
||||
|
||||
if [ -z "$JAVACMD" ] ; then |
||||
if [ -n "$JAVA_HOME" ] ; then |
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||
# IBM's JDK on AIX uses strange locations for the executables |
||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
else |
||||
JAVACMD="$JAVA_HOME/bin/java" |
||||
fi |
||||
else |
||||
JAVACMD="`\\unset -f command; \\command -v java`" |
||||
fi |
||||
fi |
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then |
||||
echo "Error: JAVA_HOME is not defined correctly." >&2 |
||||
echo " We cannot execute $JAVACMD" >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then |
||||
echo "Warning: JAVA_HOME environment variable is not set." |
||||
fi |
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher |
||||
|
||||
# traverses directory structure from process work directory to filesystem root |
||||
# first directory with .mvn subdirectory is considered project base directory |
||||
find_maven_basedir() { |
||||
|
||||
if [ -z "$1" ] |
||||
then |
||||
echo "Path not specified to find_maven_basedir" |
||||
return 1 |
||||
fi |
||||
|
||||
basedir="$1" |
||||
wdir="$1" |
||||
while [ "$wdir" != '/' ] ; do |
||||
if [ -d "$wdir"/.mvn ] ; then |
||||
basedir=$wdir |
||||
break |
||||
fi |
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc) |
||||
if [ -d "${wdir}" ]; then |
||||
wdir=`cd "$wdir/.."; pwd` |
||||
fi |
||||
# end of workaround |
||||
done |
||||
echo "${basedir}" |
||||
} |
||||
|
||||
# concatenates all lines of a file |
||||
concat_lines() { |
||||
if [ -f "$1" ]; then |
||||
echo "$(tr -s '\n' ' ' < "$1")" |
||||
fi |
||||
} |
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"` |
||||
if [ -z "$BASE_DIR" ]; then |
||||
exit 1; |
||||
fi |
||||
|
||||
########################################################################################## |
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central |
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data. |
||||
########################################################################################## |
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found .mvn/wrapper/maven-wrapper.jar" |
||||
fi |
||||
else |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." |
||||
fi |
||||
if [ -n "$MVNW_REPOURL" ]; then |
||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
else |
||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
fi |
||||
while IFS="=" read key value; do |
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; |
||||
esac |
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Downloading from: $jarUrl" |
||||
fi |
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" |
||||
if $cygwin; then |
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` |
||||
fi |
||||
|
||||
if command -v wget > /dev/null; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found wget ... using wget" |
||||
fi |
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then |
||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" |
||||
else |
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" |
||||
fi |
||||
elif command -v curl > /dev/null; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found curl ... using curl" |
||||
fi |
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then |
||||
curl -o "$wrapperJarPath" "$jarUrl" -f |
||||
else |
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f |
||||
fi |
||||
|
||||
else |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Falling back to using Java to download" |
||||
fi |
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" |
||||
# For Cygwin, switch paths to Windows format before running javac |
||||
if $cygwin; then |
||||
javaClass=`cygpath --path --windows "$javaClass"` |
||||
fi |
||||
if [ -e "$javaClass" ]; then |
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo " - Compiling MavenWrapperDownloader.java ..." |
||||
fi |
||||
# Compiling the Java class |
||||
("$JAVA_HOME/bin/javac" "$javaClass") |
||||
fi |
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then |
||||
# Running the downloader |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo " - Running MavenWrapperDownloader.java ..." |
||||
fi |
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") |
||||
fi |
||||
fi |
||||
fi |
||||
fi |
||||
########################################################################################## |
||||
# End of extension |
||||
########################################################################################## |
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo $MAVEN_PROJECTBASEDIR |
||||
fi |
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" |
||||
|
||||
# For Cygwin, switch paths to Windows format before running java |
||||
if $cygwin; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"` |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` |
||||
[ -n "$CLASSPATH" ] && |
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"` |
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] && |
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` |
||||
fi |
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will |
||||
# work with both Windows and non-Windows executions. |
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" |
||||
export MAVEN_CMD_LINE_ARGS |
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
||||
|
||||
exec "$JAVACMD" \ |
||||
$MAVEN_OPTS \ |
||||
$MAVEN_DEBUG_OPTS \ |
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ |
||||
"-Dmaven.home=${M2_HOME}" \ |
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ |
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" |
@ -0,0 +1,188 @@ |
||||
@REM ---------------------------------------------------------------------------- |
||||
@REM Licensed to the Apache Software Foundation (ASF) under one |
||||
@REM or more contributor license agreements. See the NOTICE file |
||||
@REM distributed with this work for additional information |
||||
@REM regarding copyright ownership. The ASF licenses this file |
||||
@REM to you under the Apache License, Version 2.0 (the |
||||
@REM "License"); you may not use this file except in compliance |
||||
@REM with the License. You may obtain a copy of the License at |
||||
@REM |
||||
@REM https://www.apache.org/licenses/LICENSE-2.0 |
||||
@REM |
||||
@REM Unless required by applicable law or agreed to in writing, |
||||
@REM software distributed under the License is distributed on an |
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
@REM KIND, either express or implied. See the License for the |
||||
@REM specific language governing permissions and limitations |
||||
@REM under the License. |
||||
@REM ---------------------------------------------------------------------------- |
||||
|
||||
@REM ---------------------------------------------------------------------------- |
||||
@REM Maven Start Up Batch script |
||||
@REM |
||||
@REM Required ENV vars: |
||||
@REM JAVA_HOME - location of a JDK home dir |
||||
@REM |
||||
@REM Optional ENV vars |
||||
@REM M2_HOME - location of maven2's installed home dir |
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands |
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending |
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven |
||||
@REM e.g. to debug Maven itself, use |
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
||||
@REM ---------------------------------------------------------------------------- |
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' |
||||
@echo off |
||||
@REM set title of command window |
||||
title %0 |
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' |
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% |
||||
|
||||
@REM set %HOME% to equivalent of $HOME |
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") |
||||
|
||||
@REM Execute a user defined script before this one |
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre |
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending |
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* |
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* |
||||
:skipRcPre |
||||
|
||||
@setlocal |
||||
|
||||
set ERROR_CODE=0 |
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal |
||||
@setlocal |
||||
|
||||
@REM ==== START VALIDATION ==== |
||||
if not "%JAVA_HOME%" == "" goto OkJHome |
||||
|
||||
echo. |
||||
echo Error: JAVA_HOME not found in your environment. >&2 |
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
||||
echo location of your Java installation. >&2 |
||||
echo. |
||||
goto error |
||||
|
||||
:OkJHome |
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init |
||||
|
||||
echo. |
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2 |
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2 |
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
||||
echo location of your Java installation. >&2 |
||||
echo. |
||||
goto error |
||||
|
||||
@REM ==== END VALIDATION ==== |
||||
|
||||
:init |
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". |
||||
@REM Fallback to current working directory if not found. |
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% |
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir |
||||
|
||||
set EXEC_DIR=%CD% |
||||
set WDIR=%EXEC_DIR% |
||||
:findBaseDir |
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound |
||||
cd .. |
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound |
||||
set WDIR=%CD% |
||||
goto findBaseDir |
||||
|
||||
:baseDirFound |
||||
set MAVEN_PROJECTBASEDIR=%WDIR% |
||||
cd "%EXEC_DIR%" |
||||
goto endDetectBaseDir |
||||
|
||||
:baseDirNotFound |
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR% |
||||
cd "%EXEC_DIR%" |
||||
|
||||
:endDetectBaseDir |
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig |
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion |
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a |
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% |
||||
|
||||
:endReadAdditionalConfig |
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" |
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" |
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( |
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B |
||||
) |
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central |
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data. |
||||
if exist %WRAPPER_JAR% ( |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Found %WRAPPER_JAR% |
||||
) |
||||
) else ( |
||||
if not "%MVNW_REPOURL%" == "" ( |
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" |
||||
) |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ... |
||||
echo Downloading from: %DOWNLOAD_URL% |
||||
) |
||||
|
||||
powershell -Command "&{"^ |
||||
"$webclient = new-object System.Net.WebClient;"^ |
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ |
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ |
||||
"}"^ |
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ |
||||
"}" |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Finished downloading %WRAPPER_JAR% |
||||
) |
||||
) |
||||
@REM End of extension |
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will |
||||
@REM work with both Windows and non-Windows executions. |
||||
set MAVEN_CMD_LINE_ARGS=%* |
||||
|
||||
%MAVEN_JAVA_EXE% ^ |
||||
%JVM_CONFIG_MAVEN_PROPS% ^ |
||||
%MAVEN_OPTS% ^ |
||||
%MAVEN_DEBUG_OPTS% ^ |
||||
-classpath %WRAPPER_JAR% ^ |
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ |
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* |
||||
if ERRORLEVEL 1 goto error |
||||
goto end |
||||
|
||||
:error |
||||
set ERROR_CODE=1 |
||||
|
||||
:end |
||||
@endlocal & set ERROR_CODE=%ERROR_CODE% |
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost |
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending |
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" |
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" |
||||
:skipRcPost |
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' |
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause |
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% |
||||
|
||||
cmd /C exit /B %ERROR_CODE% |
@ -0,0 +1,85 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<parent> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-parent</artifactId> |
||||
<version>2.7.9</version> |
||||
<relativePath/> |
||||
</parent> |
||||
<groupId>cc.bnblogs</groupId> |
||||
<artifactId>user-center-backend</artifactId> |
||||
<version>0.0.1-SNAPSHOT</version> |
||||
<name>user-center-backend</name> |
||||
<description>user-center-backend</description> |
||||
<properties> |
||||
<java.version>1.8</java.version> |
||||
</properties> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-web</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.mybatis.spring.boot</groupId> |
||||
<artifactId>mybatis-spring-boot-starter</artifactId> |
||||
<version>2.3.0</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-devtools</artifactId> |
||||
<scope>runtime</scope> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.mysql</groupId> |
||||
<artifactId>mysql-connector-j</artifactId> |
||||
<scope>runtime</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-configuration-processor</artifactId> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.baomidou</groupId> |
||||
<artifactId>mybatis-plus-boot-starter</artifactId> |
||||
<version>3.5.3.1</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>junit</groupId> |
||||
<artifactId>junit</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
<configuration> |
||||
<excludes> |
||||
<exclude> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
</exclude> |
||||
</excludes> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
|
||||
</project> |
@ -0,0 +1,15 @@ |
||||
package cc.bnblogs.usercenterbackend; |
||||
|
||||
import org.mybatis.spring.annotation.MapperScan; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
||||
@SpringBootApplication |
||||
@MapperScan("cc.bnblogs.usercenterbackend.mapper") |
||||
public class UserCenterBackendApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication.run(UserCenterBackendApplication.class, args); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,12 @@ |
||||
package cc.bnblogs.usercenterbackend.mapper; |
||||
|
||||
import cc.bnblogs.usercenterbackend.model.User; |
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
||||
/** |
||||
* @description: |
||||
* @author: zfp@bnblogs.cc |
||||
* @date: 2023/3/13 15:39 |
||||
*/ |
||||
public interface UserMapper extends BaseMapper<User>{ |
||||
} |
@ -0,0 +1,16 @@ |
||||
package cc.bnblogs.usercenterbackend.model; |
||||
|
||||
import lombok.Data; |
||||
|
||||
/** |
||||
* @description: 用户实体 |
||||
* @author: zfp@bnblogs.cc |
||||
* @date: 2023/3/13 15:39 |
||||
*/ |
||||
@Data |
||||
public class User { |
||||
private Long id; |
||||
private String name; |
||||
private Integer age; |
||||
private String email; |
||||
} |
@ -0,0 +1,10 @@ |
||||
spring: |
||||
application: |
||||
name: user-center |
||||
datasource: |
||||
username: root |
||||
password: zfp251217 |
||||
url: jdbc:mysql://192.168.153.135:3306/db_user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8 |
||||
driver-class-name: com.mysql.cj.jdbc.Driver |
||||
server: |
||||
port: 8080 |
@ -0,0 +1,26 @@ |
||||
package cc.bnblogs.usercenterbackend; |
||||
|
||||
import cc.bnblogs.usercenterbackend.mapper.UserMapper; |
||||
import cc.bnblogs.usercenterbackend.model.User; |
||||
import org.junit.Assert; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.List; |
||||
|
||||
@SpringBootTest |
||||
class UserCenterBackendApplicationTests { |
||||
@Resource |
||||
private UserMapper userMapper; |
||||
|
||||
@Test |
||||
void contextLoads() { |
||||
System.out.println(("----- selectAll method test ------")); |
||||
List<User> userList = userMapper.selectList(null); |
||||
Assert.assertEquals(5, userList.size()); |
||||
userList.forEach(System.out::println); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,27 @@ |
||||
package cc.bnblogs.usercenterbackend.mapper; |
||||
|
||||
import cc.bnblogs.usercenterbackend.model.User; |
||||
import org.junit.Assert; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
||||
import javax.annotation.Resource; |
||||
import java.util.List; |
||||
|
||||
@SpringBootTest |
||||
//@RunWith(SpringRunner.class)
|
||||
public class SampleTest { |
||||
|
||||
@Resource |
||||
private UserMapper userMapper; |
||||
|
||||
// 导入这个import org.junit.jupiter.api.Test;就不需要加@RunWith(SpringRunner.class)
|
||||
@Test |
||||
public void testSelect() { |
||||
System.out.println(("----- selectAll method test ------")); |
||||
List<User> userList = userMapper.selectList(null); |
||||
Assert.assertEquals(5, userList.size()); |
||||
userList.forEach(System.out::println); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,16 @@ |
||||
# http://editorconfig.org |
||||
root = true |
||||
|
||||
[*] |
||||
indent_style = space |
||||
indent_size = 2 |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
|
||||
[*.md] |
||||
trim_trailing_whitespace = false |
||||
|
||||
[Makefile] |
||||
indent_style = tab |
@ -0,0 +1,8 @@ |
||||
/lambda/ |
||||
/scripts |
||||
/config |
||||
.history |
||||
public |
||||
dist |
||||
.umi |
||||
mock |
@ -0,0 +1,8 @@ |
||||
module.exports = { |
||||
extends: [require.resolve('@umijs/fabric/dist/eslint')], |
||||
globals: { |
||||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, |
||||
page: true, |
||||
REACT_APP_ENV: true, |
||||
}, |
||||
}; |
@ -0,0 +1,40 @@ |
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
||||
|
||||
# dependencies |
||||
**/node_modules |
||||
# roadhog-api-doc ignore |
||||
/src/utils/request-temp.js |
||||
_roadhog-api-doc |
||||
|
||||
# production |
||||
/dist |
||||
|
||||
# misc |
||||
.DS_Store |
||||
npm-debug.log* |
||||
yarn-error.log |
||||
|
||||
/coverage |
||||
.idea |
||||
yarn.lock |
||||
package-lock.json |
||||
pnpm-lock.yaml |
||||
*bak |
||||
|
||||
|
||||
# visual studio code |
||||
.history |
||||
*.log |
||||
functions/* |
||||
.temp/** |
||||
|
||||
# umi |
||||
.umi |
||||
.umi-production |
||||
|
||||
# screenshot |
||||
screenshot |
||||
.firebase |
||||
.eslintcache |
||||
|
||||
build |
@ -0,0 +1 @@ |
||||
_ |
@ -0,0 +1,7 @@ |
||||
#!/bin/sh |
||||
. "$(dirname "$0")/_/husky.sh" |
||||
|
||||
# Export Git hook params |
||||
export GIT_PARAMS=$* |
||||
|
||||
npx --no-install fabric verify-commit |
@ -0,0 +1,4 @@ |
||||
#!/bin/sh |
||||
. "$(dirname "$0")/_/husky.sh" |
||||
|
||||
npx --no-install lint-staged |
@ -0,0 +1,23 @@ |
||||
**/*.svg |
||||
package.json |
||||
.umi |
||||
.umi-production |
||||
/dist |
||||
.dockerignore |
||||
.DS_Store |
||||
.eslintignore |
||||
*.png |
||||
*.toml |
||||
docker |
||||
.editorconfig |
||||
Dockerfile* |
||||
.gitignore |
||||
.prettierignore |
||||
LICENSE |
||||
.eslintcache |
||||
*.lock |
||||
yarn-error.log |
||||
.history |
||||
CNAME |
||||
/build |
||||
/public |
@ -0,0 +1,5 @@ |
||||
const fabric = require('@umijs/fabric'); |
||||
|
||||
module.exports = { |
||||
...fabric.prettier, |
||||
}; |
@ -0,0 +1,3 @@ |
||||
module.exports = { |
||||
extends: [require.resolve('@umijs/fabric/dist/stylelint')], |
||||
}; |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"recommendations": [ |
||||
"esbenp.prettier-vscode", |
||||
"dbaeumer.vscode-eslint", |
||||
"stylelint.vscode-stylelint", |
||||
"wangzy.sneak-mark" |
||||
] |
||||
} |
@ -0,0 +1,5 @@ |
||||
{ |
||||
"editor.formatOnSave": true, |
||||
"prettier.requireConfig": true, |
||||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
||||
} |
@ -0,0 +1,57 @@ |
||||
# Ant Design Pro |
||||
|
||||
This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use. |
||||
|
||||
## Environment Prepare |
||||
|
||||
Install `node_modules`: |
||||
|
||||
```bash |
||||
npm install |
||||
``` |
||||
|
||||
or |
||||
|
||||
```bash |
||||
yarn |
||||
``` |
||||
|
||||
## Provided Scripts |
||||
|
||||
Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test. |
||||
|
||||
Scripts provided in `package.json`. It's safe to modify or add additional script: |
||||
|
||||
### Start project |
||||
|
||||
```bash |
||||
npm start |
||||
``` |
||||
|
||||
### Build project |
||||
|
||||
```bash |
||||
npm run build |
||||
``` |
||||
|
||||
### Check code style |
||||
|
||||
```bash |
||||
npm run lint |
||||
``` |
||||
|
||||
You can also use script to auto fix some lint error: |
||||
|
||||
```bash |
||||
npm run lint:fix |
||||
``` |
||||
|
||||
### Test code |
||||
|
||||
```bash |
||||
npm test |
||||
``` |
||||
|
||||
## More |
||||
|
||||
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro). |
@ -0,0 +1,15 @@ |
||||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi'; |
||||
|
||||
export default defineConfig({ |
||||
plugins: [ |
||||
// https://github.com/zthxxx/react-dev-inspector
|
||||
'react-dev-inspector/plugins/umi/react-inspector', |
||||
], |
||||
// https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
|
||||
inspectorConfig: { |
||||
exclude: [], |
||||
babelPlugins: [], |
||||
babelOptions: {}, |
||||
}, |
||||
}); |
@ -0,0 +1,52 @@ |
||||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi'; |
||||
import defaultSettings from './defaultSettings'; |
||||
import proxy from './proxy'; |
||||
import routes from './routes'; |
||||
const { REACT_APP_ENV } = process.env; |
||||
export default defineConfig({ |
||||
hash: true, |
||||
antd: {}, |
||||
dva: { |
||||
hmr: true, |
||||
}, |
||||
layout: { |
||||
// https://umijs.org/zh-CN/plugins/plugin-layout
|
||||
locale: true, |
||||
siderWidth: 208, |
||||
...defaultSettings, |
||||
}, |
||||
dynamicImport: { |
||||
loading: '@ant-design/pro-layout/es/PageLoading', |
||||
}, |
||||
targets: { |
||||
ie: 11, |
||||
}, |
||||
// umi routes: https://umijs.org/docs/routing
|
||||
routes, |
||||
access: {}, |
||||
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
|
||||
theme: { |
||||
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default
|
||||
// 只有设置为 variable, 才能使用 configProvide 动态设置主色调
|
||||
// https://ant.design/docs/react/customize-theme-variable-cn
|
||||
'root-entry-name': 'variable', |
||||
}, |
||||
// esbuild is father build tools
|
||||
// https://umijs.org/plugins/plugin-esbuild
|
||||
esbuild: {}, |
||||
title: false, |
||||
ignoreMomentLocale: true, |
||||
proxy: proxy[REACT_APP_ENV || 'dev'], |
||||
manifest: { |
||||
basePath: '/', |
||||
}, |
||||
// Fast Refresh 热更新
|
||||
fastRefresh: {}, |
||||
nodeModulesTransform: { |
||||
type: 'none', |
||||
}, |
||||
mfsu: {}, |
||||
webpack5: {}, |
||||
exportStatic: {}, |
||||
}); |
@ -0,0 +1,21 @@ |
||||
import { Settings as LayoutSettings } from '@ant-design/pro-components'; |
||||
|
||||
const Settings: LayoutSettings & { |
||||
pwa?: boolean; |
||||
logo?: string; |
||||
} = { |
||||
navTheme: 'light', |
||||
// 拂晓蓝
|
||||
primaryColor: '#1890ff', |
||||
layout: 'mix', |
||||
contentWidth: 'Fluid', |
||||
fixedHeader: false, |
||||
fixSiderbar: true, |
||||
colorWeak: false, |
||||
title: 'Ant Design Pro', |
||||
pwa: false, |
||||
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', |
||||
iconfontUrl: '', |
||||
}; |
||||
|
||||
export default Settings; |
@ -0,0 +1,34 @@ |
||||
/** |
||||
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 |
||||
* ------------------------------- |
||||
* The agent cannot take effect in the production environment |
||||
* so there is no configuration of the production environment |
||||
* For details, please see |
||||
* https://pro.ant.design/docs/deploy
|
||||
*/ |
||||
export default { |
||||
dev: { |
||||
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||
'/api/': { |
||||
// 要代理的地址
|
||||
target: 'https://preview.pro.ant.design', |
||||
// 配置了这个可以从 http 代理到 https
|
||||
// 依赖 origin 的功能可能需要这个,比如 cookie
|
||||
changeOrigin: true, |
||||
}, |
||||
}, |
||||
test: { |
||||
'/api/': { |
||||
target: 'https://proapi.azurewebsites.net', |
||||
changeOrigin: true, |
||||
pathRewrite: { '^': '' }, |
||||
}, |
||||
}, |
||||
pre: { |
||||
'/api/': { |
||||
target: 'your pre url', |
||||
changeOrigin: true, |
||||
pathRewrite: { '^': '' }, |
||||
}, |
||||
}, |
||||
}; |
@ -0,0 +1,53 @@ |
||||
export default [ |
||||
{ |
||||
path: '/user', |
||||
layout: false, |
||||
routes: [ |
||||
{ |
||||
path: '/user/login', |
||||
component: './user/Login', |
||||
}, |
||||
{ |
||||
component: './404', |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
path: '/welcome', |
||||
icon: 'smile', |
||||
component: './Welcome', |
||||
}, |
||||
{ |
||||
path: '/admin', |
||||
icon: 'crown', |
||||
access: 'canAdmin', |
||||
routes: [ |
||||
{ |
||||
path: '/admin/sub-page', |
||||
icon: 'smile', |
||||
component: './Welcome', |
||||
}, |
||||
{ |
||||
component: './404', |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
icon: 'table', |
||||
path: '/list', |
||||
component: './TableList', |
||||
}, |
||||
{ |
||||
path: '/', |
||||
redirect: '/welcome', |
||||
}, |
||||
{ |
||||
name: '工作台', |
||||
icon: 'smile', |
||||
path: '/dashboardworkplace', |
||||
component: './DashboardWorkplace', |
||||
}, |
||||
{ |
||||
component: './404', |
||||
}, |
||||
]; |
@ -0,0 +1,11 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"jsx": "react-jsx", |
||||
"emitDecoratorMetadata": true, |
||||
"experimentalDecorators": true, |
||||
"baseUrl": ".", |
||||
"paths": { |
||||
"@/*": ["./src/*"] |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,174 @@ |
||||
import { Request, Response } from 'express'; |
||||
import moment from 'moment'; |
||||
import { parse } from 'url'; |
||||
|
||||
// mock tableListDataSource
|
||||
const genList = (current: number, pageSize: number) => { |
||||
const tableListDataSource: API.RuleListItem[] = []; |
||||
|
||||
for (let i = 0; i < pageSize; i += 1) { |
||||
const index = (current - 1) * 10 + i; |
||||
tableListDataSource.push({ |
||||
key: index, |
||||
disabled: i % 6 === 0, |
||||
href: 'https://ant.design', |
||||
avatar: [ |
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
][i % 2], |
||||
name: `TradeCode ${index}`, |
||||
owner: '曲丽丽', |
||||
desc: '这是一段描述', |
||||
callNo: Math.floor(Math.random() * 1000), |
||||
status: Math.floor(Math.random() * 10) % 4, |
||||
updatedAt: moment().format('YYYY-MM-DD'), |
||||
createdAt: moment().format('YYYY-MM-DD'), |
||||
progress: Math.ceil(Math.random() * 100), |
||||
}); |
||||
} |
||||
tableListDataSource.reverse(); |
||||
return tableListDataSource; |
||||
}; |
||||
|
||||
let tableListDataSource = genList(1, 100); |
||||
|
||||
function getRule(req: Request, res: Response, u: string) { |
||||
let realUrl = u; |
||||
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { |
||||
realUrl = req.url; |
||||
} |
||||
const { current = 1, pageSize = 10 } = req.query; |
||||
const params = parse(realUrl, true).query as unknown as API.PageParams & |
||||
API.RuleListItem & { |
||||
sorter: any; |
||||
filter: any; |
||||
}; |
||||
|
||||
let dataSource = [...tableListDataSource].slice( |
||||
((current as number) - 1) * (pageSize as number), |
||||
(current as number) * (pageSize as number), |
||||
); |
||||
if (params.sorter) { |
||||
const sorter = JSON.parse(params.sorter); |
||||
dataSource = dataSource.sort((prev, next) => { |
||||
let sortNumber = 0; |
||||
Object.keys(sorter).forEach((key) => { |
||||
if (sorter[key] === 'descend') { |
||||
if (prev[key] - next[key] > 0) { |
||||
sortNumber += -1; |
||||
} else { |
||||
sortNumber += 1; |
||||
} |
||||
return; |
||||
} |
||||
if (prev[key] - next[key] > 0) { |
||||
sortNumber += 1; |
||||
} else { |
||||
sortNumber += -1; |
||||
} |
||||
}); |
||||
return sortNumber; |
||||
}); |
||||
} |
||||
if (params.filter) { |
||||
const filter = JSON.parse(params.filter as any) as { |
||||
[key: string]: string[]; |
||||
}; |
||||
if (Object.keys(filter).length > 0) { |
||||
dataSource = dataSource.filter((item) => { |
||||
return Object.keys(filter).some((key) => { |
||||
if (!filter[key]) { |
||||
return true; |
||||
} |
||||
if (filter[key].includes(`${item[key]}`)) { |
||||
return true; |
||||
} |
||||
return false; |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
if (params.name) { |
||||
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || '')); |
||||
} |
||||
const result = { |
||||
data: dataSource, |
||||
total: tableListDataSource.length, |
||||
success: true, |
||||
pageSize, |
||||
current: parseInt(`${params.current}`, 10) || 1, |
||||
}; |
||||
|
||||
return res.json(result); |
||||
} |
||||
|
||||
function postRule(req: Request, res: Response, u: string, b: Request) { |
||||
let realUrl = u; |
||||
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { |
||||
realUrl = req.url; |
||||
} |
||||
|
||||
const body = (b && b.body) || req.body; |
||||
const { method, name, desc, key } = body; |
||||
|
||||
switch (method) { |
||||
/* eslint no-case-declarations:0 */ |
||||
case 'delete': |
||||
tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1); |
||||
break; |
||||
case 'post': |
||||
(() => { |
||||
const i = Math.ceil(Math.random() * 10000); |
||||
const newRule: API.RuleListItem = { |
||||
key: tableListDataSource.length, |
||||
href: 'https://ant.design', |
||||
avatar: [ |
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
][i % 2], |
||||
name, |
||||
owner: '曲丽丽', |
||||
desc, |
||||
callNo: Math.floor(Math.random() * 1000), |
||||
status: Math.floor(Math.random() * 10) % 2, |
||||
updatedAt: moment().format('YYYY-MM-DD'), |
||||
createdAt: moment().format('YYYY-MM-DD'), |
||||
progress: Math.ceil(Math.random() * 100), |
||||
}; |
||||
tableListDataSource.unshift(newRule); |
||||
return res.json(newRule); |
||||
})(); |
||||
return; |
||||
|
||||
case 'update': |
||||
(() => { |
||||
let newRule = {}; |
||||
tableListDataSource = tableListDataSource.map((item) => { |
||||
if (item.key === key) { |
||||
newRule = { ...item, desc, name }; |
||||
return { ...item, desc, name }; |
||||
} |
||||
return item; |
||||
}); |
||||
return res.json(newRule); |
||||
})(); |
||||
return; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
const result = { |
||||
list: tableListDataSource, |
||||
pagination: { |
||||
total: tableListDataSource.length, |
||||
}, |
||||
}; |
||||
|
||||
res.json(result); |
||||
} |
||||
|
||||
export default { |
||||
'GET /api/rule': getRule, |
||||
'POST /api/rule': postRule, |
||||
}; |
@ -0,0 +1,107 @@ |
||||
import { Request, Response } from 'express'; |
||||
|
||||
const getNotices = (req: Request, res: Response) => { |
||||
res.json({ |
||||
data: [ |
||||
{ |
||||
id: '000000001', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
||||
title: '你收到了 14 份新周报', |
||||
datetime: '2017-08-09', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000002', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', |
||||
title: '你推荐的 曲妮妮 已通过第三轮面试', |
||||
datetime: '2017-08-08', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000003', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', |
||||
title: '这种模板可以区分多种通知类型', |
||||
datetime: '2017-08-07', |
||||
read: true, |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000004', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', |
||||
title: '左侧图标用于区分不同的类型', |
||||
datetime: '2017-08-07', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000005', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
||||
title: '内容不要超过两行字,超出时自动截断', |
||||
datetime: '2017-08-07', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000006', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
title: '曲丽丽 评论了你', |
||||
description: '描述信息描述信息描述信息', |
||||
datetime: '2017-08-07', |
||||
type: 'message', |
||||
clickClose: true, |
||||
}, |
||||
{ |
||||
id: '000000007', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
title: '朱偏右 回复了你', |
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
datetime: '2017-08-07', |
||||
type: 'message', |
||||
clickClose: true, |
||||
}, |
||||
{ |
||||
id: '000000008', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
title: '标题', |
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
datetime: '2017-08-07', |
||||
type: 'message', |
||||
clickClose: true, |
||||
}, |
||||
{ |
||||
id: '000000009', |
||||
title: '任务名称', |
||||
description: '任务需要在 2017-01-12 20:00 前启动', |
||||
extra: '未开始', |
||||
status: 'todo', |
||||
type: 'event', |
||||
}, |
||||
{ |
||||
id: '000000010', |
||||
title: '第三方紧急代码变更', |
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', |
||||
extra: '马上到期', |
||||
status: 'urgent', |
||||
type: 'event', |
||||
}, |
||||
{ |
||||
id: '000000011', |
||||
title: '信息安全考试', |
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布', |
||||
extra: '已耗时 8 天', |
||||
status: 'doing', |
||||
type: 'event', |
||||
}, |
||||
{ |
||||
id: '000000012', |
||||
title: 'ABCD 版本发布', |
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', |
||||
extra: '进行中', |
||||
status: 'processing', |
||||
type: 'event', |
||||
}, |
||||
], |
||||
}); |
||||
}; |
||||
|
||||
export default { |
||||
'GET /api/notices': getNotices, |
||||
}; |
@ -0,0 +1,5 @@ |
||||
export default { |
||||
'/api/auth_routes': { |
||||
'/form/advanced-form': { authority: ['admin', 'user'] }, |
||||
}, |
||||
}; |
@ -0,0 +1,203 @@ |
||||
import { Request, Response } from 'express'; |
||||
|
||||
const waitTime = (time: number = 100) => { |
||||
return new Promise((resolve) => { |
||||
setTimeout(() => { |
||||
resolve(true); |
||||
}, time); |
||||
}); |
||||
}; |
||||
|
||||
async function getFakeCaptcha(req: Request, res: Response) { |
||||
await waitTime(2000); |
||||
return res.json('captcha-xxx'); |
||||
} |
||||
|
||||
const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; |
||||
|
||||
/** |
||||
* 当前用户的权限,如果为空代表没登录 |
||||
* current user access, if is '', user need login |
||||
* 如果是 pro 的预览,默认是有权限的 |
||||
*/ |
||||
let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; |
||||
|
||||
const getAccess = () => { |
||||
return access; |
||||
}; |
||||
|
||||
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
export default { |
||||
// 支持值为 Object 和 Array
|
||||
'GET /api/currentUser': (req: Request, res: Response) => { |
||||
if (!getAccess()) { |
||||
res.status(401).send({ |
||||
data: { |
||||
isLogin: false, |
||||
}, |
||||
errorCode: '401', |
||||
errorMessage: '请先登录!', |
||||
success: true, |
||||
}); |
||||
return; |
||||
} |
||||
res.send({ |
||||
success: true, |
||||
data: { |
||||
name: 'Serati Ma', |
||||
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', |
||||
userid: '00000001', |
||||
email: 'antdesign@alipay.com', |
||||
signature: '海纳百川,有容乃大', |
||||
title: '交互专家', |
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', |
||||
tags: [ |
||||
{ |
||||
key: '0', |
||||
label: '很有想法的', |
||||
}, |
||||
{ |
||||
key: '1', |
||||
label: '专注设计', |
||||
}, |
||||
{ |
||||
key: '2', |
||||
label: '辣~', |
||||
}, |
||||
{ |
||||
key: '3', |
||||
label: '大长腿', |
||||
}, |
||||
{ |
||||
key: '4', |
||||
label: '川妹子', |
||||
}, |
||||
{ |
||||
key: '5', |
||||
label: '海纳百川', |
||||
}, |
||||
], |
||||
notifyCount: 12, |
||||
unreadCount: 11, |
||||
country: 'China', |
||||
access: getAccess(), |
||||
geographic: { |
||||
province: { |
||||
label: '浙江省', |
||||
key: '330000', |
||||
}, |
||||
city: { |
||||
label: '杭州市', |
||||
key: '330100', |
||||
}, |
||||
}, |
||||
address: '西湖区工专路 77 号', |
||||
phone: '0752-268888888', |
||||
}, |
||||
}); |
||||
}, |
||||
// GET POST 可省略
|
||||
'GET /api/users': [ |
||||
{ |
||||
key: '1', |
||||
name: 'John Brown', |
||||
age: 32, |
||||
address: 'New York No. 1 Lake Park', |
||||
}, |
||||
{ |
||||
key: '2', |
||||
name: 'Jim Green', |
||||
age: 42, |
||||
address: 'London No. 1 Lake Park', |
||||
}, |
||||
{ |
||||
key: '3', |
||||
name: 'Joe Black', |
||||
age: 32, |
||||
address: 'Sidney No. 1 Lake Park', |
||||
}, |
||||
], |
||||
'POST /api/login/account': async (req: Request, res: Response) => { |
||||
const { password, username, type } = req.body; |
||||
await waitTime(2000); |
||||
if (password === 'ant.design' && username === 'admin') { |
||||
res.send({ |
||||
status: 'ok', |
||||
type, |
||||
currentAuthority: 'admin', |
||||
}); |
||||
access = 'admin'; |
||||
return; |
||||
} |
||||
if (password === 'ant.design' && username === 'user') { |
||||
res.send({ |
||||
status: 'ok', |
||||
type, |
||||
currentAuthority: 'user', |
||||
}); |
||||
access = 'user'; |
||||
return; |
||||
} |
||||
if (type === 'mobile') { |
||||
res.send({ |
||||
status: 'ok', |
||||
type, |
||||
currentAuthority: 'admin', |
||||
}); |
||||
access = 'admin'; |
||||
return; |
||||
} |
||||
|
||||
res.send({ |
||||
status: 'error', |
||||
type, |
||||
currentAuthority: 'guest', |
||||
}); |
||||
access = 'guest'; |
||||
}, |
||||
'POST /api/login/outLogin': (req: Request, res: Response) => { |
||||
access = ''; |
||||
res.send({ data: {}, success: true }); |
||||
}, |
||||
'POST /api/register': (req: Request, res: Response) => { |
||||
res.send({ status: 'ok', currentAuthority: 'user', success: true }); |
||||
}, |
||||
'GET /api/500': (req: Request, res: Response) => { |
||||
res.status(500).send({ |
||||
timestamp: 1513932555104, |
||||
status: 500, |
||||
error: 'error', |
||||
message: 'error', |
||||
path: '/base/category/list', |
||||
}); |
||||
}, |
||||
'GET /api/404': (req: Request, res: Response) => { |
||||
res.status(404).send({ |
||||
timestamp: 1513932643431, |
||||
status: 404, |
||||
error: 'Not Found', |
||||
message: 'No message available', |
||||
path: '/base/category/list/2121212', |
||||
}); |
||||
}, |
||||
'GET /api/403': (req: Request, res: Response) => { |
||||
res.status(403).send({ |
||||
timestamp: 1513932555104, |
||||
status: 403, |
||||
error: 'Forbidden', |
||||
message: 'Forbidden', |
||||
path: '/base/category/list', |
||||
}); |
||||
}, |
||||
'GET /api/401': (req: Request, res: Response) => { |
||||
res.status(401).send({ |
||||
timestamp: 1513932555104, |
||||
status: 401, |
||||
error: 'Unauthorized', |
||||
message: 'Unauthorized', |
||||
path: '/base/category/list', |
||||
}); |
||||
}, |
||||
|
||||
'GET /api/login/captcha': getFakeCaptcha, |
||||
}; |
@ -0,0 +1,110 @@ |
||||
{ |
||||
"name": "ant-design-pro", |
||||
"version": "5.2.0", |
||||
"private": true, |
||||
"description": "An out-of-box UI solution for enterprise applications", |
||||
"scripts": { |
||||
"analyze": "cross-env ANALYZE=1 umi build", |
||||
"build": "umi build", |
||||
"deploy": "npm run build && npm run gh-pages", |
||||
"dev": "npm run start:dev", |
||||
"gh-pages": "gh-pages -d dist", |
||||
"i18n-remove": "pro i18n-remove --locale=zh-CN --write", |
||||
"postinstall": "umi g tmp", |
||||
"lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier && npm run tsc", |
||||
"lint-staged": "lint-staged", |
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", |
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style", |
||||
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", |
||||
"lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto", |
||||
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", |
||||
"openapi": "umi openapi", |
||||
"playwright": "playwright install && playwright test", |
||||
"prepare": "husky install", |
||||
"prettier": "prettier -c --write \"src/**/*\"", |
||||
"serve": "umi-serve", |
||||
"start": "cross-env UMI_ENV=dev umi dev", |
||||
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev", |
||||
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev", |
||||
"start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev", |
||||
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev", |
||||
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev", |
||||
"test": "umi test", |
||||
"test:component": "umi test ./src/components", |
||||
"test:e2e": "node ./tests/run-tests.js", |
||||
"tsc": "tsc --noEmit" |
||||
}, |
||||
"lint-staged": { |
||||
"**/*.less": "stylelint --syntax less", |
||||
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js", |
||||
"**/*.{js,jsx,tsx,ts,less,md,json}": [ |
||||
"prettier --write" |
||||
] |
||||
}, |
||||
"browserslist": [ |
||||
"> 1%", |
||||
"last 2 versions", |
||||
"not ie <= 10" |
||||
], |
||||
"dependencies": { |
||||
"@ant-design/charts": "^1.2.11", |
||||
"@ant-design/icons": "^4.7.0", |
||||
"@ant-design/pro-components": "1.1.1", |
||||
"@ant-design/pro-descriptions": "^1.6.0", |
||||
"@ant-design/pro-form": "^1.16.0", |
||||
"@ant-design/pro-layout": "^6.0.0", |
||||
"@ant-design/pro-table": "^2.30.0", |
||||
"@umijs/route-utils": "^2.0.0", |
||||
"antd": "^4.20.0", |
||||
"classnames": "^2.3.0", |
||||
"lodash": "^4.17.0", |
||||
"moment": "^2.29.0", |
||||
"numeral": "^2.0.6", |
||||
"omit.js": "^2.0.2", |
||||
"rc-menu": "^9.1.0", |
||||
"rc-util": "^5.16.0", |
||||
"react": "^17.0.0", |
||||
"react-dev-inspector": "^1.7.0", |
||||
"react-dom": "^17.0.0", |
||||
"react-helmet-async": "^1.2.0", |
||||
"umi": "^3.5.0" |
||||
}, |
||||
"devDependencies": { |
||||
"@ant-design/pro-cli": "^2.1.0", |
||||
"@playwright/test": "^1.17.0", |
||||
"@types/classnames": "^2.3.1", |
||||
"@types/express": "^4.17.0", |
||||
"@types/history": "^4.7.0", |
||||
"@types/jest": "^26.0.0", |
||||
"@types/lodash": "^4.14.0", |
||||
"@types/react": "^17.0.0", |
||||
"@types/react-dom": "^17.0.0", |
||||
"@types/react-helmet": "^6.1.0", |
||||
"@umijs/fabric": "^2.11.1", |
||||
"@umijs/openapi": "^1.6.0", |
||||
"@umijs/plugin-blocks": "^2.2.0", |
||||
"@umijs/plugin-esbuild": "^1.4.0", |
||||
"@umijs/plugin-openapi": "^1.3.3", |
||||
"@umijs/preset-ant-design-pro": "^1.3.0", |
||||
"@umijs/preset-dumi": "^1.1.0", |
||||
"@umijs/preset-react": "^2.1.0", |
||||
"@umijs/preset-ui": "^2.2.9", |
||||
"cross-env": "^7.0.0", |
||||
"cross-port-killer": "^1.3.0", |
||||
"detect-installer": "^1.0.0", |
||||
"eslint": "^7.32.0", |
||||
"gh-pages": "^3.2.0", |
||||
"husky": "^7.0.4", |
||||
"jsdom-global": "^3.0.0", |
||||
"lint-staged": "^10.0.0", |
||||
"mockjs": "^1.1.0", |
||||
"prettier": "^2.5.0", |
||||
"stylelint": "^13.0.0", |
||||
"swagger-ui-dist": "^4.12.0", |
||||
"typescript": "^4.5.0", |
||||
"umi-serve": "^1.9.10" |
||||
}, |
||||
"engines": { |
||||
"node": ">=12.0.0" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
preview.pro.ant.design |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 677 B |
@ -0,0 +1,9 @@ |
||||
/** |
||||
* @see https://umijs.org/zh-CN/plugins/plugin-access
|
||||
* */ |
||||
export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) { |
||||
const { currentUser } = initialState ?? {}; |
||||
return { |
||||
canAdmin: currentUser && currentUser.access === 'admin', |
||||
}; |
||||
} |
@ -0,0 +1,107 @@ |
||||
import Footer from '@/components/Footer'; |
||||
import RightContent from '@/components/RightContent'; |
||||
import { BookOutlined, LinkOutlined } from '@ant-design/icons'; |
||||
import type { Settings as LayoutSettings } from '@ant-design/pro-components'; |
||||
import { PageLoading, SettingDrawer } from '@ant-design/pro-components'; |
||||
import type { RunTimeLayoutConfig } from 'umi'; |
||||
import { history, Link } from 'umi'; |
||||
import defaultSettings from '../config/defaultSettings'; |
||||
import { currentUser as queryCurrentUser } from './services/ant-design-pro/api'; |
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'; |
||||
const loginPath = '/user/login'; |
||||
|
||||
/** 获取用户信息比较慢的时候会展示一个 loading */ |
||||
export const initialStateConfig = { |
||||
loading: <PageLoading />, |
||||
}; |
||||
|
||||
/** |
||||
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
||||
* */ |
||||
export async function getInitialState(): Promise<{ |
||||
settings?: Partial<LayoutSettings>; |
||||
currentUser?: API.CurrentUser; |
||||
loading?: boolean; |
||||
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; |
||||
}> { |
||||
const fetchUserInfo = async () => { |
||||
try { |
||||
const msg = await queryCurrentUser(); |
||||
return msg.data; |
||||
} catch (error) { |
||||
history.push(loginPath); |
||||
} |
||||
return undefined; |
||||
}; |
||||
// 如果不是登录页面,执行
|
||||
if (history.location.pathname !== loginPath) { |
||||
const currentUser = await fetchUserInfo(); |
||||
return { |
||||
fetchUserInfo, |
||||
currentUser, |
||||
settings: defaultSettings, |
||||
}; |
||||
} |
||||
return { |
||||
fetchUserInfo, |
||||
settings: defaultSettings, |
||||
}; |
||||
} |
||||
|
||||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
||||
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { |
||||
return { |
||||
rightContentRender: () => <RightContent />, |
||||
disableContentMargin: false, |
||||
waterMarkProps: { |
||||
content: initialState?.currentUser?.name, |
||||
}, |
||||
footerRender: () => <Footer />, |
||||
onPageChange: () => { |
||||
const { location } = history; |
||||
// 如果没有登录,重定向到 login
|
||||
if (!initialState?.currentUser && location.pathname !== loginPath) { |
||||
history.push(loginPath); |
||||
} |
||||
}, |
||||
links: isDev |
||||
? [ |
||||
<Link key="openapi" to="/umi/plugin/openapi" target="_blank"> |
||||
<LinkOutlined /> |
||||
<span>OpenAPI 文档</span> |
||||
</Link>, |
||||
<Link to="/~docs" key="docs"> |
||||
<BookOutlined /> |
||||
<span>业务组件文档</span> |
||||
</Link>, |
||||
] |
||||
: [], |
||||
menuHeaderRender: undefined, |
||||
// 自定义 403 页面
|
||||
// unAccessible: <div>unAccessible</div>,
|
||||
// 增加一个 loading 的状态
|
||||
childrenRender: (children, props) => { |
||||
// if (initialState?.loading) return <PageLoading />;
|
||||
return ( |
||||
<> |
||||
{children} |
||||
{!props.location?.pathname?.includes('/login') && ( |
||||
<SettingDrawer |
||||
disableUrlParams |
||||
enableDarkTheme |
||||
settings={initialState?.settings} |
||||
onSettingChange={(settings) => { |
||||
setInitialState((preInitialState) => ({ |
||||
...preInitialState, |
||||
settings, |
||||
})); |
||||
}} |
||||
/> |
||||
)} |
||||
</> |
||||
); |
||||
}, |
||||
...initialState?.settings, |
||||
}; |
||||
}; |
@ -0,0 +1,32 @@ |
||||
import { GithubOutlined } from '@ant-design/icons'; |
||||
import { DefaultFooter } from '@ant-design/pro-components'; |
||||
const Footer: React.FC = () => { |
||||
const defaultMessage = '蚂蚁集团体验技术部出品'; |
||||
const currentYear = new Date().getFullYear(); |
||||
return ( |
||||
<DefaultFooter |
||||
copyright={`${currentYear} ${defaultMessage}`} |
||||
links={[ |
||||
{ |
||||
key: 'Ant Design Pro', |
||||
title: 'Ant Design Pro', |
||||
href: 'https://pro.ant.design', |
||||
blankTarget: true, |
||||
}, |
||||
{ |
||||
key: 'github', |
||||
title: <GithubOutlined />, |
||||
href: 'https://github.com/ant-design/ant-design-pro', |
||||
blankTarget: true, |
||||
}, |
||||
{ |
||||
key: 'Ant Design', |
||||
title: 'Ant Design', |
||||
href: 'https://ant.design', |
||||
blankTarget: true, |
||||
}, |
||||
]} |
||||
/> |
||||
); |
||||
}; |
||||
export default Footer; |
@ -0,0 +1,16 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
.container > * { |
||||
background-color: @popover-bg; |
||||
border-radius: 4px; |
||||
box-shadow: @shadow-1-down; |
||||
} |
||||
|
||||
@media screen and (max-width: @screen-xs) { |
||||
.container { |
||||
width: 100% !important; |
||||
} |
||||
.container > * { |
||||
border-radius: 0 !important; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
import { Dropdown } from 'antd'; |
||||
import type { DropDownProps } from 'antd/es/dropdown'; |
||||
import classNames from 'classnames'; |
||||
import React from 'react'; |
||||
import styles from './index.less'; |
||||
|
||||
export type HeaderDropdownProps = { |
||||
overlayClassName?: string; |
||||
overlay: React.ReactNode | (() => React.ReactNode) | any; |
||||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter'; |
||||
} & Omit<DropDownProps, 'overlay'>; |
||||
|
||||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => ( |
||||
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} /> |
||||
); |
||||
|
||||
export default HeaderDropdown; |
@ -0,0 +1,25 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
.headerSearch { |
||||
display: inline-flex; |
||||
align-items: center; |
||||
.input { |
||||
width: 0; |
||||
min-width: 0; |
||||
overflow: hidden; |
||||
background: transparent; |
||||
border-radius: 0; |
||||
transition: width 0.3s, margin-left 0.3s; |
||||
:global(.ant-select-selection) { |
||||
background: transparent; |
||||
} |
||||
input { |
||||
box-shadow: none !important; |
||||
} |
||||
|
||||
&.show { |
||||
width: 210px; |
||||
margin-left: 8px; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
import { SearchOutlined } from '@ant-design/icons'; |
||||
import type { InputRef } from 'antd'; |
||||
import { AutoComplete, Input } from 'antd'; |
||||
import type { AutoCompleteProps } from 'antd/es/auto-complete'; |
||||
import classNames from 'classnames'; |
||||
import useMergedState from 'rc-util/es/hooks/useMergedState'; |
||||
import React, { useRef } from 'react'; |
||||
import styles from './index.less'; |
||||
|
||||
export type HeaderSearchProps = { |
||||
onSearch?: (value?: string) => void; |
||||
onChange?: (value?: string) => void; |
||||
onVisibleChange?: (b: boolean) => void; |
||||
className?: string; |
||||
placeholder?: string; |
||||
options: AutoCompleteProps['options']; |
||||
defaultVisible?: boolean; |
||||
visible?: boolean; |
||||
defaultValue?: string; |
||||
value?: string; |
||||
}; |
||||
|
||||
const HeaderSearch: React.FC<HeaderSearchProps> = (props) => { |
||||
const { |
||||
className, |
||||
defaultValue, |
||||
onVisibleChange, |
||||
placeholder, |
||||
visible, |
||||
defaultVisible, |
||||
...restProps |
||||
} = props; |
||||
|
||||
const inputRef = useRef<InputRef | null>(null); |
||||
|
||||
const [value, setValue] = useMergedState<string | undefined>(defaultValue, { |
||||
value: props.value, |
||||
onChange: props.onChange, |
||||
}); |
||||
|
||||
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, { |
||||
value: props.visible, |
||||
onChange: onVisibleChange, |
||||
}); |
||||
|
||||
const inputClass = classNames(styles.input, { |
||||
[styles.show]: searchMode, |
||||
}); |
||||
return ( |
||||
<div |
||||
className={classNames(className, styles.headerSearch)} |
||||
onClick={() => { |
||||
setSearchMode(true); |
||||
if (searchMode && inputRef.current) { |
||||
inputRef.current.focus(); |
||||
} |
||||
}} |
||||
onTransitionEnd={({ propertyName }) => { |
||||
if (propertyName === 'width' && !searchMode) { |
||||
if (onVisibleChange) { |
||||
onVisibleChange(searchMode); |
||||
} |
||||
} |
||||
}} |
||||
> |
||||
<SearchOutlined |
||||
key="Icon" |
||||
style={{ |
||||
cursor: 'pointer', |
||||
}} |
||||
/> |
||||
<AutoComplete |
||||
key="AutoComplete" |
||||
className={inputClass} |
||||
value={value} |
||||
options={restProps.options} |
||||
onChange={(completeValue) => setValue(completeValue)} |
||||
> |
||||
<Input |
||||
size="small" |
||||
ref={inputRef} |
||||
defaultValue={defaultValue} |
||||
aria-label={placeholder} |
||||
placeholder={placeholder} |
||||
onKeyDown={(e) => { |
||||
if (e.key === 'Enter') { |
||||
if (restProps.onSearch) { |
||||
restProps.onSearch(value); |
||||
} |
||||
} |
||||
}} |
||||
onBlur={() => { |
||||
setSearchMode(false); |
||||
}} |
||||
/> |
||||
</AutoComplete> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default HeaderSearch; |
@ -0,0 +1,126 @@ |
||||
import { BellOutlined } from '@ant-design/icons'; |
||||
import { Badge, Spin, Tabs } from 'antd'; |
||||
import classNames from 'classnames'; |
||||
import useMergedState from 'rc-util/es/hooks/useMergedState'; |
||||
import React from 'react'; |
||||
import HeaderDropdown from '../HeaderDropdown'; |
||||
import styles from './index.less'; |
||||
import type { NoticeIconTabProps } from './NoticeList'; |
||||
import NoticeList from './NoticeList'; |
||||
|
||||
const { TabPane } = Tabs; |
||||
|
||||
export type NoticeIconProps = { |
||||
count?: number; |
||||
bell?: React.ReactNode; |
||||
className?: string; |
||||
loading?: boolean; |
||||
onClear?: (tabName: string, tabKey: string) => void; |
||||
onItemClick?: (item: API.NoticeIconItem, tabProps: NoticeIconTabProps) => void; |
||||
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void; |
||||
onTabChange?: (tabTile: string) => void; |
||||
style?: React.CSSProperties; |
||||
onPopupVisibleChange?: (visible: boolean) => void; |
||||
popupVisible?: boolean; |
||||
clearText?: string; |
||||
viewMoreText?: string; |
||||
clearClose?: boolean; |
||||
emptyImage?: string; |
||||
children?: React.ReactElement<NoticeIconTabProps>[]; |
||||
}; |
||||
|
||||
const NoticeIcon: React.FC<NoticeIconProps> & { |
||||
Tab: typeof NoticeList; |
||||
} = (props) => { |
||||
const getNotificationBox = (): React.ReactNode => { |
||||
const { |
||||
children, |
||||
loading, |
||||
onClear, |
||||
onTabChange, |
||||
onItemClick, |
||||
onViewMore, |
||||
clearText, |
||||
viewMoreText, |
||||
} = props; |
||||
if (!children) { |
||||
return null; |
||||
} |
||||
const panes: React.ReactNode[] = []; |
||||
React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => { |
||||
if (!child) { |
||||
return; |
||||
} |
||||
const { list, title, count, tabKey, showClear, showViewMore } = child.props; |
||||
const len = list && list.length ? list.length : 0; |
||||
const msgCount = count || count === 0 ? count : len; |
||||
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title; |
||||
panes.push( |
||||
<TabPane tab={tabTitle} key={tabKey}> |
||||
<NoticeList |
||||
clearText={clearText} |
||||
viewMoreText={viewMoreText} |
||||
list={list} |
||||
tabKey={tabKey} |
||||
onClear={(): void => onClear && onClear(title, tabKey)} |
||||
onClick={(item): void => onItemClick && onItemClick(item, child.props)} |
||||
onViewMore={(event): void => onViewMore && onViewMore(child.props, event)} |
||||
showClear={showClear} |
||||
showViewMore={showViewMore} |
||||
title={title} |
||||
/> |
||||
</TabPane>, |
||||
); |
||||
}); |
||||
return ( |
||||
<> |
||||
<Spin spinning={loading} delay={300}> |
||||
<Tabs className={styles.tabs} onChange={onTabChange}> |
||||
{panes} |
||||
</Tabs> |
||||
</Spin> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
const { className, count, bell } = props; |
||||
|
||||
const [visible, setVisible] = useMergedState<boolean>(false, { |
||||
value: props.popupVisible, |
||||
onChange: props.onPopupVisibleChange, |
||||
}); |
||||
const noticeButtonClass = classNames(className, styles.noticeButton); |
||||
const notificationBox = getNotificationBox(); |
||||
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />; |
||||
const trigger = ( |
||||
<span className={classNames(noticeButtonClass, { opened: visible })}> |
||||
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}> |
||||
{NoticeBellIcon} |
||||
</Badge> |
||||
</span> |
||||
); |
||||
if (!notificationBox) { |
||||
return trigger; |
||||
} |
||||
|
||||
return ( |
||||
<HeaderDropdown |
||||
placement="bottomRight" |
||||
overlay={notificationBox} |
||||
overlayClassName={styles.popover} |
||||
trigger={['click']} |
||||
visible={visible} |
||||
onVisibleChange={setVisible} |
||||
> |
||||
{trigger} |
||||
</HeaderDropdown> |
||||
); |
||||
}; |
||||
|
||||
NoticeIcon.defaultProps = { |
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg', |
||||
}; |
||||
|
||||
NoticeIcon.Tab = NoticeList; |
||||
|
||||
export default NoticeIcon; |
@ -0,0 +1,103 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
.list { |
||||
max-height: 400px; |
||||
overflow: auto; |
||||
&::-webkit-scrollbar { |
||||
display: none; |
||||
} |
||||
.item { |
||||
padding-right: 24px; |
||||
padding-left: 24px; |
||||
overflow: hidden; |
||||
cursor: pointer; |
||||
transition: all 0.3s; |
||||
|
||||
.meta { |
||||
width: 100%; |
||||
} |
||||
|
||||
.avatar { |
||||
margin-top: 4px; |
||||
background: @component-background; |
||||
} |
||||
.iconElement { |
||||
font-size: 32px; |
||||
} |
||||
|
||||
&.read { |
||||
opacity: 0.4; |
||||
} |
||||
&:last-child { |
||||
border-bottom: 0; |
||||
} |
||||
&:hover { |
||||
background: @primary-1; |
||||
} |
||||
.title { |
||||
margin-bottom: 8px; |
||||
font-weight: normal; |
||||
} |
||||
.description { |
||||
font-size: 12px; |
||||
line-height: @line-height-base; |
||||
} |
||||
.datetime { |
||||
margin-top: 4px; |
||||
font-size: 12px; |
||||
line-height: @line-height-base; |
||||
} |
||||
.extra { |
||||
float: right; |
||||
margin-top: -1.5px; |
||||
margin-right: 0; |
||||
color: @text-color-secondary; |
||||
font-weight: normal; |
||||
} |
||||
} |
||||
.loadMore { |
||||
padding: 8px 0; |
||||
color: @primary-6; |
||||
text-align: center; |
||||
cursor: pointer; |
||||
&.loadedAll { |
||||
color: rgba(0, 0, 0, 0.25); |
||||
cursor: unset; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.notFound { |
||||
padding: 73px 0 88px; |
||||
color: @text-color-secondary; |
||||
text-align: center; |
||||
img { |
||||
display: inline-block; |
||||
height: 76px; |
||||
margin-bottom: 16px; |
||||
} |
||||
} |
||||
|
||||
.bottomBar { |
||||
height: 46px; |
||||
color: @text-color; |
||||
line-height: 46px; |
||||
text-align: center; |
||||
border-top: 1px solid @border-color-split; |
||||
border-radius: 0 0 @border-radius-base @border-radius-base; |
||||
transition: all 0.3s; |
||||
div { |
||||
display: inline-block; |
||||
width: 50%; |
||||
cursor: pointer; |
||||
transition: all 0.3s; |
||||
user-select: none; |
||||
|
||||
&:only-child { |
||||
width: 100%; |
||||
} |
||||
&:not(:only-child):last-child { |
||||
border-left: 1px solid @border-color-split; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
import { Avatar, List } from 'antd'; |
||||
import classNames from 'classnames'; |
||||
import React from 'react'; |
||||
import styles from './NoticeList.less'; |
||||
|
||||
export type NoticeIconTabProps = { |
||||
count?: number; |
||||
showClear?: boolean; |
||||
showViewMore?: boolean; |
||||
style?: React.CSSProperties; |
||||
title: string; |
||||
tabKey: API.NoticeIconItemType; |
||||
onClick?: (item: API.NoticeIconItem) => void; |
||||
onClear?: () => void; |
||||
emptyText?: string; |
||||
clearText?: string; |
||||
viewMoreText?: string; |
||||
list: API.NoticeIconItem[]; |
||||
onViewMore?: (e: any) => void; |
||||
}; |
||||
const NoticeList: React.FC<NoticeIconTabProps> = ({ |
||||
list = [], |
||||
onClick, |
||||
onClear, |
||||
title, |
||||
onViewMore, |
||||
emptyText, |
||||
showClear = true, |
||||
clearText, |
||||
viewMoreText, |
||||
showViewMore = false, |
||||
}) => { |
||||
if (!list || list.length === 0) { |
||||
return ( |
||||
<div className={styles.notFound}> |
||||
<img |
||||
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" |
||||
alt="not found" |
||||
/> |
||||
<div>{emptyText}</div> |
||||
</div> |
||||
); |
||||
} |
||||
return ( |
||||
<div> |
||||
<List<API.NoticeIconItem> |
||||
className={styles.list} |
||||
dataSource={list} |
||||
renderItem={(item, i) => { |
||||
const itemCls = classNames(styles.item, { |
||||
[styles.read]: item.read, |
||||
}); |
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const leftIcon = item.avatar ? ( |
||||
typeof item.avatar === 'string' ? ( |
||||
<Avatar className={styles.avatar} src={item.avatar} /> |
||||
) : ( |
||||
<span className={styles.iconElement}>{item.avatar}</span> |
||||
) |
||||
) : null; |
||||
|
||||
return ( |
||||
<div |
||||
onClick={() => { |
||||
onClick?.(item); |
||||
}} |
||||
> |
||||
<List.Item className={itemCls} key={item.key || i}> |
||||
<List.Item.Meta |
||||
className={styles.meta} |
||||
avatar={leftIcon} |
||||
title={ |
||||
<div className={styles.title}> |
||||
{item.title} |
||||
<div className={styles.extra}>{item.extra}</div> |
||||
</div> |
||||
} |
||||
description={ |
||||
<div> |
||||
<div className={styles.description}>{item.description}</div> |
||||
<div className={styles.datetime}>{item.datetime}</div> |
||||
</div> |
||||
} |
||||
/> |
||||
</List.Item> |
||||
</div> |
||||
); |
||||
}} |
||||
/> |
||||
<div className={styles.bottomBar}> |
||||
{showClear ? ( |
||||
<div onClick={onClear}> |
||||
{clearText} {title} |
||||
</div> |
||||
) : null} |
||||
{showViewMore ? ( |
||||
<div |
||||
onClick={(e) => { |
||||
if (onViewMore) { |
||||
onViewMore(e); |
||||
} |
||||
}} |
||||
> |
||||
{viewMoreText} |
||||
</div> |
||||
) : null} |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default NoticeList; |
@ -0,0 +1,35 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
.popover { |
||||
position: relative; |
||||
width: 336px; |
||||
} |
||||
|
||||
.noticeButton { |
||||
display: inline-block; |
||||
cursor: pointer; |
||||
transition: all 0.3s; |
||||
} |
||||
.icon { |
||||
padding: 4px; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
.badge { |
||||
font-size: 16px; |
||||
} |
||||
|
||||
.tabs { |
||||
:global { |
||||
.ant-tabs-nav-list { |
||||
margin: auto; |
||||
} |
||||
|
||||
.ant-tabs-nav-scroll { |
||||
text-align: center; |
||||
} |
||||
.ant-tabs-nav { |
||||
margin-bottom: 0; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,152 @@ |
||||
import { getNotices } from '@/services/ant-design-pro/api'; |
||||
import { message, Tag } from 'antd'; |
||||
import { groupBy } from 'lodash'; |
||||
import moment from 'moment'; |
||||
import { useEffect, useState } from 'react'; |
||||
import { useModel, useRequest } from 'umi'; |
||||
import styles from './index.less'; |
||||
import NoticeIcon from './NoticeIcon'; |
||||
|
||||
export type GlobalHeaderRightProps = { |
||||
fetchingNotices?: boolean; |
||||
onNoticeVisibleChange?: (visible: boolean) => void; |
||||
onNoticeClear?: (tabName?: string) => void; |
||||
}; |
||||
|
||||
const getNoticeData = (notices: API.NoticeIconItem[]): Record<string, API.NoticeIconItem[]> => { |
||||
if (!notices || notices.length === 0 || !Array.isArray(notices)) { |
||||
return {}; |
||||
} |
||||
|
||||
const newNotices = notices.map((notice) => { |
||||
const newNotice = { ...notice }; |
||||
|
||||
if (newNotice.datetime) { |
||||
newNotice.datetime = moment(notice.datetime as string).fromNow(); |
||||
} |
||||
|
||||
if (newNotice.id) { |
||||
newNotice.key = newNotice.id; |
||||
} |
||||
|
||||
if (newNotice.extra && newNotice.status) { |
||||
const color = { |
||||
todo: '', |
||||
processing: 'blue', |
||||
urgent: 'red', |
||||
doing: 'gold', |
||||
}[newNotice.status]; |
||||
newNotice.extra = ( |
||||
<Tag |
||||
color={color} |
||||
style={{ |
||||
marginRight: 0, |
||||
}} |
||||
> |
||||
{newNotice.extra} |
||||
</Tag> |
||||
) as any; |
||||
} |
||||
|
||||
return newNotice; |
||||
}); |
||||
return groupBy(newNotices, 'type'); |
||||
}; |
||||
|
||||
const getUnreadData = (noticeData: Record<string, API.NoticeIconItem[]>) => { |
||||
const unreadMsg: Record<string, number> = {}; |
||||
Object.keys(noticeData).forEach((key) => { |
||||
const value = noticeData[key]; |
||||
|
||||
if (!unreadMsg[key]) { |
||||
unreadMsg[key] = 0; |
||||
} |
||||
|
||||
if (Array.isArray(value)) { |
||||
unreadMsg[key] = value.filter((item) => !item.read).length; |
||||
} |
||||
}); |
||||
return unreadMsg; |
||||
}; |
||||
|
||||
const NoticeIconView: React.FC = () => { |
||||
const { initialState } = useModel('@@initialState'); |
||||
const { currentUser } = initialState || {}; |
||||
const [notices, setNotices] = useState<API.NoticeIconItem[]>([]); |
||||
const { data } = useRequest(getNotices); |
||||
|
||||
useEffect(() => { |
||||
setNotices(data || []); |
||||
}, [data]); |
||||
|
||||
const noticeData = getNoticeData(notices); |
||||
const unreadMsg = getUnreadData(noticeData || {}); |
||||
|
||||
const changeReadState = (id: string) => { |
||||
setNotices( |
||||
notices.map((item) => { |
||||
const notice = { ...item }; |
||||
if (notice.id === id) { |
||||
notice.read = true; |
||||
} |
||||
return notice; |
||||
}), |
||||
); |
||||
}; |
||||
|
||||
const clearReadState = (title: string, key: string) => { |
||||
setNotices( |
||||
notices.map((item) => { |
||||
const notice = { ...item }; |
||||
if (notice.type === key) { |
||||
notice.read = true; |
||||
} |
||||
return notice; |
||||
}), |
||||
); |
||||
message.success(`${'清空了'} ${title}`); |
||||
}; |
||||
|
||||
return ( |
||||
<NoticeIcon |
||||
className={styles.action} |
||||
count={currentUser && currentUser.unreadCount} |
||||
onItemClick={(item) => { |
||||
changeReadState(item.id!); |
||||
}} |
||||
onClear={(title: string, key: string) => clearReadState(title, key)} |
||||
loading={false} |
||||
clearText="清空" |
||||
viewMoreText="查看更多" |
||||
onViewMore={() => message.info('Click on view more')} |
||||
clearClose |
||||
> |
||||
<NoticeIcon.Tab |
||||
tabKey="notification" |
||||
count={unreadMsg.notification} |
||||
list={noticeData.notification} |
||||
title="通知" |
||||
emptyText="你已查看所有通知" |
||||
showViewMore |
||||
/> |
||||
<NoticeIcon.Tab |
||||
tabKey="message" |
||||
count={unreadMsg.message} |
||||
list={noticeData.message} |
||||
title="消息" |
||||
emptyText="您已读完所有消息" |
||||
showViewMore |
||||
/> |
||||
<NoticeIcon.Tab |
||||
tabKey="event" |
||||
title="待办" |
||||
emptyText="你已完成所有待办" |
||||
count={unreadMsg.event} |
||||
list={noticeData.event} |
||||
showViewMore |
||||
/> |
||||
</NoticeIcon> |
||||
); |
||||
}; |
||||
|
||||
export default NoticeIconView; |
@ -0,0 +1,111 @@ |
||||
import { outLogin } from '@/services/ant-design-pro/api'; |
||||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; |
||||
import { Avatar, Menu, Spin } from 'antd'; |
||||
import type { ItemType } from 'antd/lib/menu/hooks/useItems'; |
||||
import { stringify } from 'querystring'; |
||||
import type { MenuInfo } from 'rc-menu/lib/interface'; |
||||
import React, { useCallback } from 'react'; |
||||
import { history, useModel } from 'umi'; |
||||
import HeaderDropdown from '../HeaderDropdown'; |
||||
import styles from './index.less'; |
||||
|
||||
export type GlobalHeaderRightProps = { |
||||
menu?: boolean; |
||||
}; |
||||
|
||||
/** |
||||
* 退出登录,并且将当前的 url 保存 |
||||
*/ |
||||
const loginOut = async () => { |
||||
await outLogin(); |
||||
const { query = {}, search, pathname } = history.location; |
||||
const { redirect } = query; |
||||
// Note: There may be security issues, please note
|
||||
if (window.location.pathname !== '/user/login' && !redirect) { |
||||
history.replace({ |
||||
pathname: '/user/login', |
||||
search: stringify({ |
||||
redirect: pathname + search, |
||||
}), |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { |
||||
const { initialState, setInitialState } = useModel('@@initialState'); |
||||
|
||||
const onMenuClick = useCallback( |
||||
(event: MenuInfo) => { |
||||
const { key } = event; |
||||
if (key === 'logout') { |
||||
setInitialState((s) => ({ ...s, currentUser: undefined })); |
||||
loginOut(); |
||||
return; |
||||
} |
||||
history.push(`/account/${key}`); |
||||
}, |
||||
[setInitialState], |
||||
); |
||||
|
||||
const loading = ( |
||||
<span className={`${styles.action} ${styles.account}`}> |
||||
<Spin |
||||
size="small" |
||||
style={{ |
||||
marginLeft: 8, |
||||
marginRight: 8, |
||||
}} |
||||
/> |
||||
</span> |
||||
); |
||||
|
||||
if (!initialState) { |
||||
return loading; |
||||
} |
||||
|
||||
const { currentUser } = initialState; |
||||
|
||||
if (!currentUser || !currentUser.name) { |
||||
return loading; |
||||
} |
||||
|
||||
const menuItems: ItemType[] = [ |
||||
...(menu |
||||
? [ |
||||
{ |
||||
key: 'center', |
||||
icon: <UserOutlined />, |
||||
label: '个人中心', |
||||
}, |
||||
{ |
||||
key: 'settings', |
||||
icon: <SettingOutlined />, |
||||
label: '个人设置', |
||||
}, |
||||
{ |
||||
type: 'divider' as const, |
||||
}, |
||||
] |
||||
: []), |
||||
{ |
||||
key: 'logout', |
||||
icon: <LogoutOutlined />, |
||||
label: '退出登录', |
||||
}, |
||||
]; |
||||
|
||||
const menuHeaderDropdown = ( |
||||
<Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick} items={menuItems} /> |
||||
); |
||||
|
||||
return ( |
||||
<HeaderDropdown overlay={menuHeaderDropdown}> |
||||
<span className={`${styles.action} ${styles.account}`}> |
||||
<Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" /> |
||||
<span className={`${styles.name} anticon`}>{currentUser.name}</span> |
||||
</span> |
||||
</HeaderDropdown> |
||||
); |
||||
}; |
||||
|
||||
export default AvatarDropdown; |
@ -0,0 +1,84 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
@pro-header-hover-bg: rgba(0, 0, 0, 0.025); |
||||
|
||||
.menu { |
||||
:global(.anticon) { |
||||
margin-right: 8px; |
||||
} |
||||
:global(.ant-dropdown-menu-item) { |
||||
min-width: 160px; |
||||
} |
||||
} |
||||
|
||||
.right { |
||||
display: flex; |
||||
float: right; |
||||
height: 48px; |
||||
margin-left: auto; |
||||
overflow: hidden; |
||||
.action { |
||||
display: flex; |
||||
align-items: center; |
||||
height: 48px; |
||||
padding: 0 12px; |
||||
cursor: pointer; |
||||
transition: all 0.3s; |
||||
> span { |
||||
vertical-align: middle; |
||||
} |
||||
&:hover { |
||||
background: @pro-header-hover-bg; |
||||
} |
||||
&:global(.opened) { |
||||
background: @pro-header-hover-bg; |
||||
} |
||||
} |
||||
.search { |
||||
padding: 0 12px; |
||||
&:hover { |
||||
background: transparent; |
||||
} |
||||
} |
||||
.account { |
||||
.avatar { |
||||
margin-right: 8px; |
||||
color: @primary-color; |
||||
vertical-align: top; |
||||
background: rgba(255, 255, 255, 0.85); |
||||
} |
||||
} |
||||
} |
||||
|
||||
.dark { |
||||
.action { |
||||
&:hover { |
||||
background: #252a3d; |
||||
} |
||||
&:global(.opened) { |
||||
background: #252a3d; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@media only screen and (max-width: @screen-md) { |
||||
:global(.ant-divider-vertical) { |
||||
vertical-align: unset; |
||||
} |
||||
.name { |
||||
display: none; |
||||
} |
||||
.right { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 12px; |
||||
.account { |
||||
.avatar { |
||||
margin-right: 0; |
||||
} |
||||
} |
||||
.search { |
||||
display: none; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
import { QuestionCircleOutlined } from '@ant-design/icons'; |
||||
import { Space } from 'antd'; |
||||
import React from 'react'; |
||||
import { useModel } from 'umi'; |
||||
import HeaderSearch from '../HeaderSearch'; |
||||
import Avatar from './AvatarDropdown'; |
||||
import styles from './index.less'; |
||||
export type SiderTheme = 'light' | 'dark'; |
||||
const GlobalHeaderRight: React.FC = () => { |
||||
const { initialState } = useModel('@@initialState'); |
||||
if (!initialState || !initialState.settings) { |
||||
return null; |
||||
} |
||||
const { navTheme, layout } = initialState.settings; |
||||
let className = styles.right; |
||||
if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') { |
||||
className = `${styles.right} ${styles.dark}`; |
||||
} |
||||
return ( |
||||
<Space className={className}> |
||||
<HeaderSearch |
||||
className={`${styles.action} ${styles.search}`} |
||||
placeholder="站内搜索" |
||||
defaultValue="umi ui" |
||||
options={[ |
||||
{ |
||||
label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, |
||||
value: 'umi ui', |
||||
}, |
||||
{ |
||||
label: <a href="next.ant.design">Ant Design</a>, |
||||
value: 'Ant Design', |
||||
}, |
||||
{ |
||||
label: <a href="https://protable.ant.design/">Pro Table</a>, |
||||
value: 'Pro Table', |
||||
}, |
||||
{ |
||||
label: <a href="https://prolayout.ant.design/">Pro Layout</a>, |
||||
value: 'Pro Layout', |
||||
}, |
||||
]} |
||||
// onSearch={value => {
|
||||
// console.log('input', value);
|
||||
// }}
|
||||
/> |
||||
<span |
||||
className={styles.action} |
||||
onClick={() => { |
||||
window.open('https://pro.ant.design/docs/getting-started'); |
||||
}} |
||||
> |
||||
<QuestionCircleOutlined /> |
||||
</span> |
||||
<Avatar /> |
||||
</Space> |
||||
); |
||||
}; |
||||
export default GlobalHeaderRight; |
@ -0,0 +1,57 @@ |
||||
@import '~antd/es/style/variable.less'; |
||||
|
||||
html, |
||||
body, |
||||
#root { |
||||
height: 100%; |
||||
} |
||||
|
||||
.colorWeak { |
||||
filter: invert(80%); |
||||
} |
||||
|
||||
.ant-layout { |
||||
min-height: 100vh; |
||||
} |
||||
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed { |
||||
left: unset; |
||||
} |
||||
|
||||
canvas { |
||||
display: block; |
||||
} |
||||
|
||||
body { |
||||
text-rendering: optimizeLegibility; |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
} |
||||
|
||||
ul, |
||||
ol { |
||||
list-style: none; |
||||
} |
||||
|
||||
@media (max-width: @screen-xs) { |
||||
.ant-table { |
||||
width: 100%; |
||||
overflow-x: auto; |
||||
&-thead > tr, |
||||
&-tbody > tr { |
||||
> th, |
||||
> td { |
||||
white-space: pre; |
||||
> span { |
||||
display: block; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Compatible with IE11 |
||||
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { |
||||
body .ant-design-pro > .ant-layout { |
||||
min-height: 100vh; |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
import { Button, message, notification } from 'antd'; |
||||
import defaultSettings from '../config/defaultSettings'; |
||||
const { pwa } = defaultSettings; |
||||
const isHttps = document.location.protocol === 'https:'; |
||||
const clearCache = () => { |
||||
// remove all caches
|
||||
if (window.caches) { |
||||
caches |
||||
.keys() |
||||
.then((keys) => { |
||||
keys.forEach((key) => { |
||||
caches.delete(key); |
||||
}); |
||||
}) |
||||
.catch((e) => console.log(e)); |
||||
} |
||||
}; |
||||
|
||||
// if pwa is true
|
||||
if (pwa) { |
||||
// Notify user if offline now
|
||||
window.addEventListener('sw.offline', () => { |
||||
message.warning('当前处于离线状态'); |
||||
}); |
||||
|
||||
// Pop up a prompt on the page asking the user if they want to use the latest version
|
||||
window.addEventListener('sw.updated', (event: Event) => { |
||||
const e = event as CustomEvent; |
||||
const reloadSW = async () => { |
||||
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
|
||||
const worker = e.detail && e.detail.waiting; |
||||
if (!worker) { |
||||
return true; |
||||
} |
||||
// Send skip-waiting event to waiting SW with MessageChannel
|
||||
await new Promise((resolve, reject) => { |
||||
const channel = new MessageChannel(); |
||||
channel.port1.onmessage = (msgEvent) => { |
||||
if (msgEvent.data.error) { |
||||
reject(msgEvent.data.error); |
||||
} else { |
||||
resolve(msgEvent.data); |
||||
} |
||||
}; |
||||
worker.postMessage( |
||||
{ |
||||
type: 'skip-waiting', |
||||
}, |
||||
[channel.port2], |
||||
); |
||||
}); |
||||
clearCache(); |
||||
window.location.reload(); |
||||
return true; |
||||
}; |
||||
const key = `open${Date.now()}`; |
||||
const btn = ( |
||||
<Button |
||||
type="primary" |
||||
onClick={() => { |
||||
notification.close(key); |
||||
reloadSW(); |
||||
}} |
||||
> |
||||
{'刷新'} |
||||
</Button> |
||||
); |
||||
notification.open({ |
||||
message: '有新内容', |
||||
description: '请点击“刷新”按钮或者手动刷新页面', |
||||
btn, |
||||
key, |
||||
onClose: async () => null, |
||||
}); |
||||
}); |
||||
} else if ('serviceWorker' in navigator && isHttps) { |
||||
// unregister service worker
|
||||
const { serviceWorker } = navigator; |
||||
if (serviceWorker.getRegistrations) { |
||||
serviceWorker.getRegistrations().then((sws) => { |
||||
sws.forEach((sw) => { |
||||
sw.unregister(); |
||||
}); |
||||
}); |
||||
} |
||||
serviceWorker.getRegistration().then((sw) => { |
||||
if (sw) sw.unregister(); |
||||
}); |
||||
clearCache(); |
||||
} |
@ -0,0 +1,22 @@ |
||||
{ |
||||
"name": "Ant Design Pro", |
||||
"short_name": "Ant Design Pro", |
||||
"display": "standalone", |
||||
"start_url": "./?utm_source=homescreen", |
||||
"theme_color": "#002140", |
||||
"background_color": "#001529", |
||||
"icons": [ |
||||
{ |
||||
"src": "icons/icon-192x192.png", |
||||
"sizes": "192x192" |
||||
}, |
||||
{ |
||||
"src": "icons/icon-128x128.png", |
||||
"sizes": "128x128" |
||||
}, |
||||
{ |
||||
"src": "icons/icon-512x512.png", |
||||
"sizes": "512x512" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,18 @@ |
||||
import { Button, Result } from 'antd'; |
||||
import React from 'react'; |
||||
import { history } from 'umi'; |
||||
|
||||
const NoFoundPage: React.FC = () => ( |
||||
<Result |
||||
status="404" |
||||
title="404" |
||||
subTitle="Sorry, the page you visited does not exist." |
||||
extra={ |
||||
<Button type="primary" onClick={() => history.push('/')}> |
||||
Back Home |
||||
</Button> |
||||
} |
||||
/> |
||||
); |
||||
|
||||
export default NoFoundPage; |
@ -0,0 +1,43 @@ |
||||
import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons'; |
||||
import { PageHeaderWrapper } from '@ant-design/pro-components'; |
||||
import { Alert, Card, Typography } from 'antd'; |
||||
import React from 'react'; |
||||
const Admin: React.FC = () => { |
||||
return ( |
||||
<PageHeaderWrapper content={' 这个页面只有 admin 权限才能查看'}> |
||||
<Card> |
||||
<Alert |
||||
message={'更快更强的重型组件,已经发布。'} |
||||
type="success" |
||||
showIcon |
||||
banner |
||||
style={{ |
||||
margin: -12, |
||||
marginBottom: 48, |
||||
}} |
||||
/> |
||||
<Typography.Title |
||||
level={2} |
||||
style={{ |
||||
textAlign: 'center', |
||||
}} |
||||
> |
||||
<SmileTwoTone /> Ant Design Pro <HeartTwoTone twoToneColor="#eb2f96" /> You |
||||
</Typography.Title> |
||||
</Card> |
||||
<p |
||||
style={{ |
||||
textAlign: 'center', |
||||
marginTop: 24, |
||||
}} |
||||
> |
||||
Want to add more pages? Please refer to{' '} |
||||
<a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer"> |
||||
use block |
||||
</a> |
||||
。 |
||||
</p> |
||||
</PageHeaderWrapper> |
||||
); |
||||
}; |
||||
export default Admin; |
@ -0,0 +1,410 @@ |
||||
import moment from 'moment'; |
||||
import type { Request, Response } from 'express'; |
||||
import type { SearchDataType, OfflineDataType, DataItem } from './data.d'; |
||||
|
||||
// mock data
|
||||
const visitData: DataItem[] = []; |
||||
const beginDay = new Date().getTime(); |
||||
|
||||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]; |
||||
for (let i = 0; i < fakeY.length; i += 1) { |
||||
visitData.push({ |
||||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), |
||||
y: fakeY[i], |
||||
}); |
||||
} |
||||
|
||||
const visitData2: DataItem[] = []; |
||||
const fakeY2 = [1, 6, 4, 8, 3, 7, 2]; |
||||
for (let i = 0; i < fakeY2.length; i += 1) { |
||||
visitData2.push({ |
||||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), |
||||
y: fakeY2[i], |
||||
}); |
||||
} |
||||
|
||||
const salesData: DataItem[] = []; |
||||
for (let i = 0; i < 12; i += 1) { |
||||
salesData.push({ |
||||
x: `${i + 1}月`, |
||||
y: Math.floor(Math.random() * 1000) + 200, |
||||
}); |
||||
} |
||||
const searchData: SearchDataType[] = []; |
||||
for (let i = 0; i < 50; i += 1) { |
||||
searchData.push({ |
||||
index: i + 1, |
||||
keyword: `搜索关键词-${i}`, |
||||
count: Math.floor(Math.random() * 1000), |
||||
range: Math.floor(Math.random() * 100), |
||||
status: Math.floor((Math.random() * 10) % 2), |
||||
}); |
||||
} |
||||
const salesTypeData = [ |
||||
{ |
||||
x: '家用电器', |
||||
y: 4544, |
||||
}, |
||||
{ |
||||
x: '食用酒水', |
||||
y: 3321, |
||||
}, |
||||
{ |
||||
x: '个护健康', |
||||
y: 3113, |
||||
}, |
||||
{ |
||||
x: '服饰箱包', |
||||
y: 2341, |
||||
}, |
||||
{ |
||||
x: '母婴产品', |
||||
y: 1231, |
||||
}, |
||||
{ |
||||
x: '其他', |
||||
y: 1231, |
||||
}, |
||||
]; |
||||
|
||||
const salesTypeDataOnline = [ |
||||
{ |
||||
x: '家用电器', |
||||
y: 244, |
||||
}, |
||||
{ |
||||
x: '食用酒水', |
||||
y: 321, |
||||
}, |
||||
{ |
||||
x: '个护健康', |
||||
y: 311, |
||||
}, |
||||
{ |
||||
x: '服饰箱包', |
||||
y: 41, |
||||
}, |
||||
{ |
||||
x: '母婴产品', |
||||
y: 121, |
||||
}, |
||||
{ |
||||
x: '其他', |
||||
y: 111, |
||||
}, |
||||
]; |
||||
|
||||
const salesTypeDataOffline = [ |
||||
{ |
||||
x: '家用电器', |
||||
y: 99, |
||||
}, |
||||
{ |
||||
x: '食用酒水', |
||||
y: 188, |
||||
}, |
||||
{ |
||||
x: '个护健康', |
||||
y: 344, |
||||
}, |
||||
{ |
||||
x: '服饰箱包', |
||||
y: 255, |
||||
}, |
||||
{ |
||||
x: '其他', |
||||
y: 65, |
||||
}, |
||||
]; |
||||
|
||||
const offlineData: OfflineDataType[] = []; |
||||
for (let i = 0; i < 10; i += 1) { |
||||
offlineData.push({ |
||||
name: `Stores ${i}`, |
||||
cvr: Math.ceil(Math.random() * 9) / 10, |
||||
}); |
||||
} |
||||
const offlineChartData: DataItem[] = []; |
||||
for (let i = 0; i < 20; i += 1) { |
||||
offlineChartData.push({ |
||||
x: new Date().getTime() + 1000 * 60 * 30 * i, |
||||
y1: Math.floor(Math.random() * 100) + 10, |
||||
y2: Math.floor(Math.random() * 100) + 10, |
||||
}); |
||||
} |
||||
|
||||
const titles = [ |
||||
'Alipay', |
||||
'Angular', |
||||
'Ant Design', |
||||
'Ant Design Pro', |
||||
'Bootstrap', |
||||
'React', |
||||
'Vue', |
||||
'Webpack', |
||||
]; |
||||
const avatars = [ |
||||
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
|
||||
]; |
||||
|
||||
const avatars2 = [ |
||||
'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/psOgztMplJMGpVEqfcgF.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/ZpBqSxLxVEXfcUNoPKrz.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/laiEnJdGHVOhJrUShBaJ.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/UrQsqscbKEpNuJcvBZBu.png', |
||||
]; |
||||
|
||||
const getNotice = (_: Request, res: Response) => { |
||||
res.json({ |
||||
data: [ |
||||
{ |
||||
id: 'xxx1', |
||||
title: titles[0], |
||||
logo: avatars[0], |
||||
description: '那是一种内在的东西,他们到达不了,也无法触及的', |
||||
updatedAt: new Date(), |
||||
member: '科学搬砖组', |
||||
href: '', |
||||
memberLink: '', |
||||
}, |
||||
{ |
||||
id: 'xxx2', |
||||
title: titles[1], |
||||
logo: avatars[1], |
||||
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的', |
||||
updatedAt: new Date('2017-07-24'), |
||||
member: '全组都是吴彦祖', |
||||
href: '', |
||||
memberLink: '', |
||||
}, |
||||
{ |
||||
id: 'xxx3', |
||||
title: titles[2], |
||||
logo: avatars[2], |
||||
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', |
||||
updatedAt: new Date(), |
||||
member: '中二少女团', |
||||
href: '', |
||||
memberLink: '', |
||||
}, |
||||
{ |
||||
id: 'xxx4', |
||||
title: titles[3], |
||||
logo: avatars[3], |
||||
description: '那时候我只会想自己想要什么,从不想自己拥有什么', |
||||
updatedAt: new Date('2017-07-23'), |
||||
member: '程序员日常', |
||||
href: '', |
||||
memberLink: '', |
||||
}, |
||||
{ |
||||
id: 'xxx5', |
||||
title: titles[4], |
||||
logo: avatars[4], |
||||
description: '凛冬将至', |
||||
updatedAt: new Date('2017-07-23'), |
||||
member: '高逼格设计天团', |
||||
href: '', |
||||
memberLink: '', |
||||
}, |
||||
{ |
||||
id: 'xxx6', |
||||
title: titles[5], |
||||
logo: avatars[5], |
||||
description: '生命就像一盒巧克力,结果往往出人意料', |
||||
updatedAt: new Date('2017-07-23'), |
||||
member: '骗你来学计算机', |
||||
href: '', |
||||
memberLink: '', |
||||
}, |
||||
], |
||||
}); |
||||
}; |
||||
|
||||
const getActivities = (_: Request, res: Response) => { |
||||
res.json({ |
||||
data: [ |
||||
{ |
||||
id: 'trend-1', |
||||
updatedAt: new Date(), |
||||
user: { |
||||
name: '曲丽丽', |
||||
avatar: avatars2[0], |
||||
}, |
||||
group: { |
||||
name: '高逼格设计天团', |
||||
link: 'http://github.com/', |
||||
}, |
||||
project: { |
||||
name: '六月迭代', |
||||
link: 'http://github.com/', |
||||
}, |
||||
template: '在 @{group} 新建项目 @{project}', |
||||
}, |
||||
{ |
||||
id: 'trend-2', |
||||
updatedAt: new Date(), |
||||
user: { |
||||
name: '付小小', |
||||
avatar: avatars2[1], |
||||
}, |
||||
group: { |
||||
name: '高逼格设计天团', |
||||
link: 'http://github.com/', |
||||
}, |
||||
project: { |
||||
name: '六月迭代', |
||||
link: 'http://github.com/', |
||||
}, |
||||
template: '在 @{group} 新建项目 @{project}', |
||||
}, |
||||
{ |
||||
id: 'trend-3', |
||||
updatedAt: new Date(), |
||||
user: { |
||||
name: '林东东', |
||||
avatar: avatars2[2], |
||||
}, |
||||
group: { |
||||
name: '中二少女团', |
||||
link: 'http://github.com/', |
||||
}, |
||||
project: { |
||||
name: '六月迭代', |
||||
link: 'http://github.com/', |
||||
}, |
||||
template: '在 @{group} 新建项目 @{project}', |
||||
}, |
||||
{ |
||||
id: 'trend-4', |
||||
updatedAt: new Date(), |
||||
user: { |
||||
name: '周星星', |
||||
avatar: avatars2[4], |
||||
}, |
||||
project: { |
||||
name: '5 月日常迭代', |
||||
link: 'http://github.com/', |
||||
}, |
||||
template: '将 @{project} 更新至已发布状态', |
||||
}, |
||||
{ |
||||
id: 'trend-5', |
||||
updatedAt: new Date(), |
||||
user: { |
||||
name: '朱偏右', |
||||
avatar: avatars2[3], |
||||
}, |
||||
project: { |
||||
name: '工程效能', |
||||
link: 'http://github.com/', |
||||
}, |
||||
comment: { |
||||
name: '留言', |
||||
link: 'http://github.com/', |
||||
}, |
||||
template: '在 @{project} 发布了 @{comment}', |
||||
}, |
||||
{ |
||||
id: 'trend-6', |
||||
updatedAt: new Date(), |
||||
user: { |
||||
name: '乐哥', |
||||
avatar: avatars2[5], |
||||
}, |
||||
group: { |
||||
name: '程序员日常', |
||||
link: 'http://github.com/', |
||||
}, |
||||
project: { |
||||
name: '品牌迭代', |
||||
link: 'http://github.com/', |
||||
}, |
||||
template: '在 @{group} 新建项目 @{project}', |
||||
}, |
||||
], |
||||
}); |
||||
}; |
||||
|
||||
const radarOriginData = [ |
||||
{ |
||||
name: '个人', |
||||
ref: 10, |
||||
koubei: 8, |
||||
output: 4, |
||||
contribute: 5, |
||||
hot: 7, |
||||
}, |
||||
{ |
||||
name: '团队', |
||||
ref: 3, |
||||
koubei: 9, |
||||
output: 6, |
||||
contribute: 3, |
||||
hot: 1, |
||||
}, |
||||
{ |
||||
name: '部门', |
||||
ref: 4, |
||||
koubei: 1, |
||||
output: 6, |
||||
contribute: 5, |
||||
hot: 7, |
||||
}, |
||||
]; |
||||
|
||||
const radarData: any[] = []; |
||||
const radarTitleMap = { |
||||
ref: '引用', |
||||
koubei: '口碑', |
||||
output: '产量', |
||||
contribute: '贡献', |
||||
hot: '热度', |
||||
}; |
||||
radarOriginData.forEach((item) => { |
||||
Object.keys(item).forEach((key) => { |
||||
if (key !== 'name') { |
||||
radarData.push({ |
||||
name: item.name, |
||||
label: radarTitleMap[key], |
||||
value: item[key], |
||||
}); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
const getChartData = (_: Request, res: Response) => { |
||||
res.json({ |
||||
data: { |
||||
visitData, |
||||
visitData2, |
||||
salesData, |
||||
searchData, |
||||
offlineData, |
||||
offlineChartData, |
||||
salesTypeData, |
||||
salesTypeDataOnline, |
||||
salesTypeDataOffline, |
||||
radarData, |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
export default { |
||||
'GET /api/project/notice': getNotice, |
||||
'GET /api/activities': getActivities, |
||||
'GET /api/fake_workplace_chart_data': getChartData, |
||||
}; |
@ -0,0 +1,16 @@ |
||||
@import '~antd/es/style/themes/default.less'; |
||||
|
||||
.linkGroup { |
||||
padding: 20px 0 8px 24px; |
||||
font-size: 0; |
||||
& > a { |
||||
display: inline-block; |
||||
width: 25%; |
||||
margin-bottom: 13px; |
||||
color: @text-color; |
||||
font-size: @font-size-base; |
||||
&:hover { |
||||
color: @primary-color; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
import React, { createElement } from 'react'; |
||||
import { PlusOutlined } from '@ant-design/icons'; |
||||
import { Button } from 'antd'; |
||||
|
||||
import styles from './index.less'; |
||||
|
||||
export type EditableLink = { |
||||
title: string; |
||||
href: string; |
||||
id?: string; |
||||
}; |
||||
|
||||
type EditableLinkGroupProps = { |
||||
onAdd: () => void; |
||||
links: EditableLink[]; |
||||
linkElement: any; |
||||
}; |
||||
|
||||
const EditableLinkGroup: React.FC<EditableLinkGroupProps> = (props) => { |
||||
const { links, linkElement, onAdd } = props; |
||||
return ( |
||||
<div className={styles.linkGroup}> |
||||
{links.map((link) => |
||||
createElement( |
||||
linkElement, |
||||
{ |
||||
key: `linkGroup-item-${link.id || link.title}`, |
||||
to: link.href, |
||||
href: link.href, |
||||
}, |
||||
link.title, |
||||
), |
||||
)} |
||||
<Button size="small" type="primary" ghost onClick={onAdd}> |
||||
<PlusOutlined /> 添加 |
||||
</Button> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
EditableLinkGroup.defaultProps = { |
||||
links: [], |
||||
onAdd: () => {}, |
||||
linkElement: 'a', |
||||
}; |
||||
|
||||
export default EditableLinkGroup; |
@ -0,0 +1,79 @@ |
||||
import React from 'react'; |
||||
|
||||
export type IReactComponent<P = any> = |
||||
| React.StatelessComponent<P> |
||||
| React.ComponentClass<P> |
||||
| React.ClassicComponentClass<P>; |
||||
|
||||
function computeHeight(node: HTMLDivElement) { |
||||
const { style } = node; |
||||
style.height = '100%'; |
||||
const totalHeight = parseInt(`${getComputedStyle(node).height}`, 10); |
||||
const padding = |
||||
parseInt(`${getComputedStyle(node).paddingTop}`, 10) + |
||||
parseInt(`${getComputedStyle(node).paddingBottom}`, 10); |
||||
return totalHeight - padding; |
||||
} |
||||
|
||||
function getAutoHeight(n: HTMLDivElement | undefined) { |
||||
if (!n) { |
||||
return 0; |
||||
} |
||||
|
||||
const node = n; |
||||
|
||||
let height = computeHeight(node); |
||||
const parentNode = node.parentNode as HTMLDivElement; |
||||
if (parentNode) { |
||||
height = computeHeight(parentNode); |
||||
} |
||||
|
||||
return height; |
||||
} |
||||
|
||||
type AutoHeightProps = { |
||||
height?: number; |
||||
}; |
||||
|
||||
function autoHeight() { |
||||
return <P extends AutoHeightProps>( |
||||
WrappedComponent: React.ComponentClass<P> | React.FC<P>, |
||||
): React.ComponentClass<P> => { |
||||
class AutoHeightComponent extends React.Component<P & AutoHeightProps> { |
||||
state = { |
||||
computedHeight: 0, |
||||
}; |
||||
|
||||
root: HTMLDivElement | undefined = undefined; |
||||
|
||||
componentDidMount() { |
||||
const { height } = this.props; |
||||
if (!height) { |
||||
let h = getAutoHeight(this.root); |
||||
this.setState({ computedHeight: h }); |
||||
if (h < 1) { |
||||
h = getAutoHeight(this.root); |
||||
this.setState({ computedHeight: h }); |
||||
} |
||||
} |
||||
} |
||||
|
||||
handleRoot = (node: HTMLDivElement) => { |
||||
this.root = node; |
||||
}; |
||||
|
||||
render() { |
||||
const { height } = this.props; |
||||
const { computedHeight } = this.state; |
||||
const h = height || computedHeight; |
||||
return ( |
||||
<div ref={this.handleRoot}> |
||||
{h > 0 && <WrappedComponent {...this.props} height={h} />} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
return AutoHeightComponent; |
||||
}; |
||||
} |
||||
export default autoHeight; |
@ -0,0 +1,219 @@ |
||||
import { Axis, Chart, Coord, Geom, Tooltip } from 'bizcharts'; |
||||
import { Col, Row } from 'antd'; |
||||
import React, { Component } from 'react'; |
||||
|
||||
import autoHeight from './autoHeight'; |
||||
import styles from './index.less'; |
||||
|
||||
export type RadarProps = { |
||||
title?: React.ReactNode; |
||||
height?: number; |
||||
padding?: [number, number, number, number]; |
||||
hasLegend?: boolean; |
||||
data: { |
||||
name: string; |
||||
label: string; |
||||
value: string | number; |
||||
}[]; |
||||
colors?: string[]; |
||||
animate?: boolean; |
||||
forceFit?: boolean; |
||||
tickCount?: number; |
||||
style?: React.CSSProperties; |
||||
}; |
||||
type RadarState = { |
||||
legendData: { |
||||
checked: boolean; |
||||
name: string; |
||||
color: string; |
||||
percent: number; |
||||
value: string; |
||||
}[]; |
||||
}; |
||||
/* eslint react/no-danger:0 */ |
||||
class Radar extends Component<RadarProps, RadarState> { |
||||
state: RadarState = { |
||||
legendData: [], |
||||
}; |
||||
|
||||
chart: G2.Chart | undefined = undefined; |
||||
|
||||
node: HTMLDivElement | undefined = undefined; |
||||
|
||||
componentDidMount() { |
||||
this.getLegendData(); |
||||
} |
||||
|
||||
componentDidUpdate(preProps: RadarProps) { |
||||
const { data } = this.props; |
||||
if (data !== preProps.data) { |
||||
this.getLegendData(); |
||||
} |
||||
} |
||||
|
||||
getG2Instance = (chart: G2.Chart) => { |
||||
this.chart = chart; |
||||
}; |
||||
|
||||
// for custom lengend view
|
||||
getLegendData = () => { |
||||
if (!this.chart) return; |
||||
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
|
||||
if (!geom) return; |
||||
const items = (geom as any).get('dataArray') || []; // 获取图形对应的
|
||||
|
||||
const legendData = items.map((item: { color: any; _origin: any }[]) => { |
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const origins = item.map((t) => t._origin); |
||||
const result = { |
||||
name: origins[0].name, |
||||
color: item[0].color, |
||||
checked: true, |
||||
value: origins.reduce((p, n) => p + n.value, 0), |
||||
}; |
||||
|
||||
return result; |
||||
}); |
||||
|
||||
this.setState({ |
||||
legendData, |
||||
}); |
||||
}; |
||||
|
||||
handleRef = (n: HTMLDivElement) => { |
||||
this.node = n; |
||||
}; |
||||
|
||||
handleLegendClick = ( |
||||
item: { |
||||
checked: boolean; |
||||
name: string; |
||||
}, |
||||
i: string | number, |
||||
) => { |
||||
const newItem = item; |
||||
newItem.checked = !newItem.checked; |
||||
|
||||
const { legendData } = this.state; |
||||
legendData[i] = newItem; |
||||
|
||||
const filteredLegendData = legendData.filter((l) => l.checked).map((l) => l.name); |
||||
|
||||
if (this.chart) { |
||||
this.chart.filter('name', (val) => filteredLegendData.indexOf(`${val}`) > -1); |
||||
this.chart.repaint(); |
||||
} |
||||
|
||||
this.setState({ |
||||
legendData, |
||||
}); |
||||
}; |
||||
|
||||
render() { |
||||
const defaultColors = [ |
||||
'#1890FF', |
||||
'#FACC14', |
||||
'#2FC25B', |
||||
'#8543E0', |
||||
'#F04864', |
||||
'#13C2C2', |
||||
'#fa8c16', |
||||
'#a0d911', |
||||
]; |
||||
|
||||
const { |
||||
data = [], |
||||
height = 0, |
||||
title, |
||||
hasLegend = false, |
||||
forceFit = true, |
||||
tickCount = 5, |
||||
padding = [35, 30, 16, 30] as [number, number, number, number], |
||||
animate = true, |
||||
colors = defaultColors, |
||||
} = this.props; |
||||
|
||||
const { legendData } = this.state; |
||||
|
||||
const scale = { |
||||
value: { |
||||
min: 0, |
||||
tickCount, |
||||
}, |
||||
}; |
||||
|
||||
const chartHeight = height - (hasLegend ? 80 : 22); |
||||
|
||||
return ( |
||||
<div className={styles.radar} style={{ height }}> |
||||
{title && <h4>{title}</h4>} |
||||
<Chart |
||||
scale={scale} |
||||
height={chartHeight} |
||||
forceFit={forceFit} |
||||
data={data} |
||||
padding={padding} |
||||
animate={animate} |
||||
onGetG2Instance={this.getG2Instance} |
||||
> |
||||
<Tooltip /> |
||||
<Coord type="polar" /> |
||||
<Axis |
||||
name="label" |
||||
line={undefined} |
||||
tickLine={undefined} |
||||
grid={{ |
||||
lineStyle: { |
||||
lineDash: undefined, |
||||
}, |
||||
hideFirstLine: false, |
||||
}} |
||||
/> |
||||
<Axis |
||||
name="value" |
||||
grid={{ |
||||
type: 'polygon', |
||||
lineStyle: { |
||||
lineDash: undefined, |
||||
}, |
||||
}} |
||||
/> |
||||
<Geom type="line" position="label*value" color={['name', colors]} size={1} /> |
||||
<Geom |
||||
type="point" |
||||
position="label*value" |
||||
color={['name', colors]} |
||||
shape="circle" |
||||
size={3} |
||||
/> |
||||
</Chart> |
||||
{hasLegend && ( |
||||
<Row className={styles.legend}> |
||||
{legendData.map((item, i) => ( |
||||
<Col |
||||
span={24 / legendData.length} |
||||
key={item.name} |
||||
onClick={() => this.handleLegendClick(item, i)} |
||||
> |
||||
<div className={styles.legendItem}> |
||||
<p> |
||||
<span |
||||
className={styles.dot} |
||||
style={{ |
||||
backgroundColor: !item.checked ? '#aaa' : item.color, |
||||
}} |
||||
/> |
||||
<span>{item.name}</span> |
||||
</p> |
||||
<h6>{item.value}</h6> |
||||
</div> |
||||
</Col> |
||||
))} |
||||
</Row> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default autoHeight()(Radar); |
@ -0,0 +1,111 @@ |
||||
import { DataItem } from '@antv/g2plot/esm/interface/config'; |
||||
|
||||
export { DataItem }; |
||||
|
||||
export interface TagType { |
||||
key: string; |
||||
label: string; |
||||
} |
||||
|
||||
export type SearchDataType = { |
||||
index: number; |
||||
keyword: string; |
||||
count: number; |
||||
range: number; |
||||
status: number; |
||||
}; |
||||
|
||||
export type OfflineDataType = { |
||||
name: string; |
||||
cvr: number; |
||||
}; |
||||
|
||||
export interface RadarData { |
||||
name: string; |
||||
label: string; |
||||
value: number; |
||||
} |
||||
|
||||
export type AnalysisData = { |
||||
visitData: VisitDataType[]; |
||||
visitData2: VisitDataType[]; |
||||
salesData: VisitDataType[]; |
||||
searchData: SearchDataType[]; |
||||
offlineData: OfflineDataType[]; |
||||
offlineChartData: OfflineChartData[]; |
||||
salesTypeData: VisitDataType[]; |
||||
salesTypeDataOnline: VisitDataType[]; |
||||
salesTypeDataOffline: VisitDataType[]; |
||||
radarData: DataItem[]; |
||||
}; |
||||
|
||||
export type GeographicType = { |
||||
province: { |
||||
label: string; |
||||
key: string; |
||||
}; |
||||
city: { |
||||
label: string; |
||||
key: string; |
||||
}; |
||||
}; |
||||
|
||||
export type NoticeType = { |
||||
id: string; |
||||
title: string; |
||||
logo: string; |
||||
description: string; |
||||
updatedAt: string; |
||||
member: string; |
||||
href: string; |
||||
memberLink: string; |
||||
}; |
||||
|
||||
export type CurrentUser = { |
||||
name: string; |
||||
avatar: string; |
||||
userid: string; |
||||
notice: NoticeType[]; |
||||
email: string; |
||||
signature: string; |
||||
title: string; |
||||
group: string; |
||||
tags: TagType[]; |
||||
notifyCount: number; |
||||
unreadCount: number; |
||||
country: string; |
||||
geographic: GeographicType; |
||||
address: string; |
||||
phone: string; |
||||
}; |
||||
|
||||
export type Member = { |
||||
avatar: string; |
||||
name: string; |
||||
id: string; |
||||
}; |
||||
|
||||
export type ActivitiesType = { |
||||
id: string; |
||||
updatedAt: string; |
||||
user: { |
||||
name: string; |
||||
avatar: string; |
||||
}; |
||||
group: { |
||||
name: string; |
||||
link: string; |
||||
}; |
||||
project: { |
||||
name: string; |
||||
link: string; |
||||
}; |
||||
|
||||
template: string; |
||||
}; |
||||
|
||||
export type RadarDataType = { |
||||
label: string; |
||||
name: string; |
||||
value: number; |
||||
}; |
@ -0,0 +1,237 @@ |
||||
import type { FC } from 'react'; |
||||
import { Avatar, Card, Col, List, Skeleton, Row, Statistic } from 'antd'; |
||||
import { Radar } from '@ant-design/charts'; |
||||
|
||||
import { Link, useRequest } from 'umi'; |
||||
import { PageContainer } from '@ant-design/pro-layout'; |
||||
import moment from 'moment'; |
||||
import EditableLinkGroup from './components/EditableLinkGroup'; |
||||
import styles from './style.less'; |
||||
import type { ActivitiesType, CurrentUser } from './data.d'; |
||||
import { queryProjectNotice, queryActivities, fakeChartData } from './service'; |
||||
|
||||
const links = [ |
||||
{ |
||||
title: '操作一', |
||||
href: '', |
||||
}, |
||||
{ |
||||
title: '操作二', |
||||
href: '', |
||||
}, |
||||
{ |
||||
title: '操作三', |
||||
href: '', |
||||
}, |
||||
{ |
||||
title: '操作四', |
||||
href: '', |
||||
}, |
||||
{ |
||||
title: '操作五', |
||||
href: '', |
||||
}, |
||||
{ |
||||
title: '操作六', |
||||
href: '', |
||||
}, |
||||
]; |
||||
|
||||
const PageHeaderContent: FC<{ currentUser: Partial<CurrentUser> }> = ({ currentUser }) => { |
||||
const loading = currentUser && Object.keys(currentUser).length; |
||||
if (!loading) { |
||||
return <Skeleton avatar paragraph={{ rows: 1 }} active />; |
||||
} |
||||
return ( |
||||
<div className={styles.pageHeaderContent}> |
||||
<div className={styles.avatar}> |
||||
<Avatar size="large" src={currentUser.avatar} /> |
||||
</div> |
||||
<div className={styles.content}> |
||||
<div className={styles.contentTitle}> |
||||
早安, |
||||
{currentUser.name} |
||||
,祝你开心每一天! |
||||
</div> |
||||
<div> |
||||
{currentUser.title} |{currentUser.group} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
const ExtraContent: FC<Record<string, any>> = () => ( |
||||
<div className={styles.extraContent}> |
||||
<div className={styles.statItem}> |
||||
<Statistic title="项目数" value={56} /> |
||||
</div> |
||||
<div className={styles.statItem}> |
||||
<Statistic title="团队内排名" value={8} suffix="/ 24" /> |
||||
</div> |
||||
<div className={styles.statItem}> |
||||
<Statistic title="项目访问" value={2223} /> |
||||
</div> |
||||
</div> |
||||
); |
||||
|
||||
const DashboardWorkplace: FC = () => { |
||||
const { loading: projectLoading, data: projectNotice = [] } = useRequest(queryProjectNotice); |
||||
const { loading: activitiesLoading, data: activities = [] } = useRequest(queryActivities); |
||||
const { data } = useRequest(fakeChartData); |
||||
|
||||
const renderActivities = (item: ActivitiesType) => { |
||||
const events = item.template.split(/@\{([^{}]*)\}/gi).map((key) => { |
||||
if (item[key]) { |
||||
return ( |
||||
<a href={item[key].link} key={item[key].name}> |
||||
{item[key].name} |
||||
</a> |
||||
); |
||||
} |
||||
return key; |
||||
}); |
||||
return ( |
||||
<List.Item key={item.id}> |
||||
<List.Item.Meta |
||||
avatar={<Avatar src={item.user.avatar} />} |
||||
title={ |
||||
<span> |
||||
<a className={styles.username}>{item.user.name}</a> |
||||
|
||||
<span className={styles.event}>{events}</span> |
||||
</span> |
||||
} |
||||
description={ |
||||
<span className={styles.datetime} title={item.updatedAt}> |
||||
{moment(item.updatedAt).fromNow()} |
||||
</span> |
||||
} |
||||
/> |
||||
</List.Item> |
||||
); |
||||
}; |
||||
|
||||
return ( |
||||
<PageContainer |
||||
content={ |
||||
<PageHeaderContent |
||||
currentUser={{ |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', |
||||
name: '吴彦祖', |
||||
userid: '00000001', |
||||
email: 'antdesign@alipay.com', |
||||
signature: '海纳百川,有容乃大', |
||||
title: '交互专家', |
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', |
||||
}} |
||||
/> |
||||
} |
||||
extraContent={<ExtraContent />} |
||||
> |
||||
<Row gutter={24}> |
||||
<Col xl={16} lg={24} md={24} sm={24} xs={24}> |
||||
<Card |
||||
className={styles.projectList} |
||||
style={{ marginBottom: 24 }} |
||||
title="进行中的项目" |
||||
bordered={false} |
||||
extra={<Link to="/">全部项目</Link>} |
||||
loading={projectLoading} |
||||
bodyStyle={{ padding: 0 }} |
||||
> |
||||
{projectNotice.map((item) => ( |
||||
<Card.Grid className={styles.projectGrid} key={item.id}> |
||||
<Card bodyStyle={{ padding: 0 }} bordered={false}> |
||||
<Card.Meta |
||||
title={ |
||||
<div className={styles.cardTitle}> |
||||
<Avatar size="small" src={item.logo} /> |
||||
<Link to={item.href}>{item.title}</Link> |
||||
</div> |
||||
} |
||||
description={item.description} |
||||
/> |
||||
<div className={styles.projectItemContent}> |
||||
<Link to={item.memberLink}>{item.member || ''}</Link> |
||||
{item.updatedAt && ( |
||||
<span className={styles.datetime} title={item.updatedAt}> |
||||
{moment(item.updatedAt).fromNow()} |
||||
</span> |
||||
)} |
||||
</div> |
||||
</Card> |
||||
</Card.Grid> |
||||
))} |
||||
</Card> |
||||
<Card |
||||
bodyStyle={{ padding: 0 }} |
||||
bordered={false} |
||||
className={styles.activeCard} |
||||
title="动态" |
||||
loading={activitiesLoading} |
||||
> |
||||
<List<ActivitiesType> |
||||
loading={activitiesLoading} |
||||
renderItem={(item) => renderActivities(item)} |
||||
dataSource={activities} |
||||
className={styles.activitiesList} |
||||
size="large" |
||||
/> |
||||
</Card> |
||||
</Col> |
||||
<Col xl={8} lg={24} md={24} sm={24} xs={24}> |
||||
<Card |
||||
style={{ marginBottom: 24 }} |
||||
title="快速开始 / 便捷导航" |
||||
bordered={false} |
||||
bodyStyle={{ padding: 0 }} |
||||
> |
||||
<EditableLinkGroup onAdd={() => {}} links={links} linkElement={Link} /> |
||||
</Card> |
||||
<Card |
||||
style={{ marginBottom: 24 }} |
||||
bordered={false} |
||||
title="XX 指数" |
||||
loading={data?.radarData?.length === 0} |
||||
> |
||||
<div className={styles.chart}> |
||||
<Radar |
||||
height={343} |
||||
data={data?.radarData || []} |
||||
seriesField="name" |
||||
xField="label" |
||||
yField="value" |
||||
point={{}} |
||||
legend={{ |
||||
position: 'bottom', |
||||
}} |
||||
/> |
||||
</div> |
||||
</Card> |
||||
<Card |
||||
bodyStyle={{ paddingTop: 12, paddingBottom: 12 }} |
||||
bordered={false} |
||||
title="团队" |
||||
loading={projectLoading} |
||||
> |
||||
<div className={styles.members}> |
||||
<Row gutter={48}> |
||||
{projectNotice.map((item) => ( |
||||
<Col span={12} key={`members-item-${item.id}`}> |
||||
<Link to={item.href}> |
||||
<Avatar src={item.logo} size="small" /> |
||||
<span className={styles.member}>{item.member}</span> |
||||
</Link> |
||||
</Col> |
||||
))} |
||||
</Row> |
||||
</div> |
||||
</Card> |
||||
</Col> |
||||
</Row> |
||||
</PageContainer> |
||||
); |
||||
}; |
||||
|
||||
export default DashboardWorkplace; |
@ -0,0 +1,14 @@ |
||||
import { request } from 'umi'; |
||||
import type { NoticeType, ActivitiesType, AnalysisData } from './data'; |
||||
|
||||
export async function queryProjectNotice(): Promise<{ data: NoticeType[] }> { |
||||
return request('/api/project/notice'); |
||||
} |
||||
|
||||
export async function queryActivities(): Promise<{ data: ActivitiesType[] }> { |
||||
return request('/api/activities'); |
||||
} |
||||
|
||||
export async function fakeChartData(): Promise<{ data: AnalysisData }> { |
||||
return request('/api/fake_workplace_chart_data'); |
||||
} |
@ -0,0 +1,250 @@ |
||||
@import '~antd/es/style/themes/default.less'; |
||||
|
||||
.textOverflow() { |
||||
overflow: hidden; |
||||
white-space: nowrap; |
||||
text-overflow: ellipsis; |
||||
word-break: break-all; |
||||
} |
||||
|
||||
// mixins for clearfix |
||||
// ------------------------ |
||||
.clearfix() { |
||||
zoom: 1; |
||||
&::before, |
||||
&::after { |
||||
display: table; |
||||
content: ' '; |
||||
} |
||||
&::after { |
||||
clear: both; |
||||
height: 0; |
||||
font-size: 0; |
||||
visibility: hidden; |
||||
} |
||||
} |
||||
|
||||
.activitiesList { |
||||
padding: 0 24px 8px 24px; |
||||
.username { |
||||
color: @text-color; |
||||
} |
||||
.event { |
||||
font-weight: normal; |
||||
} |
||||
} |
||||
|
||||
.pageHeaderContent { |
||||
display: flex; |
||||
.avatar { |
||||
flex: 0 1 72px; |
||||
& > span { |
||||
display: block; |
||||
width: 72px; |
||||
height: 72px; |
||||
border-radius: 72px; |
||||
} |
||||
} |
||||
.content { |
||||
position: relative; |
||||
top: 4px; |
||||
flex: 1 1 auto; |
||||
margin-left: 24px; |
||||
color: @text-color-secondary; |
||||
line-height: 22px; |
||||
.contentTitle { |
||||
margin-bottom: 12px; |
||||
color: @heading-color; |
||||
font-weight: 500; |
||||
font-size: 20px; |
||||
line-height: 28px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.extraContent { |
||||
.clearfix(); |
||||
|
||||
float: right; |
||||
white-space: nowrap; |
||||
.statItem { |
||||
position: relative; |
||||
display: inline-block; |
||||
padding: 0 32px; |
||||
> p:first-child { |
||||
margin-bottom: 4px; |
||||
color: @text-color-secondary; |
||||
font-size: @font-size-base; |
||||
line-height: 22px; |
||||
} |
||||
> p { |
||||
margin: 0; |
||||
color: @heading-color; |
||||
font-size: 30px; |
||||
line-height: 38px; |
||||
> span { |
||||
color: @text-color-secondary; |
||||
font-size: 20px; |
||||
} |
||||
} |
||||
&::after { |
||||
position: absolute; |
||||
top: 8px; |
||||
right: 0; |
||||
width: 1px; |
||||
height: 40px; |
||||
background-color: @border-color-split; |
||||
content: ''; |
||||
} |
||||
&:last-child { |
||||
padding-right: 0; |
||||
&::after { |
||||
display: none; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.members { |
||||
a { |
||||
display: block; |
||||
height: 24px; |
||||
margin: 12px 0; |
||||
color: @text-color; |
||||
transition: all 0.3s; |
||||
.textOverflow(); |
||||
.member { |
||||
margin-left: 12px; |
||||
font-size: @font-size-base; |
||||
line-height: 24px; |
||||
vertical-align: top; |
||||
} |
||||
&:hover { |
||||
color: @primary-color; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.projectList { |
||||
:global { |
||||
.ant-card-meta-description { |
||||
height: 44px; |
||||
overflow: hidden; |
||||
color: @text-color-secondary; |
||||
line-height: 22px; |
||||
} |
||||
} |
||||
.cardTitle { |
||||
font-size: 0; |
||||
a { |
||||
display: inline-block; |
||||
height: 24px; |
||||
margin-left: 12px; |
||||
color: @heading-color; |
||||
font-size: @font-size-base; |
||||
line-height: 24px; |
||||
vertical-align: top; |
||||
&:hover { |
||||
color: @primary-color; |
||||
} |
||||
} |
||||
} |
||||
.projectGrid { |
||||
width: 33.33%; |
||||
} |
||||
.projectItemContent { |
||||
display: flex; |
||||
height: 20px; |
||||
margin-top: 8px; |
||||
overflow: hidden; |
||||
font-size: 12px; |
||||
line-height: 20px; |
||||
.textOverflow(); |
||||
a { |
||||
display: inline-block; |
||||
flex: 1 1 0; |
||||
color: @text-color-secondary; |
||||
.textOverflow(); |
||||
&:hover { |
||||
color: @primary-color; |
||||
} |
||||
} |
||||
.datetime { |
||||
flex: 0 0 auto; |
||||
float: right; |
||||
color: @disabled-color; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.datetime { |
||||
color: @disabled-color; |
||||
} |
||||
|
||||
@media screen and (max-width: @screen-xl) and (min-width: @screen-lg) { |
||||
.activeCard { |
||||
margin-bottom: 24px; |
||||
} |
||||
.members { |
||||
margin-bottom: 0; |
||||
} |
||||
.extraContent { |
||||
margin-left: -44px; |
||||
.statItem { |
||||
padding: 0 16px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@media screen and (max-width: @screen-lg) { |
||||
.activeCard { |
||||
margin-bottom: 24px; |
||||
} |
||||
.members { |
||||
margin-bottom: 0; |
||||
} |
||||
.extraContent { |
||||
float: none; |
||||
margin-right: 0; |
||||
.statItem { |
||||
padding: 0 16px; |
||||
text-align: left; |
||||
&::after { |
||||
display: none; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@media screen and (max-width: @screen-md) { |
||||
.extraContent { |
||||
margin-left: -16px; |
||||
} |
||||
.projectList { |
||||
.projectGrid { |
||||
width: 50%; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@media screen and (max-width: @screen-sm) { |
||||
.pageHeaderContent { |
||||
display: block; |
||||
.content { |
||||
margin-left: 0; |
||||
} |
||||
} |
||||
.extraContent { |
||||
.statItem { |
||||
float: none; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@media screen and (max-width: @screen-xs) { |
||||
.projectList { |
||||
.projectGrid { |
||||
width: 100%; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,154 @@ |
||||
import { |
||||
ProFormDateTimePicker, |
||||
ProFormRadio, |
||||
ProFormSelect, |
||||
ProFormText, |
||||
ProFormTextArea, |
||||
StepsForm, |
||||
} from '@ant-design/pro-components'; |
||||
import { Modal } from 'antd'; |
||||
import React from 'react'; |
||||
export type FormValueType = { |
||||
target?: string; |
||||
template?: string; |
||||
type?: string; |
||||
time?: string; |
||||
frequency?: string; |
||||
} & Partial<API.RuleListItem>; |
||||
export type UpdateFormProps = { |
||||
onCancel: (flag?: boolean, formVals?: FormValueType) => void; |
||||
onSubmit: (values: FormValueType) => Promise<void>; |
||||
updateModalVisible: boolean; |
||||
values: Partial<API.RuleListItem>; |
||||
}; |
||||
const UpdateForm: React.FC<UpdateFormProps> = (props) => { |
||||
return ( |
||||
<StepsForm |
||||
stepsProps={{ |
||||
size: 'small', |
||||
}} |
||||
stepsFormRender={(dom, submitter) => { |
||||
return ( |
||||
<Modal |
||||
width={640} |
||||
bodyStyle={{ |
||||
padding: '32px 40px 48px', |
||||
}} |
||||
destroyOnClose |
||||
title={'规则配置'} |
||||
visible={props.updateModalVisible} |
||||
footer={submitter} |
||||
onCancel={() => { |
||||
props.onCancel(); |
||||
}} |
||||
> |
||||
{dom} |
||||
</Modal> |
||||
); |
||||
}} |
||||
onFinish={props.onSubmit} |
||||
> |
||||
<StepsForm.StepForm |
||||
initialValues={{ |
||||
name: props.values.name, |
||||
desc: props.values.desc, |
||||
}} |
||||
title={'基本信息'} |
||||
> |
||||
<ProFormText |
||||
name="name" |
||||
label={'规则名称'} |
||||
width="md" |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '请输入规则名称!', |
||||
}, |
||||
]} |
||||
/> |
||||
<ProFormTextArea |
||||
name="desc" |
||||
width="md" |
||||
label={'规则描述'} |
||||
placeholder={'请输入至少五个字符'} |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '请输入至少五个字符的规则描述!', |
||||
min: 5, |
||||
}, |
||||
]} |
||||
/> |
||||
</StepsForm.StepForm> |
||||
<StepsForm.StepForm |
||||
initialValues={{ |
||||
target: '0', |
||||
template: '0', |
||||
}} |
||||
title={'配置规则属性'} |
||||
> |
||||
<ProFormSelect |
||||
name="target" |
||||
width="md" |
||||
label={'监控对象'} |
||||
valueEnum={{ |
||||
0: '表一', |
||||
1: '表二', |
||||
}} |
||||
/> |
||||
<ProFormSelect |
||||
name="template" |
||||
width="md" |
||||
label={'规则模板'} |
||||
valueEnum={{ |
||||
0: '规则模板一', |
||||
1: '规则模板二', |
||||
}} |
||||
/> |
||||
<ProFormRadio.Group |
||||
name="type" |
||||
label={'规则类型'} |
||||
options={[ |
||||
{ |
||||
value: '0', |
||||
label: '强', |
||||
}, |
||||
{ |
||||
value: '1', |
||||
label: '弱', |
||||
}, |
||||
]} |
||||
/> |
||||
</StepsForm.StepForm> |
||||
<StepsForm.StepForm |
||||
initialValues={{ |
||||
type: '1', |
||||
frequency: 'month', |
||||
}} |
||||
title={'设定调度周期'} |
||||
> |
||||
<ProFormDateTimePicker |
||||
name="time" |
||||
width="md" |
||||
label={'开始时间'} |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '请选择开始时间!', |
||||
}, |
||||
]} |
||||
/> |
||||
<ProFormSelect |
||||
name="frequency" |
||||
label={'监控对象'} |
||||
width="md" |
||||
valueEnum={{ |
||||
month: '月', |
||||
week: '周', |
||||
}} |
||||
/> |
||||
</StepsForm.StepForm> |
||||
</StepsForm> |
||||
); |
||||
}; |
||||
export default UpdateForm; |
@ -0,0 +1,328 @@ |
||||
import { addRule, removeRule, rule, updateRule } from '@/services/ant-design-pro/api'; |
||||
import { PlusOutlined } from '@ant-design/icons'; |
||||
import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components'; |
||||
import { |
||||
FooterToolbar, |
||||
ModalForm, |
||||
PageContainer, |
||||
ProDescriptions, |
||||
ProFormText, |
||||
ProFormTextArea, |
||||
ProTable, |
||||
} from '@ant-design/pro-components'; |
||||
import { Button, Drawer, Input, message } from 'antd'; |
||||
import React, { useRef, useState } from 'react'; |
||||
import type { FormValueType } from './components/UpdateForm'; |
||||
import UpdateForm from './components/UpdateForm'; |
||||
|
||||
/** |
||||
* @en-US Add node |
||||
* @zh-CN 添加节点 |
||||
* @param fields |
||||
*/ |
||||
const handleAdd = async (fields: API.RuleListItem) => { |
||||
const hide = message.loading('正在添加'); |
||||
try { |
||||
await addRule({ |
||||
...fields, |
||||
}); |
||||
hide(); |
||||
message.success('Added successfully'); |
||||
return true; |
||||
} catch (error) { |
||||
hide(); |
||||
message.error('Adding failed, please try again!'); |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* @en-US Update node |
||||
* @zh-CN 更新节点 |
||||
* |
||||
* @param fields |
||||
*/ |
||||
const handleUpdate = async (fields: FormValueType) => { |
||||
const hide = message.loading('Configuring'); |
||||
try { |
||||
await updateRule({ |
||||
name: fields.name, |
||||
desc: fields.desc, |
||||
key: fields.key, |
||||
}); |
||||
hide(); |
||||
message.success('Configuration is successful'); |
||||
return true; |
||||
} catch (error) { |
||||
hide(); |
||||
message.error('Configuration failed, please try again!'); |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Delete node |
||||
* @zh-CN 删除节点 |
||||
* |
||||
* @param selectedRows |
||||
*/ |
||||
const handleRemove = async (selectedRows: API.RuleListItem[]) => { |
||||
const hide = message.loading('正在删除'); |
||||
if (!selectedRows) return true; |
||||
try { |
||||
await removeRule({ |
||||
key: selectedRows.map((row) => row.key), |
||||
}); |
||||
hide(); |
||||
message.success('Deleted successfully and will refresh soon'); |
||||
return true; |
||||
} catch (error) { |
||||
hide(); |
||||
message.error('Delete failed, please try again'); |
||||
return false; |
||||
} |
||||
}; |
||||
const TableList: React.FC = () => { |
||||
/** |
||||
* @en-US Pop-up window of new window |
||||
* @zh-CN 新建窗口的弹窗 |
||||
* */ |
||||
const [createModalVisible, handleModalVisible] = useState<boolean>(false); |
||||
/** |
||||
* @en-US The pop-up window of the distribution update window |
||||
* @zh-CN 分布更新窗口的弹窗 |
||||
* */ |
||||
const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false); |
||||
const [showDetail, setShowDetail] = useState<boolean>(false); |
||||
const actionRef = useRef<ActionType>(); |
||||
const [currentRow, setCurrentRow] = useState<API.RuleListItem>(); |
||||
const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]); |
||||
|
||||
/** |
||||
* @en-US International configuration |
||||
* @zh-CN 国际化配置 |
||||
* */ |
||||
|
||||
const columns: ProColumns<API.RuleListItem>[] = [ |
||||
{ |
||||
title: '规则名称', |
||||
dataIndex: 'name', |
||||
tip: 'The rule name is the unique key', |
||||
render: (dom, entity) => { |
||||
return ( |
||||
<a |
||||
onClick={() => { |
||||
setCurrentRow(entity); |
||||
setShowDetail(true); |
||||
}} |
||||
> |
||||
{dom} |
||||
</a> |
||||
); |
||||
}, |
||||
}, |
||||
{ |
||||
title: '描述', |
||||
dataIndex: 'desc', |
||||
valueType: 'textarea', |
||||
}, |
||||
{ |
||||
title: '服务调用次数', |
||||
dataIndex: 'callNo', |
||||
sorter: true, |
||||
hideInForm: true, |
||||
renderText: (val: string) => `${val}${'万'}`, |
||||
}, |
||||
{ |
||||
title: '状态', |
||||
dataIndex: 'status', |
||||
hideInForm: true, |
||||
valueEnum: { |
||||
0: { |
||||
text: '关闭', |
||||
status: 'Default', |
||||
}, |
||||
1: { |
||||
text: '运行中', |
||||
status: 'Processing', |
||||
}, |
||||
2: { |
||||
text: '已上线', |
||||
status: 'Success', |
||||
}, |
||||
3: { |
||||
text: '异常', |
||||
status: 'Error', |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
title: '上次调度时间', |
||||
sorter: true, |
||||
dataIndex: 'updatedAt', |
||||
valueType: 'dateTime', |
||||
renderFormItem: (item, { defaultRender, ...rest }, form) => { |
||||
const status = form.getFieldValue('status'); |
||||
if (`${status}` === '0') { |
||||
return false; |
||||
} |
||||
if (`${status}` === '3') { |
||||
return <Input {...rest} placeholder={'请输入异常原因!'} />; |
||||
} |
||||
return defaultRender(item); |
||||
}, |
||||
}, |
||||
{ |
||||
title: '操作', |
||||
dataIndex: 'option', |
||||
valueType: 'option', |
||||
render: (_, record) => [ |
||||
<a |
||||
key="config" |
||||
onClick={() => { |
||||
handleUpdateModalVisible(true); |
||||
setCurrentRow(record); |
||||
}} |
||||
> |
||||
配置 |
||||
</a>, |
||||
<a key="subscribeAlert" href="https://procomponents.ant.design/"> |
||||
订阅警报 |
||||
</a>, |
||||
], |
||||
}, |
||||
]; |
||||
return ( |
||||
<PageContainer> |
||||
<ProTable<API.RuleListItem, API.PageParams> |
||||
headerTitle={'查询表格'} |
||||
actionRef={actionRef} |
||||
rowKey="key" |
||||
search={{ |
||||
labelWidth: 120, |
||||
}} |
||||
toolBarRender={() => [ |
||||
<Button |
||||
type="primary" |
||||
key="primary" |
||||
onClick={() => { |
||||
handleModalVisible(true); |
||||
}} |
||||
> |
||||
<PlusOutlined /> 新建 |
||||
</Button>, |
||||
]} |
||||
request={rule} |
||||
columns={columns} |
||||
rowSelection={{ |
||||
onChange: (_, selectedRows) => { |
||||
setSelectedRows(selectedRows); |
||||
}, |
||||
}} |
||||
/> |
||||
{selectedRowsState?.length > 0 && ( |
||||
<FooterToolbar |
||||
extra={ |
||||
<div> |
||||
已选择{' '} |
||||
<a |
||||
style={{ |
||||
fontWeight: 600, |
||||
}} |
||||
> |
||||
{selectedRowsState.length} |
||||
</a>{' '} |
||||
项 |
||||
<span> |
||||
服务调用次数总计 {selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)} 万 |
||||
</span> |
||||
</div> |
||||
} |
||||
> |
||||
<Button |
||||
onClick={async () => { |
||||
await handleRemove(selectedRowsState); |
||||
setSelectedRows([]); |
||||
actionRef.current?.reloadAndRest?.(); |
||||
}} |
||||
> |
||||
批量删除 |
||||
</Button> |
||||
<Button type="primary">批量审批</Button> |
||||
</FooterToolbar> |
||||
)} |
||||
<ModalForm |
||||
title={'新建规则'} |
||||
width="400px" |
||||
visible={createModalVisible} |
||||
onVisibleChange={handleModalVisible} |
||||
onFinish={async (value) => { |
||||
const success = await handleAdd(value as API.RuleListItem); |
||||
if (success) { |
||||
handleModalVisible(false); |
||||
if (actionRef.current) { |
||||
actionRef.current.reload(); |
||||
} |
||||
} |
||||
}} |
||||
> |
||||
<ProFormText |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '规则名称为必填项', |
||||
}, |
||||
]} |
||||
width="md" |
||||
name="name" |
||||
/> |
||||
<ProFormTextArea width="md" name="desc" /> |
||||
</ModalForm> |
||||
<UpdateForm |
||||
onSubmit={async (value) => { |
||||
const success = await handleUpdate(value); |
||||
if (success) { |
||||
handleUpdateModalVisible(false); |
||||
setCurrentRow(undefined); |
||||
if (actionRef.current) { |
||||
actionRef.current.reload(); |
||||
} |
||||
} |
||||
}} |
||||
onCancel={() => { |
||||
handleUpdateModalVisible(false); |
||||
if (!showDetail) { |
||||
setCurrentRow(undefined); |
||||
} |
||||
}} |
||||
updateModalVisible={updateModalVisible} |
||||
values={currentRow || {}} |
||||
/> |
||||
|
||||
<Drawer |
||||
width={600} |
||||
visible={showDetail} |
||||
onClose={() => { |
||||
setCurrentRow(undefined); |
||||
setShowDetail(false); |
||||
}} |
||||
closable={false} |
||||
> |
||||
{currentRow?.name && ( |
||||
<ProDescriptions<API.RuleListItem> |
||||
column={2} |
||||
title={currentRow?.name} |
||||
request={async () => ({ |
||||
data: currentRow || {}, |
||||
})} |
||||
params={{ |
||||
id: currentRow?.name, |
||||
}} |
||||
columns={columns as ProDescriptionsItemProps<API.RuleListItem>[]} |
||||
/> |
||||
)} |
||||
</Drawer> |
||||
</PageContainer> |
||||
); |
||||
}; |
||||
export default TableList; |
@ -0,0 +1,8 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
.pre { |
||||
margin: 12px 0; |
||||
padding: 12px 20px; |
||||
background: @input-bg; |
||||
box-shadow: @card-shadow; |
||||
} |
@ -0,0 +1,40 @@ |
||||
import { PageContainer } from '@ant-design/pro-components'; |
||||
import { Alert, Card, Typography } from 'antd'; |
||||
import React from 'react'; |
||||
import styles from './Welcome.less'; |
||||
const CodePreview: React.FC = ({ children }) => ( |
||||
<pre className={styles.pre}> |
||||
<code> |
||||
<Typography.Text copyable>{children}</Typography.Text> |
||||
</code> |
||||
</pre> |
||||
); |
||||
const Welcome: React.FC = () => { |
||||
return ( |
||||
<PageContainer> |
||||
<Card> |
||||
<Alert |
||||
message={'更快更强的重型组件,已经发布。'} |
||||
type="success" |
||||
showIcon |
||||
banner |
||||
style={{ |
||||
margin: -12, |
||||
marginBottom: 24, |
||||
}} |
||||
/> |
||||
<Typography.Text strong> |
||||
<a |
||||
href="https://procomponents.ant.design/components/table" |
||||
rel="noopener noreferrer" |
||||
target="__blank" |
||||
> |
||||
欢迎使用 |
||||
</a> |
||||
</Typography.Text> |
||||
<CodePreview>yarn add @ant-design/pro-components</CodePreview> |
||||
</Card> |
||||
</PageContainer> |
||||
); |
||||
}; |
||||
export default Welcome; |
@ -0,0 +1,236 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<meta name="theme-color" content="#1890ff" /> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> |
||||
<meta |
||||
name="keywords" |
||||
content="antd,umi,umijs,ant design,Scaffolding, layout, Ant Design, project, Pro, admin, console, homepage, out-of-the-box, middle and back office, solution, component library" |
||||
/> |
||||
<meta |
||||
name="description" |
||||
content=" |
||||
An out-of-box UI solution for enterprise applications as a React boilerplate." |
||||
/> |
||||
<meta |
||||
name="description" |
||||
content=" |
||||
Out-of-the-box mid-stage front-end/design solution." |
||||
/> |
||||
<meta |
||||
name="viewport" |
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" |
||||
/> |
||||
<title>Ant Design Pro</title> |
||||
<link rel="icon" href="<%= context.config.publicPath +'favicon.ico'%>" type="image/x-icon" /> |
||||
</head> |
||||
<body> |
||||
<noscript> |
||||
<div class="noscript-container"> |
||||
Hi there! Please |
||||
<div class="noscript-enableJS"> |
||||
<a href="https://www.enablejavascript.io/en" target="_blank" rel="noopener noreferrer"> |
||||
<b>enable Javascript</b> |
||||
</a> |
||||
</div> |
||||
in your browser to use Ant Design, Out-of-the-box mid-stage front/design solution! |
||||
</div> |
||||
</noscript> |
||||
<div id="root"> |
||||
<style> |
||||
html, |
||||
body, |
||||
#root { |
||||
height: 100%; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
#root { |
||||
background-repeat: no-repeat; |
||||
background-size: 100% auto; |
||||
} |
||||
.noscript-container { |
||||
display: flex; |
||||
align-content: center; |
||||
justify-content: center; |
||||
margin-top: 90px; |
||||
font-size: 20px; |
||||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', |
||||
Geneva, Verdana, sans-serif; |
||||
} |
||||
.noscript-enableJS { |
||||
padding-right: 3px; |
||||
padding-left: 3px; |
||||
} |
||||
.page-loading-warp { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 98px; |
||||
} |
||||
.ant-spin { |
||||
position: absolute; |
||||
display: none; |
||||
-webkit-box-sizing: border-box; |
||||
box-sizing: border-box; |
||||
margin: 0; |
||||
padding: 0; |
||||
color: rgba(0, 0, 0, 0.65); |
||||
color: #1890ff; |
||||
font-size: 14px; |
||||
font-variant: tabular-nums; |
||||
line-height: 1.5; |
||||
text-align: center; |
||||
list-style: none; |
||||
opacity: 0; |
||||
-webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); |
||||
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); |
||||
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); |
||||
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), |
||||
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); |
||||
-webkit-font-feature-settings: 'tnum'; |
||||
font-feature-settings: 'tnum'; |
||||
} |
||||
|
||||
.ant-spin-spinning { |
||||
position: static; |
||||
display: inline-block; |
||||
opacity: 1; |
||||
} |
||||
|
||||
.ant-spin-dot { |
||||
position: relative; |
||||
display: inline-block; |
||||
width: 20px; |
||||
height: 20px; |
||||
font-size: 20px; |
||||
} |
||||
|
||||
.ant-spin-dot-item { |
||||
position: absolute; |
||||
display: block; |
||||
width: 9px; |
||||
height: 9px; |
||||
background-color: #1890ff; |
||||
border-radius: 100%; |
||||
-webkit-transform: scale(0.75); |
||||
-ms-transform: scale(0.75); |
||||
transform: scale(0.75); |
||||
-webkit-transform-origin: 50% 50%; |
||||
-ms-transform-origin: 50% 50%; |
||||
transform-origin: 50% 50%; |
||||
opacity: 0.3; |
||||
-webkit-animation: antspinmove 1s infinite linear alternate; |
||||
animation: antSpinMove 1s infinite linear alternate; |
||||
} |
||||
|
||||
.ant-spin-dot-item:nth-child(1) { |
||||
top: 0; |
||||
left: 0; |
||||
} |
||||
|
||||
.ant-spin-dot-item:nth-child(2) { |
||||
top: 0; |
||||
right: 0; |
||||
-webkit-animation-delay: 0.4s; |
||||
animation-delay: 0.4s; |
||||
} |
||||
|
||||
.ant-spin-dot-item:nth-child(3) { |
||||
right: 0; |
||||
bottom: 0; |
||||
-webkit-animation-delay: 0.8s; |
||||
animation-delay: 0.8s; |
||||
} |
||||
|
||||
.ant-spin-dot-item:nth-child(4) { |
||||
bottom: 0; |
||||
left: 0; |
||||
-webkit-animation-delay: 1.2s; |
||||
animation-delay: 1.2s; |
||||
} |
||||
|
||||
.ant-spin-dot-spin { |
||||
-webkit-transform: rotate(45deg); |
||||
-ms-transform: rotate(45deg); |
||||
transform: rotate(45deg); |
||||
-webkit-animation: antrotate 1.2s infinite linear; |
||||
animation: antRotate 1.2s infinite linear; |
||||
} |
||||
|
||||
.ant-spin-lg .ant-spin-dot { |
||||
width: 32px; |
||||
height: 32px; |
||||
font-size: 32px; |
||||
} |
||||
|
||||
.ant-spin-lg .ant-spin-dot i { |
||||
width: 14px; |
||||
height: 14px; |
||||
} |
||||
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { |
||||
.ant-spin-blur { |
||||
background: #fff; |
||||
opacity: 0.5; |
||||
} |
||||
} |
||||
|
||||
@-webkit-keyframes antSpinMove { |
||||
to { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
@keyframes antSpinMove { |
||||
to { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
@-webkit-keyframes antRotate { |
||||
to { |
||||
-webkit-transform: rotate(405deg); |
||||
transform: rotate(405deg); |
||||
} |
||||
} |
||||
|
||||
@keyframes antRotate { |
||||
to { |
||||
-webkit-transform: rotate(405deg); |
||||
transform: rotate(405deg); |
||||
} |
||||
} |
||||
</style> |
||||
<div |
||||
style=" |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
justify-content: center; |
||||
height: 100%; |
||||
min-height: 420px; |
||||
" |
||||
> |
||||
<img src="<%= context.config.publicPath +'pro_icon.svg'%>" alt="logo" width="256" /> |
||||
<div class="page-loading-warp"> |
||||
<div class="ant-spin ant-spin-lg ant-spin-spinning"> |
||||
<span class="ant-spin-dot ant-spin-dot-spin" |
||||
><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i |
||||
><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i |
||||
></span> |
||||
</div> |
||||
</div> |
||||
<div style="display: flex; align-items: center; justify-content: center"> |
||||
<img |
||||
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" |
||||
width="32" |
||||
style="margin-right: 8px" |
||||
/> |
||||
Ant Design |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,50 @@ |
||||
@import (reference) '~antd/es/style/themes/index'; |
||||
|
||||
.container { |
||||
display: flex; |
||||
flex-direction: column; |
||||
height: 100vh; |
||||
overflow: auto; |
||||
background: @layout-body-background; |
||||
} |
||||
|
||||
.lang { |
||||
width: 100%; |
||||
height: 40px; |
||||
line-height: 44px; |
||||
text-align: right; |
||||
:global(.ant-dropdown-trigger) { |
||||
margin-right: 24px; |
||||
} |
||||
} |
||||
|
||||
.content { |
||||
flex: 1; |
||||
padding: 32px 0; |
||||
} |
||||
|
||||
@media (min-width: @screen-md-min) { |
||||
.container { |
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); |
||||
background-repeat: no-repeat; |
||||
background-position: center 110px; |
||||
background-size: 100%; |
||||
} |
||||
|
||||
.content { |
||||
padding: 32px 0 24px; |
||||
} |
||||
} |
||||
|
||||
.icon { |
||||
margin-left: 8px; |
||||
color: rgba(0, 0, 0, 0.2); |
||||
font-size: 24px; |
||||
vertical-align: middle; |
||||
cursor: pointer; |
||||
transition: color 0.3s; |
||||
|
||||
&:hover { |
||||
color: @primary-color; |
||||
} |
||||
} |
@ -0,0 +1,214 @@ |
||||
import Footer from '@/components/Footer'; |
||||
import { login } from '@/services/ant-design-pro/api'; |
||||
import { getFakeCaptcha } from '@/services/ant-design-pro/login'; |
||||
import { |
||||
AlipayCircleOutlined, |
||||
LockOutlined, |
||||
MobileOutlined, |
||||
TaobaoCircleOutlined, |
||||
UserOutlined, |
||||
WeiboCircleOutlined, |
||||
} from '@ant-design/icons'; |
||||
import { |
||||
LoginForm, |
||||
ProFormCaptcha, |
||||
ProFormCheckbox, |
||||
ProFormText, |
||||
} from '@ant-design/pro-components'; |
||||
import { Alert, message, Tabs } from 'antd'; |
||||
import React, { useState } from 'react'; |
||||
import { history, useModel } from 'umi'; |
||||
import styles from './index.less'; |
||||
const LoginMessage: React.FC<{ |
||||
content: string; |
||||
}> = ({ content }) => ( |
||||
<Alert |
||||
style={{ |
||||
marginBottom: 24, |
||||
}} |
||||
message={content} |
||||
type="error" |
||||
showIcon |
||||
/> |
||||
); |
||||
const Login: React.FC = () => { |
||||
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({}); |
||||
const [type, setType] = useState<string>('account'); |
||||
const { initialState, setInitialState } = useModel('@@initialState'); |
||||
const fetchUserInfo = async () => { |
||||
const userInfo = await initialState?.fetchUserInfo?.(); |
||||
if (userInfo) { |
||||
await setInitialState((s) => ({ |
||||
...s, |
||||
currentUser: userInfo, |
||||
})); |
||||
} |
||||
}; |
||||
const handleSubmit = async (values: API.LoginParams) => { |
||||
try { |
||||
// 登录
|
||||
const msg = await login({ |
||||
...values, |
||||
type, |
||||
}); |
||||
if (msg.status === 'ok') { |
||||
const defaultLoginSuccessMessage = '登录成功!'; |
||||
message.success(defaultLoginSuccessMessage); |
||||
await fetchUserInfo(); |
||||
/** 此方法会跳转到 redirect 参数所在的位置 */ |
||||
if (!history) return; |
||||
const { query } = history.location; |
||||
const { redirect } = query as { |
||||
redirect: string; |
||||
}; |
||||
history.push(redirect || '/'); |
||||
return; |
||||
} |
||||
console.log(msg); |
||||
// 如果失败去设置用户错误信息
|
||||
setUserLoginState(msg); |
||||
} catch (error) { |
||||
const defaultLoginFailureMessage = '登录失败,请重试!'; |
||||
message.error(defaultLoginFailureMessage); |
||||
} |
||||
}; |
||||
const { status, type: loginType } = userLoginState; |
||||
return ( |
||||
<div className={styles.container}> |
||||
<div className={styles.content}> |
||||
<LoginForm |
||||
logo={<img alt="logo" src="/logo.svg" />} |
||||
title="Ant Design" |
||||
subTitle={'Ant Design 是西湖区最具影响力的 Web 设计规范'} |
||||
initialValues={{ |
||||
autoLogin: true, |
||||
}} |
||||
actions={[ |
||||
'其他登录方式 :', |
||||
<AlipayCircleOutlined key="AlipayCircleOutlined" className={styles.icon} />, |
||||
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={styles.icon} />, |
||||
<WeiboCircleOutlined key="WeiboCircleOutlined" className={styles.icon} />, |
||||
]} |
||||
onFinish={async (values) => { |
||||
await handleSubmit(values as API.LoginParams); |
||||
}} |
||||
> |
||||
<Tabs activeKey={type} onChange={setType}> |
||||
<Tabs.TabPane key="account" tab={'账户密码登录'} /> |
||||
<Tabs.TabPane key="mobile" tab={'手机号登录'} /> |
||||
</Tabs> |
||||
|
||||
{status === 'error' && loginType === 'account' && ( |
||||
<LoginMessage content={'错误的用户名和密码(admin/ant.design)'} /> |
||||
)} |
||||
{type === 'account' && ( |
||||
<> |
||||
<ProFormText |
||||
name="username" |
||||
fieldProps={{ |
||||
size: 'large', |
||||
prefix: <UserOutlined className={styles.prefixIcon} />, |
||||
}} |
||||
placeholder={'用户名: admin or user'} |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '用户名是必填项!', |
||||
}, |
||||
]} |
||||
/> |
||||
<ProFormText.Password |
||||
name="password" |
||||
fieldProps={{ |
||||
size: 'large', |
||||
prefix: <LockOutlined className={styles.prefixIcon} />, |
||||
}} |
||||
placeholder={'密码: ant.design'} |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '密码是必填项!', |
||||
}, |
||||
]} |
||||
/> |
||||
</> |
||||
)} |
||||
|
||||
{status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />} |
||||
{type === 'mobile' && ( |
||||
<> |
||||
<ProFormText |
||||
fieldProps={{ |
||||
size: 'large', |
||||
prefix: <MobileOutlined className={styles.prefixIcon} />, |
||||
}} |
||||
name="mobile" |
||||
placeholder={'请输入手机号!'} |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '手机号是必填项!', |
||||
}, |
||||
{ |
||||
pattern: /^1\d{10}$/, |
||||
message: '不合法的手机号!', |
||||
}, |
||||
]} |
||||
/> |
||||
<ProFormCaptcha |
||||
fieldProps={{ |
||||
size: 'large', |
||||
prefix: <LockOutlined className={styles.prefixIcon} />, |
||||
}} |
||||
captchaProps={{ |
||||
size: 'large', |
||||
}} |
||||
placeholder={'请输入验证码!'} |
||||
captchaTextRender={(timing, count) => { |
||||
if (timing) { |
||||
return `${count} ${'秒后重新获取'}`; |
||||
} |
||||
return '获取验证码'; |
||||
}} |
||||
name="captcha" |
||||
rules={[ |
||||
{ |
||||
required: true, |
||||
message: '验证码是必填项!', |
||||
}, |
||||
]} |
||||
onGetCaptcha={async (phone) => { |
||||
const result = await getFakeCaptcha({ |
||||
phone, |
||||
}); |
||||
if (result === false) { |
||||
return; |
||||
} |
||||
message.success('获取验证码成功!验证码为:1234'); |
||||
}} |
||||
/> |
||||
</> |
||||
)} |
||||
<div |
||||
style={{ |
||||
marginBottom: 24, |
||||
}} |
||||
> |
||||
<ProFormCheckbox noStyle name="autoLogin"> |
||||
自动登录 |
||||
</ProFormCheckbox> |
||||
<a |
||||
style={{ |
||||
float: 'right', |
||||
}} |
||||
> |
||||
忘记密码 ? |
||||
</a> |
||||
</div> |
||||
</LoginForm> |
||||
</div> |
||||
<Footer /> |
||||
</div> |
||||
); |
||||
}; |
||||
export default Login; |
@ -0,0 +1,65 @@ |
||||
/* eslint-disable no-restricted-globals */ |
||||
/* eslint-disable no-underscore-dangle */ |
||||
/* globals workbox */ |
||||
workbox.core.setCacheNameDetails({ |
||||
prefix: 'antd-pro', |
||||
suffix: 'v5', |
||||
}); |
||||
// Control all opened tabs ASAP
|
||||
workbox.clientsClaim(); |
||||
|
||||
/** |
||||
* Use precaching list generated by workbox in build process. |
||||
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
|
||||
*/ |
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest || []); |
||||
|
||||
/** |
||||
* Register a navigation route. |
||||
* https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
|
||||
*/ |
||||
workbox.routing.registerNavigationRoute('/index.html'); |
||||
|
||||
/** |
||||
* Use runtime cache: |
||||
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
|
||||
* |
||||
* Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc. |
||||
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
|
||||
*/ |
||||
|
||||
/** Handle API requests */ |
||||
workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst()); |
||||
|
||||
/** Handle third party requests */ |
||||
workbox.routing.registerRoute( |
||||
/^https:\/\/gw\.alipayobjects\.com\//, |
||||
workbox.strategies.networkFirst(), |
||||
); |
||||
workbox.routing.registerRoute( |
||||
/^https:\/\/cdnjs\.cloudflare\.com\//, |
||||
workbox.strategies.networkFirst(), |
||||
); |
||||
workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst()); |
||||
|
||||
/** Response to client after skipping waiting with MessageChannel */ |
||||
addEventListener('message', (event) => { |
||||
const replyPort = event.ports[0]; |
||||
const message = event.data; |
||||
if (replyPort && message && message.type === 'skip-waiting') { |
||||
event.waitUntil( |
||||
self.skipWaiting().then( |
||||
() => { |
||||
replyPort.postMessage({ |
||||
error: null, |
||||
}); |
||||
}, |
||||
(error) => { |
||||
replyPort.postMessage({ |
||||
error, |
||||
}); |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
}); |
@ -0,0 +1,85 @@ |
||||
// @ts-ignore
|
||||
/* eslint-disable */ |
||||
import { request } from 'umi'; |
||||
|
||||
/** 获取当前的用户 GET /api/currentUser */ |
||||
export async function currentUser(options?: { [key: string]: any }) { |
||||
return request<{ |
||||
data: API.CurrentUser; |
||||
}>('/api/currentUser', { |
||||
method: 'GET', |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 退出登录接口 POST /api/login/outLogin */ |
||||
export async function outLogin(options?: { [key: string]: any }) { |
||||
return request<Record<string, any>>('/api/login/outLogin', { |
||||
method: 'POST', |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 登录接口 POST /api/login/account */ |
||||
export async function login(body: API.LoginParams, options?: { [key: string]: any }) { |
||||
return request<API.LoginResult>('/api/login/account', { |
||||
method: 'POST', |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
}, |
||||
data: body, |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 此处后端没有提供注释 GET /api/notices */ |
||||
export async function getNotices(options?: { [key: string]: any }) { |
||||
return request<API.NoticeIconList>('/api/notices', { |
||||
method: 'GET', |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 获取规则列表 GET /api/rule */ |
||||
export async function rule( |
||||
params: { |
||||
// query
|
||||
/** 当前的页码 */ |
||||
current?: number; |
||||
/** 页面的容量 */ |
||||
pageSize?: number; |
||||
}, |
||||
options?: { [key: string]: any }, |
||||
) { |
||||
return request<API.RuleList>('/api/rule', { |
||||
method: 'GET', |
||||
params: { |
||||
...params, |
||||
}, |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 新建规则 PUT /api/rule */ |
||||
export async function updateRule(options?: { [key: string]: any }) { |
||||
return request<API.RuleListItem>('/api/rule', { |
||||
method: 'PUT', |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 新建规则 POST /api/rule */ |
||||
export async function addRule(options?: { [key: string]: any }) { |
||||
return request<API.RuleListItem>('/api/rule', { |
||||
method: 'POST', |
||||
...(options || {}), |
||||
}); |
||||
} |
||||
|
||||
/** 删除规则 DELETE /api/rule */ |
||||
export async function removeRule(options?: { [key: string]: any }) { |
||||
return request<Record<string, any>>('/api/rule', { |
||||
method: 'DELETE', |
||||
...(options || {}), |
||||
}); |
||||
} |
@ -0,0 +1,10 @@ |
||||
// @ts-ignore
|
||||
/* eslint-disable */ |
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as api from './api'; |
||||
import * as login from './login'; |
||||
export default { |
||||
api, |
||||
login, |
||||
}; |
@ -0,0 +1,21 @@ |
||||
// @ts-ignore
|
||||
/* eslint-disable */ |
||||
import { request } from 'umi'; |
||||
|
||||
/** 发送验证码 POST /api/login/captcha */ |
||||
export async function getFakeCaptcha( |
||||
params: { |
||||
// query
|
||||
/** 手机号 */ |
||||
phone?: string; |
||||
}, |
||||
options?: { [key: string]: any }, |
||||
) { |
||||
return request<API.FakeCaptcha>('/api/login/captcha', { |
||||
method: 'GET', |
||||
params: { |
||||
...params, |
||||
}, |
||||
...(options || {}), |
||||
}); |
||||
} |
@ -0,0 +1,101 @@ |
||||
// @ts-ignore
|
||||
/* eslint-disable */ |
||||
|
||||
declare namespace API { |
||||
type CurrentUser = { |
||||
name?: string; |
||||
avatar?: string; |
||||
userid?: string; |
||||
email?: string; |
||||
signature?: string; |
||||
title?: string; |
||||
group?: string; |
||||
tags?: { key?: string; label?: string }[]; |
||||
notifyCount?: number; |
||||
unreadCount?: number; |
||||
country?: string; |
||||
access?: string; |
||||
geographic?: { |
||||
province?: { label?: string; key?: string }; |
||||
city?: { label?: string; key?: string }; |
||||
}; |
||||
address?: string; |
||||
phone?: string; |
||||
}; |
||||
|
||||
type LoginResult = { |
||||
status?: string; |
||||
type?: string; |
||||
currentAuthority?: string; |
||||
}; |
||||
|
||||
type PageParams = { |
||||
current?: number; |
||||
pageSize?: number; |
||||
}; |
||||
|
||||
type RuleListItem = { |
||||
key?: number; |
||||
disabled?: boolean; |
||||
href?: string; |
||||
avatar?: string; |
||||
name?: string; |
||||
owner?: string; |
||||
desc?: string; |
||||
callNo?: number; |
||||
status?: number; |
||||
updatedAt?: string; |
||||
createdAt?: string; |
||||
progress?: number; |
||||
}; |
||||
|
||||
type RuleList = { |
||||
data?: RuleListItem[]; |
||||
/** 列表的内容总数 */ |
||||
total?: number; |
||||
success?: boolean; |
||||
}; |
||||
|
||||
type FakeCaptcha = { |
||||
code?: number; |
||||
status?: string; |
||||
}; |
||||
|
||||
type LoginParams = { |
||||
username?: string; |
||||
password?: string; |
||||
autoLogin?: boolean; |
||||
type?: string; |
||||
}; |
||||
|
||||
type ErrorResponse = { |
||||
/** 业务约定的错误码 */ |
||||
errorCode: string; |
||||
/** 业务上的错误信息 */ |
||||
errorMessage?: string; |
||||
/** 业务上的请求是否成功 */ |
||||
success?: boolean; |
||||
}; |
||||
|
||||
type NoticeIconList = { |
||||
data?: NoticeIconItem[]; |
||||
/** 列表的内容总数 */ |
||||
total?: number; |
||||
success?: boolean; |
||||
}; |
||||
|
||||
type NoticeIconItemType = 'notification' | 'message' | 'event'; |
||||
|
||||
type NoticeIconItem = { |
||||
id?: string; |
||||
extra?: string; |
||||
key?: string; |
||||
read?: boolean; |
||||
avatar?: string; |
||||
title?: string; |
||||
status?: string; |
||||
datetime?: string; |
||||
description?: string; |
||||
type?: NoticeIconItemType; |
||||
}; |
||||
} |
@ -0,0 +1,24 @@ |
||||
declare module 'slash2'; |
||||
declare module '*.css'; |
||||
declare module '*.less'; |
||||
declare module '*.scss'; |
||||
declare module '*.sass'; |
||||
declare module '*.svg'; |
||||
declare module '*.png'; |
||||
declare module '*.jpg'; |
||||
declare module '*.jpeg'; |
||||
declare module '*.gif'; |
||||
declare module '*.bmp'; |
||||
declare module '*.tiff'; |
||||
declare module 'omit.js'; |
||||
declare module 'numeral'; |
||||
declare module '@antv/data-set'; |
||||
declare module 'mockjs'; |
||||
declare module 'react-fittext'; |
||||
declare module 'bizcharts-plugin-slider'; |
||||
|
||||
// preview.pro.ant.design only do not use in your production ;
|
||||
// preview.pro.ant.design Dedicated environment variable, please do not use it in your project.
|
||||
declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined; |
||||
|
||||
declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false; |
@ -0,0 +1,42 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"outDir": "build/dist", |
||||
"module": "esnext", |
||||
"target": "esnext", |
||||
"lib": ["esnext", "dom"], |
||||
"sourceMap": true, |
||||
"baseUrl": ".", |
||||
"jsx": "react-jsx", |
||||
"resolveJsonModule": true, |
||||
"allowSyntheticDefaultImports": true, |
||||
"moduleResolution": "node", |
||||
"forceConsistentCasingInFileNames": true, |
||||
"noImplicitReturns": true, |
||||
"suppressImplicitAnyIndexErrors": true, |
||||
"noUnusedLocals": true, |
||||
"allowJs": true, |
||||
"skipLibCheck": true, |
||||
"experimentalDecorators": true, |
||||
"strict": true, |
||||
"paths": { |
||||
"@/*": ["./src/*"], |
||||
"@@/*": ["./src/.umi/*"] |
||||
} |
||||
}, |
||||
"include": [ |
||||
"mock/**/*", |
||||
"src/**/*", |
||||
"playwright.config.ts", |
||||
"tests/**/*", |
||||
"test/**/*", |
||||
"__test__/**/*", |
||||
"typings/**/*", |
||||
"config/**/*", |
||||
".eslintrc.js", |
||||
".stylelintrc.js", |
||||
".prettierrc.js", |
||||
"jest.config.js", |
||||
"mock/*" |
||||
], |
||||
"exclude": ["node_modules", "build", "dist", "scripts", "src/.umi/*", "webpack", "jest"] |
||||
} |
Loading…
Reference in new issue