first commit
This commit is contained in:
commit
fa9e69baa5
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
||||
权限角色管理系统
|
||||
---
|
||||
#### 依赖框架
|
||||
|
||||
*[x] springboot 3.5.4
|
||||
*[x] thymeleaf
|
||||
*[x] jpa
|
||||
*[x] mysql
|
259
mvnw
vendored
Normal file
259
mvnw
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
#!/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
|
||||
#
|
||||
# http://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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
set -euf
|
||||
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||
|
||||
# OS specific support.
|
||||
native_path() { printf %s\\n "$1"; }
|
||||
case "$(uname)" in
|
||||
CYGWIN* | MINGW*)
|
||||
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||
native_path() { cygpath --path --windows "$1"; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# set JAVACMD and JAVACCMD
|
||||
set_java_home() {
|
||||
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||
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"
|
||||
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||
|
||||
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v java
|
||||
)" || :
|
||||
JAVACCMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v javac
|
||||
)" || :
|
||||
|
||||
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# hash string like Java String::hashCode
|
||||
hash_string() {
|
||||
str="${1:-}" h=0
|
||||
while [ -n "$str" ]; do
|
||||
char="${str%"${str#?}"}"
|
||||
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||
str="${str#?}"
|
||||
done
|
||||
printf %x\\n $h
|
||||
}
|
||||
|
||||
verbose() { :; }
|
||||
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||
|
||||
die() {
|
||||
printf %s\\n "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||
esac
|
||||
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
|
||||
case "${distributionUrl##*/}" in
|
||||
maven-mvnd-*bin.*)
|
||||
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||
*)
|
||||
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||
distributionPlatform=linux-amd64
|
||||
;;
|
||||
esac
|
||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||
;;
|
||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||
esac
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||
|
||||
exec_maven() {
|
||||
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||
}
|
||||
|
||||
if [ -d "$MAVEN_HOME" ]; then
|
||||
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
exec_maven "$@"
|
||||
fi
|
||||
|
||||
case "${distributionUrl-}" in
|
||||
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||
esac
|
||||
|
||||
# prepare tmp dir
|
||||
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||
trap clean HUP INT TERM EXIT
|
||||
else
|
||||
die "cannot create temp dir"
|
||||
fi
|
||||
|
||||
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||
|
||||
# Download and Install Apache Maven
|
||||
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
verbose "Downloading from: $distributionUrl"
|
||||
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
# select .zip or .tar.gz
|
||||
if ! command -v unzip >/dev/null; then
|
||||
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
fi
|
||||
|
||||
# verbose opt
|
||||
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||
|
||||
# normalize http auth
|
||||
case "${MVNW_PASSWORD:+has-password}" in
|
||||
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
esac
|
||||
|
||||
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||
verbose "Found wget ... using wget"
|
||||
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||
verbose "Found curl ... using curl"
|
||||
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||
elif set_java_home; then
|
||||
verbose "Falling back to use Java to download"
|
||||
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
cat >"$javaSource" <<-END
|
||||
public class Downloader extends java.net.Authenticator
|
||||
{
|
||||
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||
}
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
setDefault( new Downloader() );
|
||||
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||
}
|
||||
}
|
||||
END
|
||||
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||
verbose " - Compiling Downloader.java ..."
|
||||
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||
verbose " - Running Downloader.java ..."
|
||||
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||
fi
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
if [ -n "${distributionSha256Sum-}" ]; then
|
||||
distributionSha256Result=false
|
||||
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
elif command -v sha256sum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
elif command -v shasum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ $distributionSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# unzip and move
|
||||
if command -v unzip >/dev/null; then
|
||||
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||
else
|
||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||
fi
|
||||
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
|
||||
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||
|
||||
clean || :
|
||||
exec_maven "$@"
|
149
mvnw.cmd
vendored
Normal file
149
mvnw.cmd
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
<# : batch portion
|
||||
@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 http://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 Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||
@SET __MVNW_CMD__=
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@SET __MVNW_PSMODULEP_SAVE=
|
||||
@SET __MVNW_ARG0_NAME__=
|
||||
@SET MVNW_USERNAME=
|
||||
@SET MVNW_PASSWORD=
|
||||
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
|
||||
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||
@GOTO :EOF
|
||||
: end batch / begin powershell #>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($env:MVNW_VERBOSE -eq "true") {
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
|
||||
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||
"maven-mvnd-*" {
|
||||
$USE_MVND = $true
|
||||
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||
$MVN_CMD = "mvnd.cmd"
|
||||
break
|
||||
}
|
||||
default {
|
||||
$USE_MVND = $false
|
||||
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
if ($env:MVNW_REPOURL) {
|
||||
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
|
||||
}
|
||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
|
||||
}
|
||||
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||
|
||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
exit $?
|
||||
}
|
||||
|
||||
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||
}
|
||||
|
||||
# prepare tmp dir
|
||||
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||
trap {
|
||||
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||
|
||||
# Download and Install Apache Maven
|
||||
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
Write-Verbose "Downloading from: $distributionUrl"
|
||||
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
}
|
||||
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||
}
|
||||
}
|
||||
|
||||
# unzip and move
|
||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||
try {
|
||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||
} catch {
|
||||
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||
Write-Error "fail to move MAVEN_HOME"
|
||||
}
|
||||
} finally {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
101
pom.xml
Normal file
101
pom.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<?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>3.5.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>top.yexuejc</groupId>
|
||||
<artifactId>permission-role-management-system</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>permission-role-management-system</name>
|
||||
<description>permission-role-management-system</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>nz.net.ultraq.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf-layout-dialect</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>top.yexuejc</groupId>
|
||||
<artifactId>yexuejc-base</artifactId>
|
||||
<version>1.5.3-jre11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,13 @@
|
||||
package top.yexuejc.admin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class PermissionRoleManagementSystemApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PermissionRoleManagementSystemApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/top/yexuejc/admin/config/I18nConfig.java
Normal file
48
src/main/java/top/yexuejc/admin/config/I18nConfig.java
Normal file
@ -0,0 +1,48 @@
|
||||
package top.yexuejc.admin.config;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import top.yexuejc.admin.constant.SymbolsConstant;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:33
|
||||
*/
|
||||
@Configuration
|
||||
public class I18nConfig {
|
||||
private static final Logger log = LoggerFactory.getLogger(I18nConfig.class);
|
||||
@Value("${default.locale:zh-CN}")
|
||||
private String defaultLocale;
|
||||
@Value("${spring.messages.basename:messages}")
|
||||
private String basename;
|
||||
@Value("${spring.messages.encoding:UTF-8}")
|
||||
private String encoding;
|
||||
@Value("${spring.messages.cache.seconds:3600}")
|
||||
private int cacheSeconds;
|
||||
|
||||
@Bean
|
||||
public MessageSource messageSource() {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
messageSource.setBasenames(basename.split(SymbolsConstant.COMMA));
|
||||
messageSource.setDefaultEncoding(encoding);
|
||||
messageSource.setCacheSeconds(cacheSeconds);
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void setDefaultLocale() {
|
||||
Locale locale = Locale.forLanguageTag(defaultLocale);
|
||||
log.debug("Local default language:{}", locale);
|
||||
Locale.setDefault(locale);
|
||||
}
|
||||
}
|
17
src/main/java/top/yexuejc/admin/constant/MenuTypeEnum.java
Normal file
17
src/main/java/top/yexuejc/admin/constant/MenuTypeEnum.java
Normal file
@ -0,0 +1,17 @@
|
||||
package top.yexuejc.admin.constant;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 20:19
|
||||
*/
|
||||
public enum MenuTypeEnum {
|
||||
MENU("1","菜单目录"),
|
||||
PATH("2","菜单"),
|
||||
BUTTON("3","按钮");
|
||||
public String code;
|
||||
public String desc;
|
||||
MenuTypeEnum(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
21
src/main/java/top/yexuejc/admin/constant/MessageCodes.java
Normal file
21
src/main/java/top/yexuejc/admin/constant/MessageCodes.java
Normal file
@ -0,0 +1,21 @@
|
||||
package top.yexuejc.admin.constant;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 19:05
|
||||
*/
|
||||
public enum MessageCodes {
|
||||
SUCCESS("SUCCESS","OK"),
|
||||
FAIL("FAIL","ERROR"),
|
||||
USER_NOT_EXIST("USER_NOT_EXIST","用户不存在"),
|
||||
USER_PASSWORD_ERROR("USER_PASSWORD_ERROR","用户密码错误"),
|
||||
USER_NOT_LOGIN("USER_NOT_LOGIN","用户未登录"),
|
||||
USER_NOT_PERMISSION("USER_NOT_PERMISSION","用户无权限"),
|
||||
;
|
||||
public String code;
|
||||
public String message;
|
||||
MessageCodes(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package top.yexuejc.admin.constant;
|
||||
|
||||
/**
|
||||
* 符号常量
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:32
|
||||
*/
|
||||
public class SymbolsConstant {
|
||||
/** 半角逗号 */
|
||||
public static final String COMMA = ",";
|
||||
public static final String EMPTY = "";
|
||||
}
|
40
src/main/java/top/yexuejc/admin/entity/Menu.java
Normal file
40
src/main/java/top/yexuejc/admin/entity/Menu.java
Normal file
@ -0,0 +1,40 @@
|
||||
package top.yexuejc.admin.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 菜单
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:45
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "sys_menu")
|
||||
@Data
|
||||
public class Menu {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
/** 菜单名称 */
|
||||
private String name;
|
||||
/**
|
||||
* 菜单类型:
|
||||
* 1:MENU
|
||||
* 2:PATH
|
||||
* 3:按钮
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* 菜单类型为2时,为path
|
||||
* 菜单类型为3时,为按钮标识
|
||||
*/
|
||||
private String tag;
|
||||
/** 父级菜单ID */
|
||||
private Long parentId;
|
||||
private Integer orderNum;
|
||||
}
|
40
src/main/java/top/yexuejc/admin/entity/Role.java
Normal file
40
src/main/java/top/yexuejc/admin/entity/Role.java
Normal file
@ -0,0 +1,40 @@
|
||||
package top.yexuejc.admin.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 角色
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:48
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "sys_role")
|
||||
@Data
|
||||
public class Role {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
/** 角色名称 */
|
||||
@Column(unique = true, nullable = false)
|
||||
private String name;
|
||||
/**
|
||||
* 角色级别
|
||||
* 值越小,级别越高
|
||||
* 默认为1
|
||||
* 值大的看不见值小的
|
||||
* */
|
||||
@Column(unique = true, nullable = false)
|
||||
private Integer level;
|
||||
private String description;
|
||||
private Date createTime;
|
||||
|
||||
}
|
22
src/main/java/top/yexuejc/admin/entity/RoleMenu.java
Normal file
22
src/main/java/top/yexuejc/admin/entity/RoleMenu.java
Normal file
@ -0,0 +1,22 @@
|
||||
package top.yexuejc.admin.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 角色菜单关联
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:55
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "sys_role_menu")
|
||||
@Data
|
||||
public class RoleMenu {
|
||||
@Id
|
||||
private Long roleId;
|
||||
@Id
|
||||
private Long menuId;
|
||||
|
||||
}
|
34
src/main/java/top/yexuejc/admin/entity/User.java
Normal file
34
src/main/java/top/yexuejc/admin/entity/User.java
Normal file
@ -0,0 +1,34 @@
|
||||
package top.yexuejc.admin.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:44
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "sys_user")
|
||||
@Data
|
||||
public class User {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
@Column(unique = true, nullable = false)
|
||||
private String username;
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
private String name;
|
||||
/**状态(0禁用,1启用)*/
|
||||
private Integer status;
|
||||
private Date createTime;
|
||||
|
||||
}
|
22
src/main/java/top/yexuejc/admin/entity/UserRole.java
Normal file
22
src/main/java/top/yexuejc/admin/entity/UserRole.java
Normal file
@ -0,0 +1,22 @@
|
||||
package top.yexuejc.admin.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户角色关联表
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:54
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "sys_user_role")
|
||||
@Data
|
||||
public class UserRole {
|
||||
@Id
|
||||
private Long userId;
|
||||
@Id
|
||||
private Long roleId;
|
||||
|
||||
}
|
12
src/main/java/top/yexuejc/admin/exception/AppException.java
Normal file
12
src/main/java/top/yexuejc/admin/exception/AppException.java
Normal file
@ -0,0 +1,12 @@
|
||||
package top.yexuejc.admin.exception;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/30 13:34
|
||||
*/
|
||||
public class AppException extends Exception{
|
||||
|
||||
public AppException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
55
src/main/java/top/yexuejc/admin/model/HttpRespBO.java
Normal file
55
src/main/java/top/yexuejc/admin/model/HttpRespBO.java
Normal file
@ -0,0 +1,55 @@
|
||||
package top.yexuejc.admin.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 外部APIレスポンスmodel
|
||||
*
|
||||
* @author ISC馬
|
||||
* @date 2023/08/28
|
||||
*/
|
||||
public class HttpRespBO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Integer code;
|
||||
private String body;
|
||||
private String cookie;
|
||||
|
||||
public HttpRespBO() {
|
||||
}
|
||||
|
||||
public HttpRespBO(Integer code, String body) {
|
||||
super();
|
||||
this.code = code;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public HttpRespBO setCookie(String cookie) {
|
||||
this.cookie = cookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
88
src/main/java/top/yexuejc/admin/model/ListResponseVO.java
Normal file
88
src/main/java/top/yexuejc/admin/model/ListResponseVO.java
Normal file
@ -0,0 +1,88 @@
|
||||
package top.yexuejc.admin.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import top.yexuejc.admin.constant.MessageCodes;
|
||||
|
||||
|
||||
/**
|
||||
* API結果が戻りする、集合型
|
||||
*
|
||||
* @author ISC
|
||||
* @date 2023/08/18
|
||||
*/
|
||||
|
||||
public class ListResponseVO<T> extends ResponseVO {
|
||||
|
||||
@SuppressWarnings("java:S1948")// オブジェクトタイプは直列化する必要があります
|
||||
private List<T> data;
|
||||
|
||||
public ListResponseVO() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ListResponseVO(List<T> data) {
|
||||
this.data = data;
|
||||
setCode(MessageCodes.SUCCESS.code);
|
||||
setMessage(MessageCodes.SUCCESS.message);
|
||||
}
|
||||
|
||||
public ListResponseVO(List<T> data, String code, String msg) {
|
||||
super(code, msg);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public List<T> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public ListResponseVO<T> setData(List<T> data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static <T> ListResponseVO<T> success(List<T> data) {
|
||||
return new ListResponseVO<>(data);
|
||||
}
|
||||
|
||||
public static ListResponseVO<String> fail(String message) {
|
||||
ListResponseVO<String> vo = new ListResponseVO<>();
|
||||
vo.setCode(MessageCodes.FAIL.code).setMessage(message);
|
||||
return vo;
|
||||
}
|
||||
|
||||
public static ListResponseVO<String> success(String errorCode, String message) {
|
||||
ListResponseVO<String> vo = new ListResponseVO<>();
|
||||
vo.setCode(errorCode).setMessage(message);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResponseVO<T> setCode(String code) {
|
||||
super.setCode(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResponseVO<T> setMessage(String message) {
|
||||
super.setMessage(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResponseVO<T> setErrCode(String errCode) {
|
||||
super.setErrCode(errCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListResponseVO<T> setErrCodeDes(String errCodeDes) {
|
||||
super.setErrCodeDes(errCodeDes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
84
src/main/java/top/yexuejc/admin/model/ObjectResponseVO.java
Normal file
84
src/main/java/top/yexuejc/admin/model/ObjectResponseVO.java
Normal file
@ -0,0 +1,84 @@
|
||||
package top.yexuejc.admin.model;
|
||||
|
||||
import top.yexuejc.admin.constant.MessageCodes;
|
||||
|
||||
/**
|
||||
* API結果が戻りする、対象型
|
||||
*
|
||||
* @author ISC
|
||||
* @date 2023/08/18
|
||||
*/
|
||||
public class ObjectResponseVO<T> extends ResponseVO {
|
||||
|
||||
@SuppressWarnings("java:S1948") // オブジェクトタイプは直列化する必要があります
|
||||
private T data;
|
||||
|
||||
public ObjectResponseVO() {
|
||||
}
|
||||
|
||||
public ObjectResponseVO(T data) {
|
||||
this.data = data;
|
||||
setCode(MessageCodes.SUCCESS.code);
|
||||
setMessage(MessageCodes.SUCCESS.message);
|
||||
}
|
||||
|
||||
public ObjectResponseVO(T data, String code, String msg) {
|
||||
super(code, msg);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public ObjectResponseVO<T> setData(T data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static <T> ObjectResponseVO<T> success(T data) {
|
||||
return new ObjectResponseVO<>(data);
|
||||
}
|
||||
|
||||
public static ObjectResponseVO<String> fail(String message) {
|
||||
ObjectResponseVO<String> vo = new ObjectResponseVO<>();
|
||||
vo.setCode(MessageCodes.FAIL.code).setMessage(message);
|
||||
return vo;
|
||||
}
|
||||
|
||||
public static ObjectResponseVO<String> fail(String errorCode, String message) {
|
||||
ObjectResponseVO<String> vo = new ObjectResponseVO<>();
|
||||
vo.setCode(errorCode).setMessage(message);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectResponseVO<T> setCode(String code) {
|
||||
super.setCode(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectResponseVO<T> setMessage(String message) {
|
||||
super.setMessage(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectResponseVO<T> setErrCode(String errCode) {
|
||||
super.setErrCode(errCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectResponseVO<T> setErrCodeDes(String errCodeDes) {
|
||||
super.setErrCodeDes(errCodeDes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
113
src/main/java/top/yexuejc/admin/model/PageResponseVO.java
Normal file
113
src/main/java/top/yexuejc/admin/model/PageResponseVO.java
Normal file
@ -0,0 +1,113 @@
|
||||
package top.yexuejc.admin.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API結果が戻りする、集合型
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/9 15:23
|
||||
*/
|
||||
public class PageResponseVO<T> extends ListResponseVO<T> {
|
||||
/**
|
||||
* 総件数
|
||||
*/
|
||||
private Long totalCount;
|
||||
/**
|
||||
* ページサイズ
|
||||
*/
|
||||
private Long pageSize;
|
||||
/**
|
||||
* 現在のページ
|
||||
*/
|
||||
private Long pageNum;
|
||||
|
||||
public PageResponseVO() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PageResponseVO(List<T> data, long count) {
|
||||
super(data);
|
||||
this.totalCount = count;
|
||||
}
|
||||
|
||||
public static <T> PageResponseVO<T> of(List<T> data, long count) {
|
||||
return new PageResponseVO<>(data, count);
|
||||
}
|
||||
|
||||
public PageResponseVO<T> withOffset(long offset, long limit) {
|
||||
this.pageNum = offset / limit + 1;
|
||||
this.pageSize = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PageResponseVO<T> withPageNum(long pageNum, long pageSize) {
|
||||
this.pageNum = pageNum;
|
||||
this.pageSize = pageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Long getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public PageResponseVO<T> setTotalCount(Long totalCount) {
|
||||
this.totalCount = totalCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Long getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public PageResponseVO<T> setPageSize(Long pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Long getPageNum() {
|
||||
return pageNum;
|
||||
}
|
||||
|
||||
public PageResponseVO<T> setPageNum(Long pageNum) {
|
||||
this.pageNum = pageNum;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResponseVO<T> setCode(String code) {
|
||||
super.setCode(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResponseVO<T> setMessage(String message) {
|
||||
super.setMessage(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResponseVO<T> setErrCode(String errCode) {
|
||||
super.setErrCode(errCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResponseVO<T> setErrCodeDes(String errCodeDes) {
|
||||
super.setErrCodeDes(errCodeDes);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResponseVO<T> setData(List<T> data) {
|
||||
super.setData(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
102
src/main/java/top/yexuejc/admin/model/ResponseVO.java
Normal file
102
src/main/java/top/yexuejc/admin/model/ResponseVO.java
Normal file
@ -0,0 +1,102 @@
|
||||
package top.yexuejc.admin.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import top.yexuejc.admin.constant.MessageCodes;
|
||||
import top.yexuejc.admin.constant.SymbolsConstant;
|
||||
|
||||
|
||||
/**
|
||||
* API結果が戻りする
|
||||
*
|
||||
* @author ISC
|
||||
* @date 2023/08/18
|
||||
*/
|
||||
public class ResponseVO implements Serializable {
|
||||
|
||||
@JsonProperty("return_code")
|
||||
private String code;
|
||||
|
||||
@JsonProperty("return_msg")
|
||||
private String message;
|
||||
|
||||
/** APP用 */
|
||||
@JsonProperty("err_code")
|
||||
private String errCode;
|
||||
|
||||
/** APP用 */
|
||||
@JsonProperty("err_code_des")
|
||||
private String errCodeDes;
|
||||
|
||||
public ResponseVO() {
|
||||
}
|
||||
|
||||
public ResponseVO(String code, String msg) {
|
||||
this.code = code;
|
||||
this.message = msg;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public ResponseVO setCode(String code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public ResponseVO setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getErrCode() {
|
||||
return errCode;
|
||||
}
|
||||
|
||||
public ResponseVO setErrCode(String errCode) {
|
||||
this.errCode = errCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getErrCodeDes() {
|
||||
return errCodeDes;
|
||||
}
|
||||
|
||||
public ResponseVO setErrCodeDes(String errCodeDes) {
|
||||
this.errCodeDes = errCodeDes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static ResponseVO success() {
|
||||
return new ResponseVO(MessageCodes.SUCCESS.code, SymbolsConstant.EMPTY);
|
||||
}
|
||||
|
||||
public static ResponseVO success(String code, String message) {
|
||||
return new ResponseVO(code, message);
|
||||
}
|
||||
|
||||
|
||||
public static ResponseVO fail(String message) {
|
||||
return new ResponseVO(MessageCodes.FAIL.code, message);
|
||||
}
|
||||
|
||||
public static ResponseVO fail4Code(String errorCode) {
|
||||
return new ResponseVO(errorCode, SymbolsConstant.EMPTY);
|
||||
}
|
||||
|
||||
|
||||
public static ResponseVO fail(String errorCode, String message) {
|
||||
return new ResponseVO(errorCode, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
20
src/main/java/top/yexuejc/admin/model/input/LoginInput.java
Normal file
20
src/main/java/top/yexuejc/admin/model/input/LoginInput.java
Normal file
@ -0,0 +1,20 @@
|
||||
package top.yexuejc.admin.model.input;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 19:50
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class LoginInput implements Serializable {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
32
src/main/java/top/yexuejc/admin/model/input/PageInput.java
Normal file
32
src/main/java/top/yexuejc/admin/model/input/PageInput.java
Normal file
@ -0,0 +1,32 @@
|
||||
package top.yexuejc.admin.model.input;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 21:49
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PageInput implements Serializable {
|
||||
|
||||
private int pageIndex;
|
||||
private int pageSize;
|
||||
|
||||
|
||||
public int getPageIndex() {
|
||||
if (pageIndex <= 0) {
|
||||
pageIndex = 1;
|
||||
}
|
||||
return pageIndex;
|
||||
}
|
||||
public int getPageSize() {
|
||||
if (pageSize <= 0) {
|
||||
pageSize = 10;
|
||||
}
|
||||
return pageSize;
|
||||
}
|
||||
}
|
16
src/main/java/top/yexuejc/admin/model/input/UserSearch.java
Normal file
16
src/main/java/top/yexuejc/admin/model/input/UserSearch.java
Normal file
@ -0,0 +1,16 @@
|
||||
package top.yexuejc.admin.model.input;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 21:50
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class UserSearch extends PageInput {
|
||||
private String username;
|
||||
}
|
91
src/main/java/top/yexuejc/admin/model/output/MenuTree.java
Normal file
91
src/main/java/top/yexuejc/admin/model/output/MenuTree.java
Normal file
@ -0,0 +1,91 @@
|
||||
package top.yexuejc.admin.model.output;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import top.yexuejc.admin.entity.Menu;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 20:21
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class MenuTree extends Menu implements Serializable {
|
||||
private List<MenuTree> children = new ArrayList<>();
|
||||
|
||||
public static MenuTree build(Menu menu) {
|
||||
MenuTree menuTree = new MenuTree();
|
||||
menuTree.setId(menu.getId());
|
||||
menuTree.setName(menu.getName());
|
||||
menuTree.setType(menu.getType());
|
||||
menuTree.setTag(menu.getTag());
|
||||
menuTree.setParentId(menu.getParentId());
|
||||
menuTree.setOrderNum(menu.getOrderNum());
|
||||
return menuTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将菜单列表构建成树状结构
|
||||
*
|
||||
* @param menuList 菜单列表
|
||||
* @return 树状菜单结构
|
||||
*/
|
||||
public static List<MenuTree> buildMenuTree(List<Menu> menuList) {
|
||||
// 使用Map存储所有菜单项,便于快速查找
|
||||
Map<Long, MenuTree> menuMap = new HashMap<>();
|
||||
|
||||
// 第一次遍历:创建所有菜单节点
|
||||
for (Menu menu : menuList) {
|
||||
menuMap.put(menu.getId(), MenuTree.build(menu));
|
||||
}
|
||||
|
||||
// 根节点列表
|
||||
List<MenuTree> rootMenus = new ArrayList<>();
|
||||
|
||||
// 第二次遍历:建立父子关系
|
||||
for (Menu menu : menuList) {
|
||||
MenuTree menuTree = menuMap.get(menu.getId());
|
||||
Long parentId = menu.getParentId();
|
||||
|
||||
if (parentId == null || parentId == 0) {
|
||||
// 没有父节点的为根节点
|
||||
rootMenus.add(menuTree);
|
||||
} else {
|
||||
// 有父节点的,建立父子关系
|
||||
MenuTree parentMenuTree = menuMap.get(parentId);
|
||||
if (parentMenuTree != null) {
|
||||
parentMenuTree.getChildren().add(menuTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 对菜单进行排序
|
||||
rootMenus.sort(Comparator.comparing(MenuTree::getOrderNum));
|
||||
sortChildren(rootMenus);
|
||||
|
||||
return rootMenus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归对子菜单进行排序
|
||||
*
|
||||
* @param menus 菜单列表
|
||||
*/
|
||||
private static void sortChildren(List<MenuTree> menus) {
|
||||
for (MenuTree menu : menus) {
|
||||
List<MenuTree> children = menu.getChildren();
|
||||
if (children != null && !children.isEmpty()) {
|
||||
children.sort(Comparator.comparing(MenuTree::getOrderNum));
|
||||
sortChildren(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package top.yexuejc.admin.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import top.yexuejc.admin.entity.Menu;
|
||||
import top.yexuejc.admin.entity.Role;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 20:01
|
||||
*/
|
||||
public interface MenuRepository extends JpaRepository<Menu, Long> {
|
||||
@Query("select m from Menu m, RoleMenu rm, UserRole ur where m.id = rm.menuId and rm.roleId = ur.roleId and ur.userId = ?1")
|
||||
List<Menu> findAllByUserId(Long userId);
|
||||
|
||||
@Query("select m from Menu m, RoleMenu rm where m.id = rm.menuId and rm.roleId = ?1")
|
||||
List<Menu> findAllByRoleId(Long roleId);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package top.yexuejc.admin.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import top.yexuejc.admin.entity.Role;
|
||||
import top.yexuejc.admin.entity.UserRole;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 20:02
|
||||
*/
|
||||
public interface RoleRepository extends JpaRepository<Role, Long> {
|
||||
|
||||
@Query("select r from Role r, UserRole ur where r.id = ur.roleId and ur.userId = ?1")
|
||||
List<Role> findAllByUserId(Long userId);
|
||||
|
||||
List<Role> findByLevelGreaterThanEqual(int level);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package top.yexuejc.admin.repository;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import top.yexuejc.admin.entity.User;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 20:00
|
||||
*/
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User findByUsername(String username);
|
||||
|
||||
Page<User> findByUsernameContaining(String username, Pageable pageable);
|
||||
}
|
27
src/main/java/top/yexuejc/admin/util/HttpUtil.java
Normal file
27
src/main/java/top/yexuejc/admin/util/HttpUtil.java
Normal file
@ -0,0 +1,27 @@
|
||||
package top.yexuejc.admin.util;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.yexuejc.admin.entity.User;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/30 13:30
|
||||
*/
|
||||
public class HttpUtil {
|
||||
|
||||
public static User getLoginUser() {
|
||||
User user = null;
|
||||
try {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (null != attributes) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
user = (User) request.getSession().getAttribute("user");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignored Exception
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
106
src/main/java/top/yexuejc/admin/web/LoginCtrl.java
Normal file
106
src/main/java/top/yexuejc/admin/web/LoginCtrl.java
Normal file
@ -0,0 +1,106 @@
|
||||
package top.yexuejc.admin.web;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.yexuejc.base.util.JsonUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.yexuejc.admin.entity.Menu;
|
||||
import top.yexuejc.admin.entity.User;
|
||||
import top.yexuejc.admin.model.ResponseVO;
|
||||
import top.yexuejc.admin.model.input.LoginInput;
|
||||
import top.yexuejc.admin.model.output.MenuTree;
|
||||
import top.yexuejc.admin.repository.MenuRepository;
|
||||
import top.yexuejc.admin.repository.UserRepository;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:56
|
||||
*/
|
||||
@RestController
|
||||
public class LoginCtrl {
|
||||
private static final Logger log = LoggerFactory.getLogger(LoginCtrl.class);
|
||||
@Resource
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Resource
|
||||
private MenuRepository menuRepository;
|
||||
|
||||
/**
|
||||
* 登录处理
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/login", consumes = "application/x-www-form-urlencoded")
|
||||
public ResponseVO login(@RequestParam String username, @RequestParam String password, HttpServletRequest request, HttpServletResponse response) {
|
||||
return login(new LoginInput().setUsername(username).setPassword(password), request, response);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/login", consumes = "application/json")
|
||||
public ResponseVO login(@RequestBody @Validated LoginInput loginInput, HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpSession session = request.getSession();
|
||||
// 用户存在
|
||||
User user = userRepository.findByUsername(loginInput.getUsername());
|
||||
if (user == null) {
|
||||
return ResponseVO.fail("用户不存在");
|
||||
}
|
||||
// 密码正确
|
||||
if (!user.getPassword().equals(loginInput.getPassword())) {
|
||||
return ResponseVO.fail("密码错误");
|
||||
}
|
||||
// 用户状态正常
|
||||
if (user.getStatus() != 1) {
|
||||
return ResponseVO.fail("用户状态异常");
|
||||
}
|
||||
// 登录成功
|
||||
session.setAttribute("user", user);
|
||||
setCookie("user", JsonUtil.obj2Json(user), response);
|
||||
|
||||
// 获取用户角色=>菜单
|
||||
List<Menu> menuList = menuRepository.findAllByUserId(user.getId());
|
||||
if (menuList == null || menuList.isEmpty()) {
|
||||
return ResponseVO.success();
|
||||
}
|
||||
// 通过Menu.parentId来整理菜单为树状结构
|
||||
List<MenuTree> menuTree = MenuTree.buildMenuTree(menuList);
|
||||
session.setAttribute("menuTree", menuTree);
|
||||
// 把menuTree设置到cookie
|
||||
setCookie("menuTree", JsonUtil.obj2Json(menuTree), response);
|
||||
System.out.println(JsonUtil.obj2Json(user));
|
||||
System.out.println(JsonUtil.obj2Json(menuTree));
|
||||
return ResponseVO.success();
|
||||
}
|
||||
|
||||
private static void setCookie(String name, String value, HttpServletResponse response) {
|
||||
log.info("setCookie name:{},value:{}", name, value);
|
||||
Cookie cookie = new Cookie(name, URLEncoder.encode(value, StandardCharsets.UTF_8));
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(30 * 60); // 设置cookie有效期为30分钟
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping("/logout")
|
||||
public ResponseVO lougout(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession();
|
||||
session.invalidate();
|
||||
return ResponseVO.success();
|
||||
}
|
||||
}
|
53
src/main/java/top/yexuejc/admin/web/MenuCtrl.java
Normal file
53
src/main/java/top/yexuejc/admin/web/MenuCtrl.java
Normal file
@ -0,0 +1,53 @@
|
||||
package top.yexuejc.admin.web;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.yexuejc.base.util.JsonUtil;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.yexuejc.admin.model.ListResponseVO;
|
||||
import top.yexuejc.admin.model.output.MenuTree;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 21:15
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/menu")
|
||||
public class MenuCtrl {
|
||||
|
||||
@RequestMapping("/list")
|
||||
public ListResponseVO<MenuTree> getMenuTree(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession();
|
||||
List<MenuTree> menuTree = (List<MenuTree>) session.getAttribute("menuTree");
|
||||
if (menuTree == null) {
|
||||
//从cookie中获取
|
||||
menuTree = getByCookie(request);
|
||||
}
|
||||
return ListResponseVO.success(menuTree);
|
||||
}
|
||||
|
||||
private List<MenuTree> getByCookie(HttpServletRequest request) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if ("menuTree".equals(cookie.getName())) {
|
||||
try {
|
||||
String menuTreeJson = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8);
|
||||
return JsonUtil.json2Obj(menuTreeJson, new TypeReference<List<MenuTree>>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
33
src/main/java/top/yexuejc/admin/web/PageCtrl.java
Normal file
33
src/main/java/top/yexuejc/admin/web/PageCtrl.java
Normal file
@ -0,0 +1,33 @@
|
||||
package top.yexuejc.admin.web;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 画面跳转
|
||||
*
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 18:58
|
||||
*/
|
||||
@Controller
|
||||
public class PageCtrl {
|
||||
@RequestMapping("/view/{uri}")
|
||||
public ModelAndView index(@PathVariable String uri, @RequestParam Map<String, String> params) {
|
||||
ModelAndView mv = new ModelAndView("view/" + uri);
|
||||
mv.addObject("params", params);
|
||||
return mv;
|
||||
}
|
||||
|
||||
@RequestMapping("/v/{*uri}")
|
||||
public ModelAndView bizPage(@PathVariable String uri, @RequestParam Map<String, String> params) {
|
||||
ModelAndView mv = new ModelAndView(uri);
|
||||
mv.addObject("params", params);
|
||||
return mv;
|
||||
}
|
||||
|
||||
}
|
56
src/main/java/top/yexuejc/admin/web/RoleCtrl.java
Normal file
56
src/main/java/top/yexuejc/admin/web/RoleCtrl.java
Normal file
@ -0,0 +1,56 @@
|
||||
package top.yexuejc.admin.web;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.yexuejc.admin.entity.Menu;
|
||||
import top.yexuejc.admin.entity.Role;
|
||||
import top.yexuejc.admin.entity.User;
|
||||
import top.yexuejc.admin.exception.AppException;
|
||||
import top.yexuejc.admin.model.ListResponseVO;
|
||||
import top.yexuejc.admin.model.output.MenuTree;
|
||||
import top.yexuejc.admin.repository.MenuRepository;
|
||||
import top.yexuejc.admin.repository.RoleRepository;
|
||||
import top.yexuejc.admin.util.HttpUtil;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/30 10:38
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/role")
|
||||
public class RoleCtrl {
|
||||
@Resource
|
||||
private RoleRepository roleRepository;
|
||||
@Resource
|
||||
private MenuRepository menuRepository;
|
||||
|
||||
@RequestMapping("/list")
|
||||
public ListResponseVO<Role> getRoleList() throws AppException {
|
||||
User loginUser = HttpUtil.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
throw new AppException("请先登录");
|
||||
}
|
||||
//获取用户角色中等级最高的角色
|
||||
List<Role> roleList = roleRepository.findAllByUserId(loginUser.getId());
|
||||
int minLevel =
|
||||
Optional.ofNullable(roleList).orElse(Collections.emptyList()).stream().min(Comparator.comparingInt(Role::getLevel)).map(Role::getLevel).orElse(99);
|
||||
// 获取level大于等于minLevel的所有角色
|
||||
List<Role> all = roleRepository.findByLevelGreaterThanEqual(minLevel);
|
||||
return ListResponseVO.success(all);
|
||||
}
|
||||
|
||||
@RequestMapping("/{id}/menus")
|
||||
public ListResponseVO<MenuTree> getMenusByRoleId(@PathVariable Long id) {
|
||||
List<Menu> menuList = menuRepository.findAllByRoleId(id);
|
||||
//根据菜单的parentId来整理菜单为树状结构
|
||||
List<MenuTree> menuTree = MenuTree.buildMenuTree(menuList);
|
||||
return ListResponseVO.success(menuTree);
|
||||
}
|
||||
}
|
40
src/main/java/top/yexuejc/admin/web/UserCtrl.java
Normal file
40
src/main/java/top/yexuejc/admin/web/UserCtrl.java
Normal file
@ -0,0 +1,40 @@
|
||||
package top.yexuejc.admin.web;
|
||||
|
||||
import com.yexuejc.base.util.JsonUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.yexuejc.admin.entity.User;
|
||||
import top.yexuejc.admin.model.PageResponseVO;
|
||||
import top.yexuejc.admin.model.input.UserSearch;
|
||||
import top.yexuejc.admin.repository.UserRepository;
|
||||
|
||||
/**
|
||||
* @author maxiaofeng
|
||||
* @date 2025/7/29 21:48
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserCtrl {
|
||||
@Resource
|
||||
private UserRepository userRepository;
|
||||
|
||||
|
||||
@RequestMapping("/list")
|
||||
public PageResponseVO<User> getUserList(UserSearch userSearch) {
|
||||
Sort sortComment = Sort.by(Sort.Order.asc("id"));
|
||||
Pageable pageableComment = PageRequest.of(userSearch.getPageIndex() - 1, userSearch.getPageSize(), sortComment);
|
||||
Page<User> all;
|
||||
if (userSearch.getUsername() != null && !userSearch.getUsername().isEmpty()) {
|
||||
all = userRepository.findByUsernameContaining(userSearch.getUsername(), pageableComment);
|
||||
} else {
|
||||
all = userRepository.findAll(pageableComment);
|
||||
}
|
||||
System.out.println(JsonUtil.obj2Json(all));
|
||||
return PageResponseVO.of(all.getContent(), all.getTotalElements());
|
||||
}
|
||||
}
|
50
src/main/resources/application.properties
Normal file
50
src/main/resources/application.properties
Normal file
@ -0,0 +1,50 @@
|
||||
spring.application.name=permission-role-management-system
|
||||
|
||||
# Thymeleaf \u914D\u7F6E
|
||||
spring.thymeleaf.cache=false
|
||||
spring.thymeleaf.prefix=classpath:/templates/
|
||||
spring.thymeleaf.suffix=.html
|
||||
spring.thymeleaf.mode=HTML
|
||||
spring.thymeleaf.encoding=UTF-8
|
||||
# \u8BBE\u5B9A\u9759\u6001\u8D44\u6E90\u7F13\u5B58(\u9700\u8981\u7F13\u5B58\u4F7F\u7528false)
|
||||
spring.web.resources.chain.strategy.content.enabled=false
|
||||
spring.web.resources.chain.strategy.content.paths=/**
|
||||
# \u5173\u95ED\u4E25\u683C\u8D44\u6E90\u68C0\u67E5
|
||||
spring.mvc.static-path-pattern=/**
|
||||
|
||||
# \u591A\u8BED\u8A00
|
||||
spring.messages.basename=classpath:i18n/messages,classpath:i18n/layout
|
||||
spring.messages.encoding=UTF-8
|
||||
spring.messages.fallback-to-system-locale=true
|
||||
default.locale=ja-JP
|
||||
|
||||
# DB
|
||||
spring.datasource.url=jdbc:mysql://192.168.5.125:3308/test_role?useUnicode=true&characterEncoding=UTF-8
|
||||
spring.datasource.username=intasect
|
||||
spring.datasource.password=2$hGv1Dz[3)J7]BS
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.datasource.hikari.maximum-pool-size=300
|
||||
spring.datasource.hikari.minimum-idle=20
|
||||
spring.datasource.hikari.connection-timeout=10000
|
||||
spring.datasource.hikari.idle-timeout=10000
|
||||
|
||||
# log
|
||||
#\u914D\u7F6E\u5168\u5C40\u8F93\u51FA\u7EA7\u522B off<trace<debug<info<warn<error<fatal
|
||||
logging.level.root=info
|
||||
logging.level.jp.co.ppih.taxrefund=debug
|
||||
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{hashId}] [%thread] [%-5level] %c.%M:%L - %msg%n
|
||||
logging.charset.console=UTF-8
|
||||
#\u914D\u7F6E\u65E5\u5FD7\u6587\u4EF6\u683C\u5F0F
|
||||
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{hashId}] [%thread] [%-5level] %c.%M:%L - %msg%n
|
||||
# \u65E5\u5FD7\u6587\u4EF6\u6700\u5927\u5927\u5C0F
|
||||
logging.logback.rollingpolicy.max-file-size=10GB
|
||||
logging.file.path=/opt/logs/${spring.application.name}/
|
||||
logging.file.name=${logging.file.path}${spring.application.name}.log
|
||||
|
||||
# jpa
|
||||
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
|
||||
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
|
@ -0,0 +1,3 @@
|
||||
Bootstrap v5.3.7
|
||||
替换了主题色(primary)
|
||||
#0d6efd -> #337ab7
|
4085
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.css
vendored
Normal file
4085
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4084
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.rtl.css
vendored
Normal file
4084
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.rtl.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-grid.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
597
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.css
vendored
Normal file
597
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.css
vendored
Normal file
@ -0,0 +1,597 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.7 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #337ab7;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #337ab7;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #337ab7;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: inherit;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
594
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.rtl.css
vendored
Normal file
594
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.rtl.css
vendored
Normal file
@ -0,0 +1,594 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.7 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #337ab7;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #337ab7;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #337ab7;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: inherit;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5406
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.css
vendored
Normal file
5406
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5397
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.rtl.css
vendored
Normal file
5397
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12043
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.css
vendored
Normal file
12043
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12016
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.rtl.css
vendored
Normal file
12016
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.rtl.min.css
vendored
Normal file
6
src/main/resources/static/plugin/bootstrap-5.3.7-dist/css/bootstrap.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6315
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.js
vendored
Normal file
6315
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js
vendored
Normal file
7
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4450
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.esm.js
vendored
Normal file
4450
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.esm.min.js
vendored
Normal file
7
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4497
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.js
vendored
Normal file
4497
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.min.js
vendored
Normal file
7
src/main/resources/static/plugin/bootstrap-5.3.7-dist/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
src/main/resources/static/plugin/jquery-3.7.1/jquery-3.7.1.min.js
vendored
Normal file
2
src/main/resources/static/plugin/jquery-3.7.1/jquery-3.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
102
src/main/resources/templates/menu/menu-manage.html
Normal file
102
src/main/resources/templates/menu/menu-manage.html
Normal file
@ -0,0 +1,102 @@
|
||||
<!-- templates/view/permission-manage.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>菜单管理</title>
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="content">
|
||||
<h2>菜单管理</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">菜单列表</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>菜单名称</th>
|
||||
<th>菜单类型</th>
|
||||
<th>资源值</th>
|
||||
<th>上级菜单</th>
|
||||
<th>排序</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>用户管理</td>
|
||||
<td>1(MENU)</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>用户管理</td>
|
||||
<td>2(PATH)</td>
|
||||
<td>user/user-manage</td>
|
||||
<td>1</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>用户管理</td>
|
||||
<td>3(BTN)</td>
|
||||
<td>create</td>
|
||||
<td>2</td>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>用户管理</td>
|
||||
<td>3(BTN)</td>
|
||||
<td>update</td>
|
||||
<td>2</td>
|
||||
<td>2</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>用户管理</td>
|
||||
<td>3(BTN)</td>
|
||||
<td>delete</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
280
src/main/resources/templates/role/role-add.html
Normal file
280
src/main/resources/templates/role/role-add.html
Normal file
@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>添加用户</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="../../static/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css"
|
||||
th:href="@{/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css}"
|
||||
rel="stylesheet">
|
||||
<!-- jQuery -->
|
||||
<script src="../../static/plugin/jquery-3.7.1/jquery-3.7.1.min.js"
|
||||
th:src="@{/plugin/jquery-3.7.1/jquery-3.7.1.min.js}"></script>
|
||||
<script src="../../static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js"
|
||||
th:src="@{/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js}"></script>
|
||||
|
||||
</head>
|
||||
<body th:fragment="content">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addRoleModalLabel">添加角色</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="addRoleForm">
|
||||
<div class="mb-3">
|
||||
<label for="addRoleName" class="form-label">角色名称</label>
|
||||
<input type="text" class="form-control" id="addRoleName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addRoleLevel" class="form-label">角色级别</label>
|
||||
<input type="number" class="form-control" id="addRoleLevel" name="level" required>
|
||||
<div class="form-text">数值越小权限越高</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addRoleDescription" class="form-label">角色描述</label>
|
||||
<textarea class="form-control" id="addRoleDescription" name="description" rows="3"></textarea>
|
||||
</div>
|
||||
<!-- 菜单权限选择 -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">菜单权限</label>
|
||||
<div id="menuTreeContainer" class="border p-3 rounded">
|
||||
<div id="menuTree"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="saveBtn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// 加载菜单树
|
||||
function addRoleInit() {
|
||||
$.ajax({
|
||||
url: '/menu/list',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.code === 200 && res.data) {
|
||||
renderMenuTree(res.data);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('加载菜单失败:', error);
|
||||
alert('加载菜单列表失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染带复选框的菜单树
|
||||
function renderMenuTree(menus) {
|
||||
const menuTree = $('#menuTree');
|
||||
menuTree.empty();
|
||||
|
||||
if (!menus || menus.length === 0) {
|
||||
menuTree.html('<p class="text-muted">暂无菜单数据</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
const treeHtml = buildMenuTreeWithCheckbox(menus);
|
||||
menuTree.html(treeHtml);
|
||||
|
||||
// 绑定复选框事件
|
||||
bindCheckboxEvents();
|
||||
}
|
||||
|
||||
// 构建带复选框的菜单树HTML
|
||||
function buildMenuTreeWithCheckbox(menus, level = 0, parentIds = []) {
|
||||
let html = '<ul class="list-group">';
|
||||
|
||||
menus.forEach(function(menu) {
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const paddingLeft = level * 20;
|
||||
const currentIds = [...parentIds, menu.id];
|
||||
const uniqueId = 'menu_' + currentIds.join('_');
|
||||
|
||||
html += '<li class="list-group-item border-0" style="padding-left: ' + paddingLeft + 'px;">';
|
||||
html += '<div class="form-check">';
|
||||
html += '<input class="form-check-input menu-checkbox" type="checkbox" value="' + menu.id + '" id="' + uniqueId + '" data-menu-id="' + menu.id + '">';
|
||||
html += '<label class="form-check-label d-flex align-items-center" for="' + uniqueId + '">';
|
||||
html += '<span class="me-2">';
|
||||
html += hasChildren ? '📂' : '📄';
|
||||
html += '</span>';
|
||||
html += '<span>' + menu.name + '</span>';
|
||||
html += '</label>';
|
||||
html += '</div>';
|
||||
|
||||
if (hasChildren) {
|
||||
html += buildMenuTreeWithCheckbox(menu.children, level + 1, currentIds);
|
||||
}
|
||||
|
||||
html += '</li>';
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 绑定复选框事件
|
||||
function bindCheckboxEvents() {
|
||||
// 父级选中时,子级也选中;父级取消时,子级也取消
|
||||
$('.menu-checkbox').change(function() {
|
||||
const isChecked = $(this).prop('checked');
|
||||
const menuId = $(this).data('menu-id');
|
||||
|
||||
// 查找所有子级复选框并设置相同状态
|
||||
$(this).closest('li').find('ul .menu-checkbox').prop('checked', isChecked);
|
||||
|
||||
// 如果选中,向上查找父级并选中
|
||||
if (isChecked) {
|
||||
$(this).parents('li').find('> div > .menu-checkbox:first').prop('checked', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取选中的菜单ID列表
|
||||
function getSelectedMenuIds() {
|
||||
const selectedIds = [];
|
||||
$('.menu-checkbox:checked').each(function() {
|
||||
selectedIds.push($(this).val());
|
||||
});
|
||||
return selectedIds;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
// 保存角色
|
||||
$('#saveRoleBtn').click(function() {
|
||||
const formData = {
|
||||
name: $('#addRoleName').val(),
|
||||
level: $('#addRoleLevel').val(),
|
||||
description: $('#addRoleDescription').val()
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/role/add',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(formData),
|
||||
success: function(res) {
|
||||
if (res.code === 200) {
|
||||
alert('角色添加成功');
|
||||
$('#addRoleModal').modal('hide');
|
||||
// 刷新角色列表
|
||||
location.reload();
|
||||
} else {
|
||||
alert('角色添加失败: ' + res.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('添加角色失败:', error);
|
||||
alert('添加角色失败');
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
function addRoleInit() {
|
||||
$.ajax({
|
||||
url: '/menu/list',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
var roleSelect = $('#addRoleId');
|
||||
roleSelect.empty();
|
||||
roleSelect.append('<option value="">请选择角色</option>');
|
||||
|
||||
if (res.data && res.data.length > 0) {
|
||||
$.each(res.data, function (index, role) {
|
||||
roleSelect.append('<option value="' + role.id + '">' + role.name + '</option>');
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('加载角色失败:', error);
|
||||
alert('加载角色列表失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取角色菜单树
|
||||
function loadRoleMenus(roleId) {
|
||||
if (!roleId) {
|
||||
$('#menuTreeContainer').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/role/' + roleId + '/menus',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.return_code === "SUCCESS" && res.data) {
|
||||
renderMenuTree(res.data);
|
||||
$('#menuTreeContainer').show();
|
||||
} else {
|
||||
$('#menuTreeContainer').hide();
|
||||
console.warn('未获取到菜单数据');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('加载菜单失败:', error);
|
||||
$('#menuTreeContainer').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染菜单树
|
||||
function renderMenuTree(menus) {
|
||||
const menuTree = $('#menuTree');
|
||||
menuTree.empty();
|
||||
|
||||
if (!menus || menus.length === 0) {
|
||||
menuTree.html('<p class="text-muted">该角色暂无菜单权限</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
const treeHtml = buildMenuTree(menus);
|
||||
menuTree.html(treeHtml);
|
||||
}
|
||||
|
||||
// 构建菜单树HTML
|
||||
function buildMenuTree(menus, level = 0) {
|
||||
let html = '<ul class="list-group">';
|
||||
|
||||
menus.forEach(function (menu) {
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const paddingLeft = level * 20;
|
||||
|
||||
html += '<li class="list-group-item border-0" style="padding-left: ' + paddingLeft + 'px;">';
|
||||
html += '<span class="d-flex align-items-center">';
|
||||
html += '<span class="me-2">';
|
||||
html += hasChildren ? '📂' : '📄';
|
||||
html += '</span>';
|
||||
html += '<span>' + menu.name + '</span>';
|
||||
html += '</span>';
|
||||
|
||||
if (hasChildren) {
|
||||
html += buildMenuTree(menu.children, level + 1);
|
||||
}
|
||||
|
||||
html += '</li>';
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 绑定角色选择事件
|
||||
$(document).on('change', '#addRoleId', function () {
|
||||
const roleId = $(this).val();
|
||||
loadRoleMenus(roleId);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
150
src/main/resources/templates/role/role-manage.html
Normal file
150
src/main/resources/templates/role/role-manage.html
Normal file
@ -0,0 +1,150 @@
|
||||
<!-- templates/view/role-manage.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>角色管理</title>
|
||||
<script>
|
||||
$(function (){
|
||||
$.ajax({
|
||||
url: '/role/list',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
setData(res.data)
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('请求失败:', error);
|
||||
}
|
||||
});
|
||||
// 设置表格数据
|
||||
function setData(datas) {
|
||||
var tbody = $('tbody');
|
||||
tbody.empty(); // 清空现有数据
|
||||
|
||||
if (!datas || datas.length === 0) {
|
||||
tbody.append('<tr><td colspan="5" class="text-center">暂无数据</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历数据并添加到表格
|
||||
$.each(datas, function (index, role) {
|
||||
var row = '<tr>' +
|
||||
'<td>' + (role.id || '') + '</td>' +
|
||||
'<td>' + (role.name || '') + '</td>' +
|
||||
'<td>' + (role.level || '') + '</td>' +
|
||||
'<td>' + (role.description || '') + '</td>' +
|
||||
'<td>' + (role.createTime || '') + '</td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn-sm btn-primary edit-btn" data-id="' + role.id + '">编辑</button> ' +
|
||||
'<button class="btn btn-sm btn-danger delete-btn" data-id="' + role.id + '">删除</button>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
tbody.append(row);
|
||||
});
|
||||
|
||||
// 绑定编辑和删除按钮事件
|
||||
$('.edit-btn').click(function () {
|
||||
var userId = $(this).data('id');
|
||||
editUser(userId);
|
||||
});
|
||||
|
||||
$('.delete-btn').click(function () {
|
||||
var userId = $(this).data('id');
|
||||
deleteUser(userId);
|
||||
});
|
||||
|
||||
// 编辑用户
|
||||
function editUser(userId) {
|
||||
alert('编辑用户功能待实现,用户ID: ' + userId);
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
function deleteUser(userId) {
|
||||
if (confirm('确定要删除该用户吗?')) {
|
||||
alert('删除用户功能待实现,用户ID: ' + userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打开添加用户模态框
|
||||
$('#addBtn').click(function () {
|
||||
addRoleInit(); // 加载角色列表
|
||||
$('#addRoleForm')[0].reset(); // 重置表单
|
||||
$('#addRoleModal').modal('show'); // 显示模态框
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="content">
|
||||
<h2>角色管理</h2>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<input name="username" type="text" class="form-control" placeholder="搜索角色名...">
|
||||
<button class="btn btn-outline-secondary" type="button" id="searchBtn">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<button class="btn btn-primary" type="button" id="addBtn">添加角色</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">角色列表</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>角色名称</th>
|
||||
<th>角色级别</th>
|
||||
<th>角色描述</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>管理员</td>
|
||||
<td>1</td>
|
||||
<td>系统管理员,拥有所有权限</td>
|
||||
<td>2023-01-01</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>普通用户</td>
|
||||
<td>2</td>
|
||||
<td>普通用户,拥有基本权限</td>
|
||||
<td>2023-01-01</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="addRoleModal" tabindex="-1" aria-labelledby="addRoleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div th:include="role/role-add::content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
166
src/main/resources/templates/user/user-add.html
Normal file
166
src/main/resources/templates/user/user-add.html
Normal file
@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>添加用户</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="../../static/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css"
|
||||
th:href="@{/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css}"
|
||||
rel="stylesheet">
|
||||
<!-- jQuery -->
|
||||
<script src="../../static/plugin/jquery-3.7.1/jquery-3.7.1.min.js"
|
||||
th:src="@{/plugin/jquery-3.7.1/jquery-3.7.1.min.js}"></script>
|
||||
<script src="../../static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js"
|
||||
th:src="@{/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js}"></script>
|
||||
|
||||
</head>
|
||||
<body th:fragment="content">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addUserModalLabel">添加用户</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="addUserForm">
|
||||
<!-- 表单字段保持不变 -->
|
||||
<div class="mb-3">
|
||||
<label for="addUsername" class="form-label">用户名</label>
|
||||
<input type="text" class="form-control" id="addUsername" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addEmail" class="form-label">邮箱</label>
|
||||
<input type="email" class="form-control" id="addEmail" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addPassword" class="form-label">密码</label>
|
||||
<input type="password" class="form-control" id="addPassword" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addRoleId" class="form-label">角色</label>
|
||||
<select class="form-select" id="addRoleId" name="roleId" required>
|
||||
<option value="">请选择角色</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- 添加菜单树展示区域 -->
|
||||
<div class="mb-3" id="menuTreeContainer" style="display: none;">
|
||||
<label class="form-label">角色菜单权限</label>
|
||||
<div id="menuTree" class="border p-3 rounded"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addStatus" class="form-label">状态</label>
|
||||
<select class="form-select" id="addStatus" name="status" required>
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="saveUserBtn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<script> // 加载角色列表
|
||||
function addUserInit() {
|
||||
$.ajax({
|
||||
url: '/role/list',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
var roleSelect = $('#addRoleId');
|
||||
roleSelect.empty();
|
||||
roleSelect.append('<option value="">请选择角色</option>');
|
||||
|
||||
if (res.data && res.data.length > 0) {
|
||||
$.each(res.data, function (index, role) {
|
||||
roleSelect.append('<option value="' + role.id + '">' + role.name + '</option>');
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('加载角色失败:', error);
|
||||
alert('加载角色列表失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取角色菜单树
|
||||
function loadRoleMenus(roleId) {
|
||||
if (!roleId) {
|
||||
$('#menuTreeContainer').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/role/' + roleId + '/menus',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.return_code === "SUCCESS" && res.data) {
|
||||
renderMenuTree(res.data);
|
||||
$('#menuTreeContainer').show();
|
||||
} else {
|
||||
$('#menuTreeContainer').hide();
|
||||
console.warn('未获取到菜单数据');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('加载菜单失败:', error);
|
||||
$('#menuTreeContainer').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染菜单树
|
||||
function renderMenuTree(menus) {
|
||||
const menuTree = $('#menuTree');
|
||||
menuTree.empty();
|
||||
|
||||
if (!menus || menus.length === 0) {
|
||||
menuTree.html('<p class="text-muted">该角色暂无菜单权限</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
const treeHtml = buildMenuTree(menus);
|
||||
menuTree.html(treeHtml);
|
||||
}
|
||||
|
||||
// 构建菜单树HTML
|
||||
function buildMenuTree(menus, level = 0) {
|
||||
let html = '<ul class="list-group">';
|
||||
|
||||
menus.forEach(function (menu) {
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const paddingLeft = level * 20;
|
||||
|
||||
html += '<li class="list-group-item border-0" style="padding-left: ' + paddingLeft + 'px;">';
|
||||
html += '<span class="d-flex align-items-center">';
|
||||
html += '<span class="me-2">';
|
||||
html += hasChildren ? '📂' : '📄';
|
||||
html += '</span>';
|
||||
html += '<span>' + menu.name + '</span>';
|
||||
html += '</span>';
|
||||
|
||||
if (hasChildren) {
|
||||
html += buildMenuTree(menu.children, level + 1);
|
||||
}
|
||||
|
||||
html += '</li>';
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 绑定角色选择事件
|
||||
$(document).on('change', '#addRoleId', function () {
|
||||
const roleId = $(this).val();
|
||||
loadRoleMenus(roleId);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
166
src/main/resources/templates/user/user-manage.html
Normal file
166
src/main/resources/templates/user/user-manage.html
Normal file
@ -0,0 +1,166 @@
|
||||
<!-- templates/view/user-manage.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>用户管理</title>
|
||||
<script>
|
||||
$(function () {
|
||||
$.ajax({
|
||||
url: '/user/list',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
setData(res.data)
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('请求失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
$("#searchBtn").click(function () {
|
||||
var username = $('input[name = "username"]').val();
|
||||
$.ajax({
|
||||
url: '/user/list',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
username: username,
|
||||
},
|
||||
success: function (res) {
|
||||
setData(res.data)
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('请求失败:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 设置表格数据
|
||||
function setData(datas) {
|
||||
var tbody = $('tbody');
|
||||
tbody.empty(); // 清空现有数据
|
||||
|
||||
if (!datas || datas.length === 0) {
|
||||
tbody.append('<tr><td colspan="5" class="text-center">暂无数据</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历数据并添加到表格
|
||||
$.each(datas, function (index, user) {
|
||||
var row = '<tr>' +
|
||||
'<td>' + (user.username || '') + '</td>' +
|
||||
'<td>' + (user.email || '') + '</td>' +
|
||||
'<td>' + (user.roleName || '') + '</td>' +
|
||||
'<td>' + (user.status === 1 ? '启用' : '禁用') + '</td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn-sm btn-primary edit-btn" data-id="' + user.id + '">编辑</button> ' +
|
||||
'<button class="btn btn-sm btn-danger delete-btn" data-id="' + user.id + '">删除</button>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
tbody.append(row);
|
||||
});
|
||||
|
||||
// 绑定编辑和删除按钮事件
|
||||
$('.edit-btn').click(function () {
|
||||
var userId = $(this).data('id');
|
||||
editUser(userId);
|
||||
});
|
||||
|
||||
$('.delete-btn').click(function () {
|
||||
var userId = $(this).data('id');
|
||||
deleteUser(userId);
|
||||
});
|
||||
|
||||
// 编辑用户
|
||||
function editUser(userId) {
|
||||
alert('编辑用户功能待实现,用户ID: ' + userId);
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
function deleteUser(userId) {
|
||||
if (confirm('确定要删除该用户吗?')) {
|
||||
alert('删除用户功能待实现,用户ID: ' + userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打开添加用户模态框
|
||||
$('#addBtn').click(function () {
|
||||
addUserInit(); // 加载角色列表
|
||||
$('#addUserForm')[0].reset(); // 重置表单
|
||||
$('#addUserModal').modal('show'); // 显示模态框
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="content">
|
||||
<h2>用户管理</h2>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<input name="username" type="text" class="form-control" placeholder="搜索用户名...">
|
||||
<button class="btn btn-outline-secondary" type="button" id="searchBtn">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<button class="btn btn-primary" type="button" id="addBtn">添加用户</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">用户列表</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>角色</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>admin</td>
|
||||
<td>admin@example.com</td>
|
||||
<td>管理员</td>
|
||||
<td>启用</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>user</td>
|
||||
<td>user@example.com</td>
|
||||
<td>普通用户</td>
|
||||
<td>启用</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">编辑</button>
|
||||
<button class="btn btn-sm btn-danger">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div th:include="user/user-add::content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
288
src/main/resources/templates/view/index.html
Normal file
288
src/main/resources/templates/view/index.html
Normal file
@ -0,0 +1,288 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>首页</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="../static/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css"
|
||||
th:href="@{/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css}"
|
||||
rel="stylesheet">
|
||||
<!-- jQuery -->
|
||||
<script src="../static/plugin/jquery-3.7.1/jquery-3.7.1.min.js"
|
||||
th:src="@{/plugin/jquery-3.7.1/jquery-3.7.1.min.js}"></script>
|
||||
<style>
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
height: calc(100vh - 56px);
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 200px;
|
||||
margin-top: 56px;
|
||||
padding: 20px;
|
||||
height: calc(100vh - 56px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background-color: #0d6efd;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 二级菜单样式 */
|
||||
.submenu {
|
||||
padding-left: 1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.submenu .list-group-item {
|
||||
border-left: 3px solid #0d6efd;
|
||||
}
|
||||
|
||||
.menu-has-children::after {
|
||||
content: "▶";
|
||||
float: right;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.menu-has-children.active::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.menu-has-children.active + .submenu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 菜单项间距调整 */
|
||||
.menu-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 顶部导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">权限管理系统</a>
|
||||
<div class="d-flex">
|
||||
<span class="navbar-text text-white me-3" id="currentUser"></span>
|
||||
<button class="btn btn-outline-light btn-sm" id="logoutBtn">退出</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div class="d-flex">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="sidebar bg-light">
|
||||
<div class="list-group list-group-flush mt-3" id="menuContainer">
|
||||
<!-- 菜单将通过AJAX动态加载 -->
|
||||
<div class="loading">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">菜单加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="content flex-grow-1">
|
||||
<div id="content-container">
|
||||
<!-- 默认加载欢迎页内容 -->
|
||||
<div class="loading">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="../static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js"
|
||||
th:src="@{/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js}"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// 页面加载完成后默认加载欢迎页
|
||||
loadContent('/view/welcome');
|
||||
|
||||
// 加载菜单
|
||||
loadMenu();
|
||||
|
||||
// 退出登录
|
||||
$('#logoutBtn').on('click', function () {
|
||||
window.location.href = '/logout';
|
||||
});
|
||||
|
||||
// 获取当前用户信息(模拟)
|
||||
$('#currentUser').text('当前用户: admin');
|
||||
|
||||
// 加载内容的函数
|
||||
function loadContent(url) {
|
||||
// 显示加载动画
|
||||
$('#content-container').html(`
|
||||
<div class="loading">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// 使用AJAX加载内容
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
success: function (data) {
|
||||
$('#content-container').html(data);
|
||||
},
|
||||
error: function () {
|
||||
$('#content-container').html(`
|
||||
<div class="alert alert-danger">
|
||||
内容加载失败,请稍后重试。
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载菜单的函数
|
||||
function loadMenu() {
|
||||
$.ajax({
|
||||
url: '/menu/list',
|
||||
type: 'GET',
|
||||
success: function (res) {
|
||||
let menus = res.data;
|
||||
if (menus.length > 0 && menus[0].id) {
|
||||
renderMenu(menus);
|
||||
// 存储到本地存储中
|
||||
localStorage.setItem('menuTree', JSON.stringify(menus));
|
||||
} else {
|
||||
let menus = localStorage.getItem('menuTree');
|
||||
if (menus) {
|
||||
menus = JSON.parse(menus);
|
||||
renderMenu(menus);
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定菜单事件
|
||||
bindMenuEvents();
|
||||
},
|
||||
error: function () {
|
||||
$('#menuContainer').html(`
|
||||
<div class="alert alert-danger m-3">
|
||||
菜单加载失败,请稍后重试。
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染菜单函数
|
||||
function renderMenu(menus) {
|
||||
let menuHtml = '';
|
||||
|
||||
// 添加默认欢迎页
|
||||
menuHtml += `
|
||||
<a href="#" class="list-group-item list-group-item-action menu-item active" data-url="/view/welcome">
|
||||
欢迎页
|
||||
</a>
|
||||
`;
|
||||
|
||||
// 递归渲染菜单
|
||||
menus.forEach(menu => {
|
||||
menuHtml += renderMenuItem(menu);
|
||||
});
|
||||
|
||||
$('#menuContainer').html(menuHtml);
|
||||
}
|
||||
|
||||
// 渲染单个菜单项
|
||||
function renderMenuItem(menu) {
|
||||
// 只显示type为1或2的菜单项
|
||||
if (menu.type !== "1" && menu.type !== "2") {
|
||||
return '';
|
||||
}
|
||||
|
||||
let menuHtml = '';
|
||||
|
||||
if (menu.type === "1") {
|
||||
// 一级菜单(有子菜单)
|
||||
menuHtml += `
|
||||
<a href="#" class="list-group-item list-group-item-action menu-has-children menu-item">
|
||||
${menu.name}
|
||||
</a>
|
||||
`;
|
||||
|
||||
// 渲染子菜单
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
menuHtml += '<div class="submenu">';
|
||||
menu.children.forEach(child => {
|
||||
menuHtml += renderMenuItem(child);
|
||||
});
|
||||
menuHtml += '</div>';
|
||||
}
|
||||
} else if (menu.type === "2") {
|
||||
// 二级菜单(页面链接)
|
||||
menuHtml += `
|
||||
<a href="#" class="list-group-item list-group-item-action menu-item" data-url="/v/${menu.tag}">
|
||||
${menu.name}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
return menuHtml;
|
||||
}
|
||||
|
||||
// 绑定菜单事件
|
||||
function bindMenuEvents() {
|
||||
// 菜单点击事件(页面链接)
|
||||
$('.menu-item').not('.menu-has-children').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// 移除所有活动状态
|
||||
$('.menu-item').removeClass('active');
|
||||
// 判断父元素是不是菜单,如果是菜单,则添加活动状态
|
||||
if ($(this).parent().hasClass('submenu')) {
|
||||
//获取当前元素的前一个兄弟元素
|
||||
$(this).parent().prev().addClass('active');
|
||||
}
|
||||
// 为当前点击的菜单项添加活动状态
|
||||
$(this).addClass('active');
|
||||
|
||||
// 获取目标URL并加载内容
|
||||
const url = $(this).data('url');
|
||||
if (url) {
|
||||
loadContent(url);
|
||||
}
|
||||
});
|
||||
|
||||
// 二级菜单展开/收起 - 优化后的逻辑
|
||||
$('.menu-has-children').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
// 切换当前菜单的展开状态
|
||||
$(this).toggleClass('active');
|
||||
// 阻止事件冒泡,避免影响其他菜单
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
101
src/main/resources/templates/view/login.html
Normal file
101
src/main/resources/templates/view/login.html
Normal file
@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>登录</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="../../static/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css"
|
||||
th:href="@{/plugin/bootstrap-5.3.7-dist/css/bootstrap.min.css}"
|
||||
rel="stylesheet">
|
||||
<!-- jQuery -->
|
||||
<script src="../../static/plugin/jquery-3.7.1/jquery-3.7.1.min.js"
|
||||
th:src="@{/plugin/jquery-3.7.1/jquery-3.7.1.min.js}"></script>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-4 col-md-6 col-sm-8">
|
||||
<div class="card mt-5 shadow">
|
||||
<div class="card-header text-center">
|
||||
<h3>用户登录</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">用户名</label>
|
||||
<input type="text" class="form-control" id="username" name="username" placeholder="请输入用户名">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">密码</label>
|
||||
<input type="password" class="form-control" id="password" name="password" placeholder="请输入密码">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary" id="login">登录</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="message" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="../../static/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js"
|
||||
th:src="@{/plugin/bootstrap-5.3.7-dist/js/bootstrap.bundle.min.js}"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#loginForm').on('submit', function (e) {
|
||||
e.preventDefault(); // 阻止表单默认提交
|
||||
|
||||
// 获取表单数据
|
||||
var username = $('#username').val();
|
||||
var password = $('#password').val();
|
||||
|
||||
// 简单验证
|
||||
if (!username || !password) {
|
||||
$('#message').html('<div class="alert alert-warning">请输入用户名和密码</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ajax提交
|
||||
$.ajax({
|
||||
url: '/login', // 根据实际接口地址修改
|
||||
type: 'POST',
|
||||
data: {
|
||||
username: username,
|
||||
password: password
|
||||
},
|
||||
dataType: 'json',
|
||||
beforeSend: function () {
|
||||
$('#login').prop('disabled', true).text('登录中...');
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.return_code === "SUCCESS") {
|
||||
$('#message').html('<div class="alert alert-success">登录成功,正在跳转...</div>');
|
||||
// 登录成功后的处理,比如跳转页面
|
||||
setTimeout(function () {
|
||||
window.location.href = response.redirectUrl || '/view/index'; // 根据实际跳转地址修改
|
||||
}, 1000);
|
||||
} else {
|
||||
$('#message').html('<div class="alert alert-danger">' + response.return_msg + '</div>');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#message').html('<div class="alert alert-danger">登录请求失败,请稍后重试</div>');
|
||||
},
|
||||
complete: function () {
|
||||
$('#login').prop('disabled', false).text('登录');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
32
src/main/resources/templates/view/welcome.html
Normal file
32
src/main/resources/templates/view/welcome.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!-- templates/view/welcome.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>欢迎页</title>
|
||||
</head>
|
||||
<body>
|
||||
<div th:fragment="content">
|
||||
<h2>欢迎使用权限管理系统</h2>
|
||||
<p>请选择左侧菜单进行操作</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">系统信息</h5>
|
||||
<p class="card-text">当前系统运行正常</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">快捷操作</h5>
|
||||
<p class="card-text">您可以从左侧菜单开始操作</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,13 @@
|
||||
package top.yexuejc.admin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class PermissionRoleManagementSystemApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user