fix: 修复CI镜像构建失败(Dockerfile COPY docker/cas.war 但上下文缺失/被 .dockerignore 排除)
build-push / build-push (push) Failing after 42s
build-push / build-push (push) Failing after 42s
- .dockerignore 移除对 docker/cas.war 的排除,保证 COPY 可取到 - docker/cas.war 同步最新构建产物(含自定义登录模板 casLoginView/casGenericSuccessView、check-sso、登录成功跳默认落点、完整版前端资源) - 补充登录页模板源码与 build 脚本入库
This commit is contained in:
+2
-3
@@ -1,8 +1,7 @@
|
||||
# 缩小 docker build 上下文(Dockerfile 只需 target/cas.war、docker/*、etc/cas/config/log4j2.xml)
|
||||
# 缩小 docker build 上下文(Dockerfile 需 docker/cas.war、docker/*、etc/cas/config/log4j2.xml)
|
||||
# 注意:不能排除 docker/cas.war —— Dockerfile 的 COPY docker/cas.war 需要它进入构建上下文
|
||||
src
|
||||
.gitea
|
||||
.git
|
||||
*.md
|
||||
maven-settings.xml
|
||||
docker/cas.war
|
||||
docker/cas.war.bak
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
@echo off
|
||||
|
||||
@set JAVA_ARGS=-Xms500m -Xmx1g
|
||||
@if "%CAS_SERVER_NAME%" == "" set CAS_SERVER_NAME=http://localhost:8443
|
||||
@if "%CAS_SERVER_PREFIX%" == "" set CAS_SERVER_PREFIX=http://localhost:8443/cas
|
||||
@set CAS_JAVA_OVERRIDES=-Dserver.use-forward-headers=false -Dcas.server.name=%CAS_SERVER_NAME% -Dcas.server.prefix=%CAS_SERVER_PREFIX%
|
||||
@set CAS_DIR=\etc\cas
|
||||
@set CONFIG_DIR=\etc\cas\config
|
||||
|
||||
@rem Call this script with DNAME and CERT_SUBJ_ALT_NAMES already set to override
|
||||
@if "%DNAME%" == "" set DNAME=CN=cas.example.org,OU=Example,OU=Org,C=US
|
||||
@rem List other host names or ip addresses you want in your certificate, may help with host name verification,
|
||||
@rem if client apps make https connection for ticket validation and compare name in cert (include sub. alt. names)
|
||||
@rem to name used to access CAS
|
||||
@if "%CERT_SUBJ_ALT_NAMES%" == "" set CERT_SUBJ_ALT_NAMES=dns:example.org,dns:localhost,dns:%COMPUTERNAME%,ip:127.0.0.1
|
||||
|
||||
@rem Check for mvn in path, use it if found, otherwise use maven wrapper
|
||||
@set MAVEN_CMD=mvn
|
||||
@where /q mvn
|
||||
@if %ERRORLEVEL% neq 0 set MAVEN_CMD=.\mvnw.bat
|
||||
|
||||
@if "%1" == "" call:help
|
||||
@if "%1" == "copy" call:copy
|
||||
@if "%1" == "clean" call:clean %2 %3 %4
|
||||
@if "%1" == "package" call:package %2 %3 %4
|
||||
@if "%1" == "bootrun" call:bootrun %2 %3 %4
|
||||
@if "%1" == "debug" call:debug %2 %3 %4
|
||||
@if "%1" == "run" call:run %2 %3 %4
|
||||
@if "%1" == "runalone" call:runalone %2 %3 %4
|
||||
@if "%1" == "help" call:help
|
||||
@if "%1" == "gencert" call:gencert
|
||||
@if "%1" == "cli" call:runcli %2 %3 %4
|
||||
|
||||
@rem function section starts here
|
||||
@goto:eof
|
||||
|
||||
:copy
|
||||
@echo "Creating configuration directory under %CONFIG_DIR%"
|
||||
if not exist %CONFIG_DIR% mkdir %CONFIG_DIR%
|
||||
|
||||
@echo "Copying configuration files from etc/cas to /etc/cas"
|
||||
xcopy /S /Y etc\cas\* \etc\cas
|
||||
@goto:eof
|
||||
|
||||
:help
|
||||
@echo "Usage: build.bat [copy|clean|package|run|debug|bootrun|gencert|cli] [optional extra args for maven or cli]"
|
||||
@echo "To get started on a clean system, run "build.bat copy" and "build.bat gencert", then "build.bat run"
|
||||
@echo "Note that using the copy or gencert arguments will create and/or overwrite the %CAS_DIR% which is outside this project"
|
||||
@goto:eof
|
||||
|
||||
:clean
|
||||
call %MAVEN_CMD% clean %1 %2 %3
|
||||
exit /B %ERRORLEVEL%
|
||||
@goto:eof
|
||||
|
||||
:package
|
||||
call %MAVEN_CMD% clean package -T 5 %1 %2 %3
|
||||
exit /B %ERRORLEVEL%
|
||||
@goto:eof
|
||||
|
||||
:bootrun
|
||||
call %MAVEN_CMD% clean package spring-boot:run -T 5 %1 %2 %3
|
||||
exit /B %ERRORLEVEL%
|
||||
@goto:eof
|
||||
|
||||
:debug
|
||||
call:package %1 %2 %3 & java %JAVA_ARGS% %CAS_JAVA_OVERRIDES% -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n -jar target/cas.war
|
||||
@goto:eof
|
||||
|
||||
:run
|
||||
call:package %1 %2 %3 & java %JAVA_ARGS% %CAS_JAVA_OVERRIDES% -jar target/cas.war
|
||||
@goto:eof
|
||||
|
||||
:runalone
|
||||
call:package %1 %2 %3 & target/cas.war
|
||||
@goto:eof
|
||||
|
||||
:gencert
|
||||
where /q keytool
|
||||
if ERRORLEVEL 1 (
|
||||
@echo Java keytool.exe not found in path.
|
||||
exit /b 1
|
||||
) else (
|
||||
if not exist %CAS_DIR% mkdir %CAS_DIR%
|
||||
@echo on
|
||||
@echo Generating self-signed SSL cert for %DNAME% in %CAS_DIR%\thekeystore
|
||||
keytool -genkeypair -alias cas -keyalg RSA -keypass changeit -storepass changeit -keystore %CAS_DIR%\thekeystore -dname %DNAME% -ext SAN=%CERT_SUBJ_ALT_NAMES%
|
||||
@echo Exporting cert for use in trust store (used by cas clients)
|
||||
keytool -exportcert -alias cas -storepass changeit -keystore %CAS_DIR%\thekeystore -file %CAS_DIR%\cas.cer
|
||||
)
|
||||
@goto:eof
|
||||
|
||||
:runcli
|
||||
for /f %%i in ('call %MAVEN_CMD% -q --non-recursive "-Dexec.executable=cmd" "-Dexec.args=/C echo ${cas.version}" "org.codehaus.mojo:exec-maven-plugin:1.3.1:exec"') do set CAS_VERSION=%%i
|
||||
@set CAS_VERSION=%CAS_VERSION: =%
|
||||
@set DOWNLOAD_DIR=target
|
||||
@set COMMAND_FILE=cas-server-support-shell-%CAS_VERSION%.jar
|
||||
@if not exist %DOWNLOAD_DIR% mkdir %DOWNLOAD_DIR%
|
||||
@if not exist %DOWNLOAD_DIR%\%COMMAND_FILE% (
|
||||
@call mvn org.apache.maven.plugins:maven-dependency-plugin:3.0.2:get -DgroupId=org.apereo.cas -DartifactId=cas-server-support-shell -Dversion=%CAS_VERSION% -Dpackaging=jar -DartifactItem.outputDirectory=%DOWNLOAD_DIR% -DartifactItem.destFileName=%COMMAND_FILE% -DremoteRepositories=central::default::http://repo1.maven.apache.org/maven2,snapshots::::https://oss.sonatype.org/content/repositories/snapshots -Dtransitive=false
|
||||
@call mvn org.apache.maven.plugins:maven-dependency-plugin:3.0.2:copy -Dmdep.useBaseVersion=true -Dartifact=org.apereo.cas:cas-server-support-shell:%CAS_VERSION%:jar -DoutputDirectory=%DOWNLOAD_DIR%
|
||||
)
|
||||
@call java %JAVA_ARGS% -jar %DOWNLOAD_DIR%\%COMMAND_FILE% %1 %2 %3
|
||||
|
||||
@goto:eof
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
function copy() {
|
||||
echo -e "Creating configuration directory under /etc/cas"
|
||||
mkdir -p /etc/cas/config
|
||||
|
||||
echo -e "Copying configuration files from etc/cas to /etc/cas"
|
||||
cp -rfv etc/cas/* /etc/cas
|
||||
}
|
||||
|
||||
function help() {
|
||||
echo "Usage: build.sh [copy|clean|package|run|debug|bootrun|gencert]"
|
||||
echo " copy: Copy config from ./etc/cas/config to /etc/cas/config"
|
||||
echo " clean: Clean Maven build directory"
|
||||
echo " package: Clean and build CAS war"
|
||||
echo " run: Build and run cas.war via Java (i.e. java -jar target/cas.war)"
|
||||
echo " runalone: Build and run cas.war on its own as a standalone executable (target/cas.war)"
|
||||
echo " debug: Run CAS.war and listen for Java debugger on port 5000"
|
||||
echo " bootrun: Run with maven spring boot plugin"
|
||||
echo " listviews: List all CAS views that ship with the web application and can be customized in the overlay"
|
||||
echo " getview: Ask for a view name to be included in the overlay for customizations"
|
||||
echo " gencert: Create keystore with SSL certificate in location where CAS looks by default"
|
||||
echo " cli: Run the CAS command line shell and pass commands"
|
||||
}
|
||||
|
||||
function clean() {
|
||||
shift
|
||||
./mvnw clean "$@"
|
||||
}
|
||||
|
||||
function package() {
|
||||
shift
|
||||
./mvnw clean package -T 5 "$@"
|
||||
# copy
|
||||
}
|
||||
|
||||
function bootrun() {
|
||||
shift
|
||||
./mvnw clean package spring-boot:run -P bootiful -T 5 "$@"
|
||||
}
|
||||
|
||||
function debug() {
|
||||
package && java -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n -jar target/cas.war
|
||||
}
|
||||
|
||||
function run() {
|
||||
package && java -jar target/cas.war
|
||||
}
|
||||
|
||||
function runalone() {
|
||||
shift
|
||||
./mvnw clean package -P default,exec "$@"
|
||||
chmod +x target/cas.war
|
||||
target/cas.war
|
||||
}
|
||||
|
||||
function listviews() {
|
||||
shift
|
||||
explodeapp
|
||||
find $PWD/target/cas -type f -name "*.html" | xargs -n 1 basename | sort | more
|
||||
}
|
||||
|
||||
function explodeapp() {
|
||||
if [ ! -d $PWD/target/cas ];then
|
||||
echo "Building the CAS web application and exploding the final war file..."
|
||||
./mvnw clean package war:exploded "$@"
|
||||
fi
|
||||
echo "Exploded the CAS web application file."
|
||||
}
|
||||
|
||||
function getview() {
|
||||
shift
|
||||
explodeapp
|
||||
echo "Searching for view name $@..."
|
||||
results=`find $PWD/target/cas -type f -name "*.html" | grep -i "$@"`
|
||||
echo -e "Found view(s): \n$results"
|
||||
count=`wc -w <<< "$results"`
|
||||
if [ "$count" -eq 1 ];then
|
||||
# echo "Found view $results to include in the overlay"
|
||||
firststring="target/cas/WEB-INF/classes"
|
||||
secondstring="src/main/resources"
|
||||
overlayfile=`echo "${results/$firststring/$secondstring}"`
|
||||
overlaypath=`dirname "${overlayfile}"`
|
||||
# echo "Overlay file is $overlayfile to be created at $overlaypath"
|
||||
mkdir -p $overlaypath
|
||||
cp $results $overlaypath
|
||||
echo "Created view at $overlayfile"
|
||||
ls $overlayfile
|
||||
else
|
||||
echo "More than one view file is found. Narrow down the search query..."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
function gencert() {
|
||||
if [[ ! -d /etc/cas ]] ; then
|
||||
copy
|
||||
fi
|
||||
which keytool
|
||||
if [[ $? -ne 0 ]] ; then
|
||||
echo Error: Java JDK \'keytool\' is not installed or is not in the path
|
||||
exit 1
|
||||
fi
|
||||
# override DNAME and CERT_SUBJ_ALT_NAMES before calling or use dummy values
|
||||
DNAME="${DNAME:-CN=cas.example.org,OU=Example,OU=Org,C=US}"
|
||||
CERT_SUBJ_ALT_NAMES="${CERT_SUBJ_ALT_NAMES:-dns:example.org,dns:localhost,ip:127.0.0.1}"
|
||||
echo "Generating keystore for CAS with DN ${DNAME}"
|
||||
keytool -genkeypair -alias cas -keyalg RSA -keypass changeit -storepass changeit -keystore /etc/cas/thekeystore -dname ${DNAME} -ext SAN=${CERT_SUBJ_ALT_NAMES}
|
||||
keytool -exportcert -alias cas -storepass changeit -keystore /etc/cas/thekeystore -file /etc/cas/cas.cer
|
||||
}
|
||||
|
||||
function cli() {
|
||||
|
||||
CAS_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${cas.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec 2>/dev/null)
|
||||
# echo "CAS version: $CAS_VERSION"
|
||||
JAR_FILE_NAME="cas-server-support-shell-${CAS_VERSION}.jar"
|
||||
# echo "JAR name: $JAR_FILE_NAME"
|
||||
JAR_PATH="org/apereo/cas/cas-server-support-shell/${CAS_VERSION}/${JAR_FILE_NAME}"
|
||||
# echo "JAR path: $JAR_PATH"
|
||||
|
||||
JAR_FILE_LOCAL="$HOME/.m2/repository/$JAR_PATH";
|
||||
# echo "Local JAR file path: $JAR_FILE_LOCAL";
|
||||
if [ -f "$JAR_FILE_LOCAL" ]; then
|
||||
# echo "Using JAR file locally at $JAR_FILE_LOCAL"
|
||||
java -jar $JAR_FILE_LOCAL "$@"
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
DOWNLOAD_DIR=./target
|
||||
COMMAND_FILE="${DOWNLOAD_DIR}/${JAR_FILE_NAME}"
|
||||
if [ ! -f "$COMMAND_FILE" ]; then
|
||||
mkdir -p $DOWNLOAD_DIR
|
||||
./mvnw org.apache.maven.plugins:maven-dependency-plugin:3.0.2:get -DgroupId=org.apereo.cas -DartifactId=cas-server-support-shell -Dversion=$CAS_VERSION -Dpackaging=jar -DartifactItem.outputDirectory=$DOWNLOAD_DIR -DremoteRepositories=central::default::http://repo1.maven.apache.org/maven2,snapshots::::https://oss.sonatype.org/content/repositories/snapshots -Dtransitive=false
|
||||
./mvnw org.apache.maven.plugins:maven-dependency-plugin:3.0.2:copy -Dmdep.useBaseVersion=true -Dartifact=org.apereo.cas:cas-server-support-shell:$CAS_VERSION:jar -DoutputDirectory=$DOWNLOAD_DIR
|
||||
fi
|
||||
java -jar $COMMAND_FILE "$@"
|
||||
exit 0;
|
||||
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo -e "No commands provided. Defaulting to [run]\n"
|
||||
run
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
"copy")
|
||||
copy
|
||||
;;
|
||||
"clean")
|
||||
shift
|
||||
clean "$@"
|
||||
;;
|
||||
"package")
|
||||
shift
|
||||
package "$@"
|
||||
;;
|
||||
"bootrun")
|
||||
shift
|
||||
bootrun "$@"
|
||||
;;
|
||||
"debug")
|
||||
debug "$@"
|
||||
;;
|
||||
"run")
|
||||
run "$@"
|
||||
;;
|
||||
"runalone")
|
||||
runalone "$@"
|
||||
;;
|
||||
"listviews")
|
||||
listviews "$@"
|
||||
;;
|
||||
"gencert")
|
||||
gencert "$@"
|
||||
;;
|
||||
"getview")
|
||||
getview "$@"
|
||||
;;
|
||||
"cli")
|
||||
shift
|
||||
cli "$@"
|
||||
;;
|
||||
*)
|
||||
help
|
||||
;;
|
||||
esac
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>请稍候</title>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
覆盖 CAS 原生 casGenericSuccessView.html:已有有效 TGT 后再访问 /cas/login(无 service)
|
||||
时,CAS 不再渲染默认"您已经成功登录中央认证系统"页,而是自动跳转到配置的默认落点
|
||||
(custom.login.default-redirect-url,即 base.yunhaixingtong.com)。
|
||||
跳转地址复用 /cas/api/check-sso 的返回(跟随 Nacos 配置,不硬编码);接口异常时兜底。
|
||||
-->
|
||||
<script>
|
||||
(function () {
|
||||
var fallback = "https://base.yunhaixingtong.com";
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/cas/api/check-sso", true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
try {
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
if (resp && resp.code == 0 && resp.data && resp.data.url) {
|
||||
window.location.replace(resp.data.url);
|
||||
return;
|
||||
}
|
||||
} catch (e) { /* 兜底 */ }
|
||||
window.location.replace(fallback);
|
||||
};
|
||||
xhr.send();
|
||||
} catch (e) {
|
||||
window.location.replace(fallback);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,401 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<title>登录</title>
|
||||
<!-- 覆盖 CAS 原生 casLoginView.html:视觉采用 front/CasSsoFront(完整版企业登录页),
|
||||
逻辑由 native-login.js 接管(含 check-sso 免密预检 + RSA 登录)。
|
||||
URL 是 /cas/login,所有资源必须绝对路径(相对路径会解析到 /cas/libs/)。 -->
|
||||
<script src="/cas/front/libs/jquery.min.js?v=2.1"></script>
|
||||
<script src="/cas/front/js/jsencrypt.min.js?v=2.1"></script>
|
||||
<script src="/cas/front/js/conf.js?v=2.3"></script>
|
||||
<script src="/cas/front/js/bschlogin.js?v=2.1"></script>
|
||||
<script src="/cas/front/js/native-login.js?v=3.0"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: #eef2f7;
|
||||
color: #1f2d4d;
|
||||
}
|
||||
|
||||
.login-wrap {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 左侧品牌区 */
|
||||
.login-banner {
|
||||
flex: 0 0 46%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 7%;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(155deg, #1e3a8a 0%, #2563eb 52%, #3b82f6 100%);
|
||||
}
|
||||
|
||||
.login-banner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -140px;
|
||||
left: -100px;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.16), transparent 70%);
|
||||
}
|
||||
|
||||
.login-banner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -120px;
|
||||
bottom: -120px;
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.12), transparent 70%);
|
||||
}
|
||||
|
||||
.banner-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 52px;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
height: 44px;
|
||||
width: auto;
|
||||
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.banner-desc {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 38px;
|
||||
}
|
||||
|
||||
.banner-points {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.banner-points li {
|
||||
position: relative;
|
||||
padding-left: 22px;
|
||||
font-size: 15px;
|
||||
line-height: 2.3;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.banner-points li::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 16px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.banner-footer {
|
||||
position: absolute;
|
||||
left: 7%;
|
||||
right: 7%;
|
||||
bottom: 26px;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 右侧登录区 */
|
||||
.login-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 380px;
|
||||
max-width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 14px 44px rgba(31, 45, 80, 0.12);
|
||||
padding: 42px 36px 34px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.form-item input {
|
||||
width: 100%;
|
||||
padding: 13px 14px;
|
||||
border: 1px solid #dfe3ea;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #1f2d4d;
|
||||
background: #fbfcfe;
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.form-item input:focus {
|
||||
border-color: #2563eb;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
|
||||
}
|
||||
|
||||
.captcha-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.captcha-row input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.captcha-row img {
|
||||
height: 46px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dfe3ea;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 13px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
background: linear-gradient(135deg, #2563eb, #3b82f6);
|
||||
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.28);
|
||||
transition: transform 0.1s, box-shadow 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-submit:hover {
|
||||
box-shadow: 0 8px 22px rgba(37, 99, 235, 0.38);
|
||||
}
|
||||
|
||||
.btn-submit:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #f56c6c;
|
||||
font-size: 13px;
|
||||
margin-top: 14px;
|
||||
text-align: center;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* 登录成功遮罩 */
|
||||
.login-success {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background: rgba(255, 255, 255, 0.97);
|
||||
}
|
||||
|
||||
.login-success .check-circle {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 50%;
|
||||
background: #e8f5ee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: lc-pop 0.35s ease;
|
||||
}
|
||||
|
||||
.login-success .check-circle svg {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.check-path {
|
||||
stroke: #22c55e;
|
||||
stroke-width: 5;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-dasharray: 50;
|
||||
stroke-dashoffset: 50;
|
||||
animation: lc-draw 0.4s 0.15s forwards ease;
|
||||
}
|
||||
|
||||
.login-success .success-text {
|
||||
margin-top: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
@keyframes lc-pop {
|
||||
0% {
|
||||
transform: scale(0.6);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes lc-draw {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端:隐藏品牌区,卡片居中 */
|
||||
@media (max-width: 860px) {
|
||||
.login-banner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-wrap {
|
||||
background: linear-gradient(155deg, #1e3a8a, #3b82f6);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
box-shadow: 0 14px 44px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-wrap">
|
||||
<aside class="login-banner">
|
||||
<div class="banner-inner">
|
||||
<div class="brand">
|
||||
<img class="brand-logo" src="/cas/front/image/logo.png" alt="logo" />
|
||||
<span class="brand-name">云海星通</span>
|
||||
</div>
|
||||
<h1 class="banner-title">统一身份认证平台</h1>
|
||||
<p class="banner-desc">一次登录,畅享所有校园应用<br />安全、便捷的单点登录服务</p>
|
||||
<ul class="banner-points">
|
||||
<li>统一账号体系,免去多套密码</li>
|
||||
<li>单点登录,应用间无缝切换</li>
|
||||
<li>全程加密传输,保障数据安全</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="banner-footer">
|
||||
©2021 bestsch.com inc All Rights reserved
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="login-main">
|
||||
<div class="login-card">
|
||||
<h2 class="login-title">账号登录</h2>
|
||||
<form id="loginForm" onsubmit="return false;">
|
||||
<div class="form-item">
|
||||
<input type="text" id="user_input" name="username"
|
||||
placeholder="用户名/手机号/身份证号" autocomplete="username" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<input type="password" id="password_input" name="password"
|
||||
placeholder="请输入密码" autocomplete="current-password" />
|
||||
</div>
|
||||
<input type="hidden" id="loginname_input" name="loginname" />
|
||||
<input type="hidden" id="pwd_input" name="pwd" />
|
||||
<input type="hidden" id="captcha_hidden" name="captcha" />
|
||||
<input type="hidden" id="service_hidden" name="service" />
|
||||
<div class="form-item" id="captchaPanel" style="display:none">
|
||||
<div class="captcha-row">
|
||||
<input type="text" id="captcha_input" placeholder="输入验证码" autocomplete="off" />
|
||||
<img id="captchaImg" title="点击刷新" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-submit" type="button" id="loginBtn">登录</button>
|
||||
<div class="warning" id="warningLabel"></div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 登录成功遮罩:由 native-login.js 在登录成功后显示 -->
|
||||
<div class="login-success" id="loginSuccess">
|
||||
<div class="check-circle">
|
||||
<svg viewBox="0 0 54 54" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="check-path" d="M15 28 L23 36 L40 18" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="success-text">登录成功</div>
|
||||
</div>
|
||||
|
||||
<!-- 记住密码最佳努力:真实表单提交到隐藏 iframe(不跳转页面);主流程用 AJAX 拿 data.url 手动跳转,互不干扰 -->
|
||||
<iframe name="pwdMgrFrame" style="display:none"></iframe>
|
||||
<form id="pwdMgrForm" method="post" action="/cas/api/login" target="pwdMgrFrame" style="display:none">
|
||||
<input type="text" name="username" id="pm_user" />
|
||||
<input type="password" name="password" id="pm_pwd" />
|
||||
<input type="hidden" name="loginname" id="pm_loginname" />
|
||||
<input type="hidden" name="pwd" id="pm_pwd_enc" />
|
||||
<input type="hidden" name="captcha" id="pm_captcha" />
|
||||
<input type="hidden" name="service" id="pm_service" />
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user