- docker/cas.war: 注入 NacosEnvironmentPostProcessor(name->ID 解析,yh-reader 只读 prod) - docker/run-cas.sh: 改为纯启动器,移除旧 Nacos 拉取写文件逻辑(避免覆盖 post-processor) - docker/cas.properties: 移除 cas.server.name/JDBC,改由 Nacos 注入 - docker/docker-compose.cas.yml: NACOS 账号改回 yh-reader - .gitea/workflows/deploy.yml: docker run 显式传 NACOS_NAMESPACE=prod + yh-reader 凭据 - src: 同步自定义源码(Nacos 注入器 + LoginController + spring.factories) - 移除 webapp/front(前端改由 CasSsoFront 独立发布)+ 清理误提交诊断脚本
@@ -29,9 +29,17 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy on host (infra-infra-net, CAS 端口 8443)
|
- name: Deploy on host (infra-infra-net, CAS 端口 8443)
|
||||||
# 镜像已在宿主本地,直接运行,无需推送到 nexus(nexus docker 仓库为 HTTP,需 daemon insecure-registries 才能推送)
|
# 镜像已在宿主本地,直接运行,无需推送到 nexus(nexus docker 仓库为 HTTP,需 daemon insecure-registries 才能推送)
|
||||||
|
# Nacos 环境变量:NacosEnvironmentPostProcessor 启动阶段据此从 Nacos 原生拉取
|
||||||
|
# common.yaml (DEFAULT_GROUP) + cas.yaml (CAS group),namespace=prod。
|
||||||
|
# 不传 NACOS_NAMESPACE 会默认连 dev(dev 命名空间无配置),登录必失败——必须显式传 prod。
|
||||||
run: |
|
run: |
|
||||||
docker rm -f cas-sso 2>/dev/null || true
|
docker rm -f cas-sso 2>/dev/null || true
|
||||||
docker run -d --name cas-sso --network infra-infra-net -p 8443:8443 --restart unless-stopped cassso-service:latest
|
docker run -d --name cas-sso --network infra-infra-net -p 8443:8443 --restart unless-stopped \
|
||||||
|
-e NACOS_ADDR=nacos:8848 \
|
||||||
|
-e NACOS_NAMESPACE=prod \
|
||||||
|
-e NACOS_USER=yh-reader \
|
||||||
|
-e NACOS_PASS=123456 \
|
||||||
|
cassso-service:latest
|
||||||
|
|
||||||
- name: Done
|
- name: Done
|
||||||
run: 'echo "CasSsoService deployed -> https://192.168.10.160:8443/login"'
|
run: 'echo "CasSsoService deployed -> https://192.168.10.160:8443/login"'
|
||||||
|
|||||||
@@ -1,4 +1,18 @@
|
|||||||
docker/live_cas.war
|
docker/live_cas.war
|
||||||
docker/diag*.py
|
docker/diag*.py
|
||||||
docker/verify*.py
|
docker/verify*.py
|
||||||
|
docker/inject*.py
|
||||||
|
docker/live_*.py
|
||||||
|
docker/*_diag*.py
|
||||||
|
docker/fetch_*.py
|
||||||
|
docker/find_*.py
|
||||||
|
docker/login_debug.py
|
||||||
|
docker/probe*.py
|
||||||
|
docker/raw_post.py
|
||||||
|
docker/remote_run.py
|
||||||
|
docker/ssh_*.py
|
||||||
|
docker/upload.py
|
||||||
|
docker/diag2_out.txt
|
||||||
|
# 前端由 CasSsoFront 仓库独立发布,本后端仓库只发纯后端(cas.war)
|
||||||
|
src/main/webapp/front/
|
||||||
*.iml
|
*.iml
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ COPY docker/run-cas.sh /app/run-cas.sh
|
|||||||
COPY etc/cas/config/log4j2.xml /etc/cas/config/log4j2.xml
|
COPY etc/cas/config/log4j2.xml /etc/cas/config/log4j2.xml
|
||||||
RUN chmod +x /app/run-cas.sh
|
RUN chmod +x /app/run-cas.sh
|
||||||
|
|
||||||
# 镜像内 cas.properties 仅作「nacos 不可达时的兜底默认」;
|
# 镜像内 cas.properties 仅保留本地无关配置(HTTPS/端口/日志/管理页白名单);
|
||||||
# 正常启动由 run-cas.sh 从 nacos 拉取线上配置(dataId=cas-prod.properties)后覆盖。
|
# 业务配置(cas.server.name / JDBC / Redis 等)由 NacosEnvironmentPostProcessor 在启动阶段
|
||||||
|
# 从 Nacos 原生拉取(common.yaml + cas.yaml,namespace=NACOS_NAMESPACE),镜像保持环境无关。
|
||||||
EXPOSE 8443
|
EXPOSE 8443
|
||||||
ENTRYPOINT ["/app/run-cas.sh"]
|
ENTRYPOINT ["/app/run-cas.sh"]
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
# CAS 烤进镜像的兜底配置(仅当 Nacos 不可用时由 run-cas.sh 回退使用)
|
# CAS 烤进镜像的本地兜底配置(与运行环境无关、不含任何凭据)
|
||||||
# 正常情况下以 Nacos 的 cas-prod.properties(连接信息来自 base-common)为准
|
# 业务配置由 Nacos 原生注入(NacosEnvironmentPostProcessor):
|
||||||
cas.server.name: https://localhost:8443
|
# common.yaml(DEFAULT_GROUP) + cas.yaml(CAS),namespace=NACOS_NAMESPACE
|
||||||
cas.server.prefix: https://localhost:8443/cas
|
# -> cas.server.name / JDBC 连接 / Redis 等全部来自 Nacos,本文件不写。
|
||||||
|
# 仅保留本地必需的:HTTPS 密钥库、端口、日志、管理页白名单。
|
||||||
cas.adminPagesSecurity.ip=127\.0\.0\.1
|
|
||||||
|
|
||||||
logging.config: file:/etc/cas/config/log4j2.xml
|
|
||||||
|
|
||||||
server.port: 8443
|
server.port: 8443
|
||||||
server.ssl.enabled: true
|
server.ssl.enabled: true
|
||||||
server.ssl.key-store: /etc/cas/cas.keystore
|
server.ssl.key-store: /etc/cas/cas.keystore
|
||||||
@@ -14,15 +10,6 @@ server.ssl.key-store-password: changeit
|
|||||||
server.ssl.key-password: changeit
|
server.ssl.key-password: changeit
|
||||||
server.ssl.key-alias: cas
|
server.ssl.key-alias: cas
|
||||||
|
|
||||||
# ===== 数据库认证(兜底:连公共 mysql8 上的 bsch_userbase)=====
|
logging.config: file:/etc/cas/config/log4j2.xml
|
||||||
# 注意:以下 user/password 为占位符;Nacos 不可用时需改为公共 mysql8 真实账号
|
|
||||||
cas.authn.jdbc.query[0].sql: select * from sys_user where username=?
|
cas.adminPagesSecurity.ip=127\.0\.0\.1
|
||||||
cas.authn.jdbc.query[0].url: jdbc:mysql://mysql8:3306/bsch_userbase?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
|
||||||
cas.authn.jdbc.query[0].user: __SHARED_MYSQL_USER__
|
|
||||||
cas.authn.jdbc.query[0].password: __SHARED_MYSQL_PASSWORD__
|
|
||||||
cas.authn.jdbc.query[0].driverClass: com.mysql.jdbc.Driver
|
|
||||||
cas.authn.jdbc.query[0].fieldPassword: password
|
|
||||||
cas.authn.jdbc.query[0].fieldDisabled: disabled
|
|
||||||
cas.authn.jdbc.query[0].fieldExpired: expired
|
|
||||||
cas.authn.jdbc.query[0].passwordEncoder.type: BCRYPT
|
|
||||||
cas.authn.jdbc.query[0].passwordEncoder.strength: 10
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
COPIED_OK
|
|
||||||
|
|
||||||
local war size: 173751847
|
|
||||||
|
|
||||||
=== RUNNING WAR LoginController features ===
|
|
||||||
has window.location.href (v1.8 success): True
|
|
||||||
has sendRedirect (error path): True
|
|
||||||
has text/html (v1.8 success): True
|
|
||||||
|
|
||||||
=== RUNNING WAR index.html ===
|
|
||||||
has action=/api/login: True
|
|
||||||
has ?v=1.8: True
|
|
||||||
has name=username: True
|
|
||||||
|
|
||||||
=== container filesystem: any default-redirect-url? ===
|
|
||||||
---env---
|
|
||||||
|
|
||||||
|
|
||||||
=== effective cas.properties (head) ===
|
|
||||||
# CAS prod config (managed in nacos, dataId=cas-prod.properties, group=CAS, namespace=cas)
|
|
||||||
# 去掉 /cas 上下文路径,对外域名走 sso.yunhaixingtong.com
|
|
||||||
cas.server.name: http://sso.yunhaixingtong.com
|
|
||||||
cas.server.prefix: http://sso.yunhaixingtong.com
|
|
||||||
|
|
||||||
cas.adminPagesSecurity.ip: 127\.0\.0\.1
|
|
||||||
|
|
||||||
logging.config: file:/etc/cas/config/log4j2.xml
|
|
||||||
|
|
||||||
server.port: 8443
|
|
||||||
server.context-path: /
|
|
||||||
server.ssl.enabled: true
|
|
||||||
server.ssl.key-store: /etc/cas/cas.keystore
|
|
||||||
server.ssl.key-store-password: changeit
|
|
||||||
server.ssl.key-password: changeit
|
|
||||||
server.ssl.key-alias: cas
|
|
||||||
|
|
||||||
# TGC 经 caddy(HTTP) 透传,关闭 Secure 以便浏览器在 http 下也能存 cookie
|
|
||||||
cas.tgc.secure: false
|
|
||||||
# 上下文路径已改为 /,cookie 作用域必须同步为 /,否则 SSO 票据校验拿不到 TGC
|
|
||||||
cas.tgc.path: /
|
|
||||||
|
|
||||||
# ===== 数据库认证(自定义 DbAuthenticationHandler 读取)=====
|
|
||||||
cas.authn.jdbc.query[0].sql: select * from sys_user where username=?
|
|
||||||
cas.authn.jdbc.query[0].url: jdbc:mysql://mysql:3306/cas?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
|
||||||
cas.authn.jdbc.query[0].user: cas
|
|
||||||
cas.authn.jdbc.query[0].password: Cas@123456
|
|
||||||
cas.authn.jdbc.query[0].driverClass: com.mysql.jdbc.Driver
|
|
||||||
cas.authn.jdbc.query[0].fieldPassword: password
|
|
||||||
cas.authn.jdbc.query[0].fieldDisabled: disabled
|
|
||||||
cas.authn.jdbc.query[0].fieldExpired: expired
|
|
||||||
cas.authn.jdbc.query[0].url=jdbc:mysql://mysql8:3306/bsch_userbase?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
|
||||||
cas.authn.jdbc.query[0].user=root
|
|
||||||
cas.authn.jdbc.query[0].password=CVf^x$sW*b6CGHxh
|
|
||||||
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
|
|
||||||
spring.redis.host=redis-server
|
|
||||||
spring.redis.port=6379
|
|
||||||
spring.redis.password=wv&WcDYAmLtqTIGM
|
|
||||||
spring.redis.database=0
|
|
||||||
spring.redis.timeout=5000
|
|
||||||
spring.redis.ssl=false
|
|
||||||
spring.redis.jedis.pool.max-active=8
|
|
||||||
spring.redis.jedis.pool.max-idle=8
|
|
||||||
spring.redis.jedis.pool.min-idle=0
|
|
||||||
spring.redis.jedis.pool.max-wait=3000
|
|
||||||
spring.rabbitmq.host=rabbitmq
|
|
||||||
spring.rabbitmq.port=5672
|
|
||||||
spring.rabbitmq.username=admin
|
|
||||||
spring.rabbitmq.password=JdU29I#Q2E4yXoUl
|
|
||||||
spring.rabbitmq.virtual-host=/
|
|
||||||
|
|
||||||
|
|
||||||
=== JDBC url in container ===
|
|
||||||
cas.authn.jdbc.query[0].sql: select * from sys_user where username=?
|
|
||||||
cas.authn.jdbc.query[0].url: jdbc:mysql://mysql:3306/cas?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
|
||||||
cas.authn.jdbc.query[0].user: cas
|
|
||||||
cas.authn.jdbc.query[0].password: Cas@123456
|
|
||||||
cas.authn.jdbc.query[0].driverClass: com.mysql.jdbc.Driver
|
|
||||||
cas.authn.jdbc.query[0].fieldPassword: password
|
|
||||||
cas.authn.jdbc.query[0].fieldDisabled: disabled
|
|
||||||
cas.authn.jdbc.query[0].fieldExpired: expired
|
|
||||||
cas.authn.jdbc.query[0].url=jdbc:mysql://mysql8:3306/bsch_userbase?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
|
||||||
cas.authn.jdbc.query[0].user=root
|
|
||||||
cas.authn.jdbc.query[0].password=CVf^x$sW*b6CGHxh
|
|
||||||
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
|
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# CAS 服务端编排:仅 cas 应用。
|
# CAS 服务端编排:仅 cas 应用。
|
||||||
# MySQL / Redis / RabbitMQ 均为 infra 公共实例(mysql8 等),不在本编排内自建。
|
# MySQL / Redis / RabbitMQ 均为 infra 公共实例(mysql8 等),不在本编排内自建。
|
||||||
# CAS 启动时由 run-cas.sh 从 Nacos 拉取:
|
# CAS 启动后由 NacosEnvironmentPostProcessor 从 Nacos 原生拉取:
|
||||||
# - 公共基础配置 common.properties (namespace=public)
|
# - 公共基础配置 common.yaml (namespace=NACOS_NAMESPACE, group=DEFAULT_GROUP)
|
||||||
# - CAS 自身配置 cas-prod.properties (namespace=cas)
|
# - CAS 自身配置 cas.yaml (namespace=NACOS_NAMESPACE, group=CAS)
|
||||||
# 启动:cd /opt/docker/apps/cas-sso && docker compose -f docker-compose.cas.yml up -d
|
# 启动:cd /opt/docker/apps/cas-sso && docker compose -f docker-compose.cas.yml up -d
|
||||||
services:
|
services:
|
||||||
cas:
|
cas:
|
||||||
@@ -12,19 +12,13 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- infra-infra-net
|
- infra-infra-net
|
||||||
environment:
|
environment:
|
||||||
|
# Nacos 连接(与 UserBase bootstrap 同一套约定)
|
||||||
NACOS_ADDR: nacos:8848
|
NACOS_ADDR: nacos:8848
|
||||||
NACOS_USER: nacos
|
NACOS_USER: yh-reader
|
||||||
NACOS_PASS: nacos
|
NACOS_PASS: 123456
|
||||||
# CAS 自身配置
|
# 环境隔离:连哪个 Nacos 命名空间;prod 命名空间内含 common.yaml + cas.yaml
|
||||||
CAS_CONFIG_DATA_ID: cas-prod.properties
|
NACOS_NAMESPACE: prod
|
||||||
CAS_CONFIG_GROUP: CAS
|
# 用宿主机(部署目录)最新的 run-cas.sh 覆盖镜像内旧版本(纯启动器,无需重建镜像也能改)
|
||||||
CAS_CONFIG_NAMESPACE: cas
|
|
||||||
# 公共基础配置(含 mysql / redis / rabbitmq 连接)
|
|
||||||
CAS_BASE_CONFIG_DATA_ID: common.properties
|
|
||||||
CAS_BASE_CONFIG_GROUP: DEFAULT_GROUP
|
|
||||||
CAS_BASE_CONFIG_NAMESPACE: public
|
|
||||||
# 用宿主机(部署目录)最新的 run-cas.sh 覆盖镜像内旧版本,避免每次改脚本都重新构建镜像
|
|
||||||
# 注意:cas.war 不再挂载,直接烤进镜像(由 build-push.sh 重建),避免热修包被 recreate 冲掉/覆盖导致不可预期的问题
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./run-cas.sh:/app/run-cas.sh:ro
|
- ./run-cas.sh:/app/run-cas.sh:ro
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import paramiko
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
out=run("docker logs --tail 200 cas-sso 2>&1 | grep -n -A 25 '建立 SSO 会话失败\\|createTicketGrantingTicket\\|AbstractTicketException\\|AuthenticationException\\|Cannot\\|Exception' | tail -60")
|
|
||||||
print(out if out.strip() else "(no exception traces found)")
|
|
||||||
# also check redis ticket registry config
|
|
||||||
print("\n=== redis ticket registry config? ===")
|
|
||||||
print(run("docker exec cas-sso sh -c 'grep -ri ticket.registry /etc/cas/config/cas.properties 2>/dev/null; grep -ri ticket.registry /app/*.properties 2>/dev/null' || echo none"))
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import paramiko
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
|
|
||||||
print("=== 1) env vars containing REDIRECT / CUSTOM / CAS_LOGIN ===")
|
|
||||||
print(run("docker exec cas-sso env 2>/dev/null | grep -iE 'REDIRECT|CUSTOM|LOGIN|CAS_SERVER' "))
|
|
||||||
print("\n=== 2) full grep default-redirect-url in container FS ===")
|
|
||||||
print(run("docker exec cas-sso grep -rni 'default-redirect-url' /etc/cas /app 2>/dev/null | head"))
|
|
||||||
print("\n=== 3) run-cas.sh: how is cas.properties built? any hardcode of redirect/cas/login ===")
|
|
||||||
print(run("docker exec cas-sso grep -niE 'redirect|default-redirect|cas/login|cas.properties|NACOS|dataId|cat >' /app/run-cas.sh 2>/dev/null | head -40"))
|
|
||||||
print("\n=== 4) the effective cas.properties custom.* block ===")
|
|
||||||
print(run("docker exec cas-sso sh -c 'grep -niE \"^custom|^cas\" /etc/cas/config/cas.properties 2>/dev/null | head -40'"))
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import zipfile, os, shutil
|
|
||||||
|
|
||||||
PROJ = "D:/SoftProject/yunhai/CasSso/cas-overlay-template-5.3"
|
|
||||||
WAR = os.path.join(PROJ, "docker/cas.war")
|
|
||||||
|
|
||||||
# (entry_name_in_war, local_source_file)
|
|
||||||
REPL = [
|
|
||||||
("WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class",
|
|
||||||
os.path.join(PROJ, "target/cas/WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class")),
|
|
||||||
("WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController$LocalAuthenticationResult.class",
|
|
||||||
os.path.join(PROJ, "target/cas/WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController$LocalAuthenticationResult.class")),
|
|
||||||
("front/index.html",
|
|
||||||
os.path.join(PROJ, "src/main/webapp/front/index.html")),
|
|
||||||
]
|
|
||||||
|
|
||||||
repl_map = {name: open(src, "rb").read() for name, src in REPL}
|
|
||||||
|
|
||||||
tmp = WAR + ".tmp"
|
|
||||||
zin = zipfile.ZipFile(WAR)
|
|
||||||
zout = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
|
|
||||||
for item in zin.infolist():
|
|
||||||
if item.filename in repl_map:
|
|
||||||
zout.writestr(item, repl_map[item.filename])
|
|
||||||
else:
|
|
||||||
zout.writestr(item, zin.read(item.filename))
|
|
||||||
zin.close()
|
|
||||||
zout.close()
|
|
||||||
shutil.move(tmp, WAR)
|
|
||||||
|
|
||||||
z = zipfile.ZipFile(WAR)
|
|
||||||
print("testzip:", z.testzip())
|
|
||||||
html = z.read("front/index.html").decode("utf-8", "replace")
|
|
||||||
print("has pwdMgrForm:", "pwdMgrForm" in html)
|
|
||||||
print("has name=\"username\":", 'name="username"' in html)
|
|
||||||
print("has name=\"password\":", 'name="password"' in html)
|
|
||||||
cls = z.read("WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class")
|
|
||||||
print("default url in class:", b"front/index.html" in cls)
|
|
||||||
print("entries replaced OK")
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import zipfile, os, shutil
|
|
||||||
|
|
||||||
WAR = "docker/cas.war"
|
|
||||||
CLS_DIR = "target/cas/WEB-INF/classes/org/apereo/cas/web/custom/controller"
|
|
||||||
SRC_INDEX = "src/main/webapp/front/index.html"
|
|
||||||
IDX_ENTRY = "front/index.html"
|
|
||||||
|
|
||||||
files = {
|
|
||||||
os.path.join(CLS_DIR, "LoginController.class"): "WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class",
|
|
||||||
os.path.join(CLS_DIR, "LoginController$LocalAuthenticationResult.class"): "WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController$LocalAuthenticationResult.class",
|
|
||||||
SRC_INDEX: IDX_ENTRY,
|
|
||||||
"src/main/resources/application.yml": "WEB-INF/classes/application.yml",
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = WAR + ".tmp"
|
|
||||||
zin = zipfile.ZipFile(WAR)
|
|
||||||
zout = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
|
|
||||||
for item in zin.infolist():
|
|
||||||
if item.filename in files.values():
|
|
||||||
continue
|
|
||||||
zout.writestr(item, zin.read(item.filename))
|
|
||||||
zin.close()
|
|
||||||
for src, entry in files.items():
|
|
||||||
data = open(src, "rb").read()
|
|
||||||
# 用原条目元数据保持权限/时间戳
|
|
||||||
zi = zipfile.ZipInfo(entry)
|
|
||||||
zi.date_time = (2026, 8, 3, 18, 10, 0)
|
|
||||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
|
||||||
zi.external_attr = 0o644 << 16
|
|
||||||
zout.writestr(zi, data)
|
|
||||||
zout.close()
|
|
||||||
os.replace(tmp, WAR)
|
|
||||||
|
|
||||||
# 校验
|
|
||||||
z = zipfile.ZipFile(WAR)
|
|
||||||
print("testzip:", z.testzip())
|
|
||||||
idx = z.read(IDX_ENTRY).decode("utf-8", "replace")
|
|
||||||
cls = z.read("WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class").decode("latin1")
|
|
||||||
yml = z.read("WEB-INF/classes/application.yml").decode("utf-8", "replace")
|
|
||||||
print("index.html has ?v=1.8:", "?v=1.8" in idx)
|
|
||||||
print("index.html has real form action /api/login:", 'action="/api/login"' in idx)
|
|
||||||
print("index.html has loginname hidden:", 'name="loginname"' in idx)
|
|
||||||
print("class has window.location.href (v1.8 success):", "window.location.href" in cls)
|
|
||||||
print("class has guard /cas/login fallback:", 'contains("/cas/login")' in cls)
|
|
||||||
yml_ok = "default-redirect-url: /front/index.html" in yml and "default-redirect-url: /cas/login" not in yml
|
|
||||||
print("application.yml default-redirect-url fixed (/front/index.html):", yml_ok)
|
|
||||||
print("WAR size:", os.path.getsize(WAR))
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import urllib.request, ssl, paramiko, json, base64, bcrypt, socket
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
|
|
||||||
BASE="http://sso.yunhaixingtong.com"; HOST="sso.yunhaixingtong.com"; PORT=80
|
|
||||||
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
||||||
PLAIN="SsoTest@2026"
|
|
||||||
h=bcrypt.hashpw(PLAIN.encode(), bcrypt.gensalt(rounds=10)).decode()
|
|
||||||
if h.startswith("$2b$"): h=h.replace("$2b$","$2a$",1)
|
|
||||||
USER="ssotest_tmp"
|
|
||||||
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
PWD="CVf^x$sW*b6CGHxh"
|
|
||||||
|
|
||||||
# write SQL via sftp (avoid shell $ expansion of the BCrypt hash)
|
|
||||||
seed=f"DELETE FROM sys_user WHERE username='{USER}';\nINSERT INTO sys_user(username,create_time,create_user,password,disabled,expired) VALUES('{USER}',NOW(),0,'{h}',0,0);\n"
|
|
||||||
sftp=ssh.open_sftp(); sftp.open('/tmp/seed.sql','w').write(seed); sftp.close()
|
|
||||||
print("seed.sql:\n", seed)
|
|
||||||
print("import result:", run(f"docker exec -i mysql8 mysql -uroot -p'{PWD}' bsch_userbase < /tmp/seed.sql 2>&1").strip())
|
|
||||||
|
|
||||||
pub=json.loads(urllib.request.urlopen(urllib.request.Request(BASE+"/api/public-key"),timeout=20,context=ctx).read().decode())["data"]["publicKey"]
|
|
||||||
rsa_pub=load_der_public_key(base64.b64decode(pub))
|
|
||||||
def rsa_enc(s): return base64.b64encode(rsa_pub.encrypt(s.encode(), padding.PKCS1v15())).decode()
|
|
||||||
|
|
||||||
def raw_post(extra=None):
|
|
||||||
body=f"loginname={rsa_enc(USER)}&pwd={rsa_enc(PLAIN)}"
|
|
||||||
hh={"Host":HOST,"Content-Type":"application/x-www-form-urlencoded","Content-Length":str(len(body))}
|
|
||||||
if extra: hh.update(extra)
|
|
||||||
req="POST /api/login HTTP/1.1\r\n"+"\r\n".join(f"{k}: {v}" for k,v in hh.items())+"\r\nConnection: close\r\n\r\n"+body
|
|
||||||
s=socket.create_connection((HOST,PORT),timeout=20); s.sendall(req.encode()); data=b""
|
|
||||||
while True:
|
|
||||||
c=s.recv(4096)
|
|
||||||
if not c: break
|
|
||||||
data+=c
|
|
||||||
s.close(); return data.decode('utf-8','ignore')
|
|
||||||
|
|
||||||
print("\n=== SUCCESS login, NATIVE FORM (no XHR) ===")
|
|
||||||
r=raw_post(); print(r.split("\r\n\r\n",1)[0][:1000]); print("BODY:", r.split("\r\n\r\n",1)[1][:300] if "\r\n\r\n" in r else "(no body)")
|
|
||||||
|
|
||||||
print("\n=== SUCCESS login, AJAX (X-Requested-With) ===")
|
|
||||||
r2=raw_post({"X-Requested-With":"XMLHttpRequest","Accept":"application/json"})
|
|
||||||
print(r2.split("\r\n\r\n",1)[0][:400]); print("BODY:", r2.split("\r\n\r\n",1)[1][:200] if "\r\n\r\n" in r2 else "")
|
|
||||||
|
|
||||||
# cleanup
|
|
||||||
sftp=ssh.open_sftp(); sftp.open('/tmp/del.sql','w').write(f"DELETE FROM sys_user WHERE username='{USER}';\n"); sftp.close()
|
|
||||||
run(f"docker exec -i mysql8 mysql -uroot -p'{PWD}' bsch_userbase < /tmp/del.sql 2>&1")
|
|
||||||
print("\ncleanup done")
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import urllib.request, ssl, paramiko, json, base64, bcrypt
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
|
|
||||||
BASE="http://sso.yunhaixingtong.com"
|
|
||||||
HOST="sso.yunhaixingtong.com"; PORT=80
|
|
||||||
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
||||||
|
|
||||||
# ---- 1) known password + BCrypt hash (strength 10, prefix $2a$ to match Spring) ----
|
|
||||||
PLAIN="SsoTest@2026"
|
|
||||||
h=bcrypt.hashpw(PLAIN.encode(), bcrypt.gensalt(rounds=10)).decode()
|
|
||||||
if h.startswith("$2b$"): h=h.replace("$2b$","$2a$",1)
|
|
||||||
print("BCrypt hash:", h)
|
|
||||||
|
|
||||||
# ---- 2) insert temp user into bsch_userbase ----
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
PWD="CVf^x$sW*b6CGHxh"
|
|
||||||
USER="ssotest_tmp"
|
|
||||||
run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e \"DELETE FROM sys_user WHERE username='{USER}';\" 2>&1")
|
|
||||||
ins=run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e \"INSERT INTO sys_user(username,create_time,create_user,password,disabled,expired) VALUES('{USER}',NOW(),0,'{h}',0,0);\" 2>&1")
|
|
||||||
print("INSERT result:", ins.strip())
|
|
||||||
|
|
||||||
# ---- 3) RSA public key ----
|
|
||||||
pub=json.loads(urllib.request.urlopen(urllib.request.Request(BASE+"/api/public-key"),timeout=20,context=ctx).read().decode())["data"]["publicKey"]
|
|
||||||
der=base64.b64decode(pub)
|
|
||||||
rsa_pub=load_der_public_key(der)
|
|
||||||
def rsa_enc(s):
|
|
||||||
return base64.b64encode(rsa_pub.encrypt(s.encode(), padding.PKCS1v15())).decode()
|
|
||||||
|
|
||||||
# ---- 4) raw socket POST (native form, NO X-Requested-With) ----
|
|
||||||
def raw_post(headers_extra=None):
|
|
||||||
body=f"loginname={rsa_enc(USER)}&pwd={rsa_enc(PLAIN)}"
|
|
||||||
h={"Host":HOST,"Content-Type":"application/x-www-form-urlencoded",
|
|
||||||
"Content-Length":str(len(body))}
|
|
||||||
if headers_extra: h.update(headers_extra)
|
|
||||||
req="POST /api/login HTTP/1.1\r\n"+"\r\n".join(f"{k}: {v}" for k,v in h.items())+"\r\nConnection: close\r\n\r\n"+body
|
|
||||||
s=socket_create(); s.sendall(req.encode())
|
|
||||||
data=b""
|
|
||||||
while True:
|
|
||||||
c=s.recv(4096)
|
|
||||||
if not c: break
|
|
||||||
data+=c
|
|
||||||
s.close()
|
|
||||||
return data.decode('utf-8','ignore')
|
|
||||||
|
|
||||||
import socket
|
|
||||||
def socket_create(): return socket.create_connection((HOST,PORT),timeout=20)
|
|
||||||
|
|
||||||
print("\n=== SUCCESS login, NATIVE FORM (no XHR) -> expect 200 HTML w/ window.location.href ===")
|
|
||||||
resp=raw_post()
|
|
||||||
head=resp.split("\r\n\r\n",1)[0]
|
|
||||||
print(head[:1200])
|
|
||||||
|
|
||||||
print("\n=== SUCCESS login, AJAX (X-Requested-With) -> expect JSON {code:0,data:{url}} ===")
|
|
||||||
resp2=raw_post({"X-Requested-With":"XMLHttpRequest","Accept":"application/json"})
|
|
||||||
head2=resp2.split("\r\n\r\n",1)[0]
|
|
||||||
print(head2[:800])
|
|
||||||
|
|
||||||
# cleanup
|
|
||||||
run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e \"DELETE FROM sys_user WHERE username='{USER}';\" 2>&1")
|
|
||||||
print("\ncleanup done")
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import urllib.request, json, base64, ssl
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
|
||||||
|
|
||||||
HOST_HDR = "sso.yunhaixingtong.com"
|
|
||||||
BASE = "http://192.168.10.157:80"
|
|
||||||
|
|
||||||
def req(method, path, data=None, headers=None):
|
|
||||||
h = {"Host": HOST_HDR}
|
|
||||||
if headers: h.update(headers)
|
|
||||||
if data is not None:
|
|
||||||
data = urllib.parse.urlencode(data).encode()
|
|
||||||
h["Content-Type"] = "application/x-www-form-urlencoded"
|
|
||||||
r = urllib.request.Request(BASE+path, data=data, headers=h, method=method)
|
|
||||||
try:
|
|
||||||
resp = urllib.request.urlopen(r, timeout=20)
|
|
||||||
body = resp.read().decode("utf-8","replace")
|
|
||||||
return resp.status, dict(resp.getheaders()), body
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
return e.code, dict(e.headers), e.read().decode("utf-8","replace")
|
|
||||||
|
|
||||||
# 1) 取公钥
|
|
||||||
st, hd, body = req("GET", "/api/public-key")
|
|
||||||
print("=== /api/public-key ===")
|
|
||||||
print("status", st)
|
|
||||||
print("body", body[:600])
|
|
||||||
pk = json.loads(body)["data"]["publicKey"]
|
|
||||||
print("publicKey len", len(pk))
|
|
||||||
print("publicKey head:", pk[:60])
|
|
||||||
|
|
||||||
# 重组为合法 PEM(端点把空白去掉了,补回换行)
|
|
||||||
b64 = pk.replace("-----BEGIN PUBLIC KEY-----","").replace("-----END PUBLIC KEY-----","").replace("-----BEGIN RSA PUBLIC KEY-----","").replace("-----END RSA PUBLIC KEY-----","").replace("\n","").replace(" ","")
|
|
||||||
pem = "-----BEGIN PUBLIC KEY-----\n" + "\n".join(b64[i:i+64] for i in range(0,len(b64),64)) + "\n-----END PUBLIC KEY-----"
|
|
||||||
pub = load_pem_public_key(pem.encode())
|
|
||||||
|
|
||||||
def enc(pt):
|
|
||||||
ct = pub.encrypt(pt.encode("utf-8"), asym_padding.PKCS1v15())
|
|
||||||
return base64.b64encode(ct).decode()
|
|
||||||
|
|
||||||
# 用户名/密码:用已知可登录的 yunhaisch(密码需正确才能成功;若不知道正确密码,可先看错误分支)
|
|
||||||
USERNAME = "yunhaisch"
|
|
||||||
PASSWORD = input("请输入 yunhaisch 的密码(仅本地测试用,不会外发): ").strip()
|
|
||||||
|
|
||||||
euser = enc(USERNAME)
|
|
||||||
epwd = enc(PASSWORD)
|
|
||||||
|
|
||||||
# 2) 登录(不带 service)
|
|
||||||
st, hd, body = req("POST", "/api/login", data={
|
|
||||||
"loginname": euser, "pwd": epwd
|
|
||||||
})
|
|
||||||
print("\n=== POST /api/login (无 service) ===")
|
|
||||||
print("status", st)
|
|
||||||
print("Set-Cookie:", hd.get("Set-Cookie"))
|
|
||||||
print("body", body[:800])
|
|
||||||
try:
|
|
||||||
j = json.loads(body)
|
|
||||||
print(">>> code=", j.get("code"), "msg=", j.get("msg"))
|
|
||||||
print(">>> data.url=", (j.get("data") or {}).get("url"))
|
|
||||||
except Exception as e:
|
|
||||||
print("parse err", e)
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import urllib.request, ssl, paramiko, json, base64, bcrypt, socket
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
|
|
||||||
BASE="http://sso.yunhaixingtong.com"; HOST="sso.yunhaixingtong.com"; PORT=80
|
|
||||||
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
||||||
PLAIN="SsoTest@2026"
|
|
||||||
h=bcrypt.hashpw(PLAIN.encode(), bcrypt.gensalt(rounds=10)).decode()
|
|
||||||
if h.startswith("$2b$"): h=h.replace("$2b$","$2a$",1)
|
|
||||||
USER="ssotest_tmp"
|
|
||||||
UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
|
|
||||||
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
PWD="CVf^x$sW*b6CGHxh"
|
|
||||||
seed=f"DELETE FROM sys_user WHERE username='{USER}';\nINSERT INTO sys_user(username,create_time,create_user,password,disabled,expired) VALUES('{USER}',NOW(),0,'{h}',0,0);\n"
|
|
||||||
sftp=ssh.open_sftp(); sftp.open('/tmp/seed.sql','w').write(seed); sftp.close()
|
|
||||||
run(f"docker exec -i mysql8 mysql -uroot -p'{PWD}' bsch_userbase < /tmp/seed.sql 2>&1")
|
|
||||||
|
|
||||||
pub=json.loads(urllib.request.urlopen(urllib.request.Request(BASE+"/api/public-key"),timeout=20,context=ctx).read().decode())["data"]["publicKey"]
|
|
||||||
rsa_pub=load_der_public_key(base64.b64decode(pub))
|
|
||||||
def rsa_enc(s): return base64.b64encode(rsa_pub.encrypt(s.encode(), padding.PKCS1v15())).decode()
|
|
||||||
|
|
||||||
def raw_post(extra=None):
|
|
||||||
body=f"loginname={rsa_enc(USER)}&pwd={rsa_enc(PLAIN)}"
|
|
||||||
hh={"Host":HOST,"User-Agent":UA,"Content-Type":"application/x-www-form-urlencoded","Content-Length":str(len(body))}
|
|
||||||
if extra: hh.update(extra)
|
|
||||||
req="POST /api/login HTTP/1.1\r\n"+"\r\n".join(f"{k}: {v}" for k,v in hh.items())+"\r\nConnection: close\r\n\r\n"+body
|
|
||||||
s=socket.create_connection((HOST,PORT),timeout=20); s.sendall(req.encode()); data=b""
|
|
||||||
while True:
|
|
||||||
c=s.recv(4096)
|
|
||||||
if not c: break
|
|
||||||
data+=c
|
|
||||||
s.close(); return data.decode('utf-8','ignore')
|
|
||||||
|
|
||||||
print("=== SUCCESS login w/ User-Agent, NATIVE FORM ===")
|
|
||||||
r=raw_post()
|
|
||||||
print("HEAD:\n", r.split("\r\n\r\n",1)[0][:700])
|
|
||||||
print("BODY:\n", r.split("\r\n\r\n",1)[1][:400] if "\r\n\r\n" in r else "(no body)")
|
|
||||||
|
|
||||||
print("\n=== SUCCESS login w/ User-Agent, AJAX ===")
|
|
||||||
r2=raw_post({"X-Requested-With":"XMLHttpRequest","Accept":"application/json"})
|
|
||||||
print("HEAD:\n", r2.split("\r\n\r\n",1)[0][:400])
|
|
||||||
print("BODY:\n", r2.split("\r\n\r\n",1)[1][:300] if "\r\n\r\n" in r2 else "")
|
|
||||||
|
|
||||||
sftp=ssh.open_sftp(); sftp.open('/tmp/del.sql','w').write(f"DELETE FROM sys_user WHERE username='{USER}';\n"); sftp.close()
|
|
||||||
run(f"docker exec -i mysql8 mysql -uroot -p'{PWD}' bsch_userbase < /tmp/del.sql 2>&1")
|
|
||||||
print("\ncleanup done"); ssh.close()
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import urllib.request, urllib.error, ssl
|
|
||||||
|
|
||||||
BASE = "http://sso.yunhaixingtong.com"
|
|
||||||
ctx = ssl.create_default_context()
|
|
||||||
ctx.check_hostname = False
|
|
||||||
ctx.verify_mode = ssl.CERT_NONE
|
|
||||||
|
|
||||||
def req(method, path, data=None, headers=None):
|
|
||||||
url = BASE + path
|
|
||||||
h = headers or {}
|
|
||||||
r = urllib.request.Request(url, data=data, method=method, headers=h)
|
|
||||||
try:
|
|
||||||
resp = urllib.request.urlopen(r, timeout=20, context=ctx)
|
|
||||||
body = resp.read().decode('utf-8', 'ignore')
|
|
||||||
return resp.status, dict(resp.getheaders()), body
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
body = e.read().decode('utf-8', 'ignore')
|
|
||||||
return e.code, dict(e.headers), body
|
|
||||||
|
|
||||||
print("=== 1) /front/index.html WITHOUT auth (is it behind CAS?) ===")
|
|
||||||
st, hd, body = req("GET", "/front/index.html")
|
|
||||||
print("status:", st, "| Location:", hd.get("Location"))
|
|
||||||
print("body has /cas/login:", "/cas/login" in body, "| ?v=1.7:", "v=1.7" in body,
|
|
||||||
"| action=/api/login:", 'action="/api/login"' in body, "| name=username:", 'name="username"' in body)
|
|
||||||
|
|
||||||
print("\n=== 2) POST /api/login BOGUS (native form, no XHR header) ===")
|
|
||||||
form = "loginname=AAA&pwd=BBB&captcha=".encode()
|
|
||||||
st, hd, body = req("POST", "/api/login", data=form, headers={"Content-Type":"application/x-www-form-urlencoded"})
|
|
||||||
print("status:", st, "| Location:", hd.get("Location"))
|
|
||||||
|
|
||||||
print("\n=== 3) POST /api/login BOGUS (AJAX X-Requested-With) ===")
|
|
||||||
st, hd, body = req("POST", "/api/login", data=form, headers={"Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"})
|
|
||||||
print("status:", st, "| body[:180]:", body[:180])
|
|
||||||
|
|
||||||
print("\n=== 4) /api/public-key ===")
|
|
||||||
st, hd, body = req("GET", "/api/public-key")
|
|
||||||
print("status:", st, "| body[:160]:", body[:160])
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import urllib.request, ssl, paramiko, json, base64, bcrypt, socket
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
|
|
||||||
BASE="http://sso.yunhaixingtong.com"; HOST="sso.yunhaixingtong.com"; PORT=80
|
|
||||||
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
||||||
PLAIN="SsoTest@2026"
|
|
||||||
h=bcrypt.hashpw(PLAIN.encode(), bcrypt.gensalt(rounds=10)).decode()
|
|
||||||
if h.startswith("$2b$"): h=h.replace("$2b$","$2a$",1)
|
|
||||||
print("hash self-check (python):", bcrypt.checkpw(PLAIN.encode(), h.encode()))
|
|
||||||
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
PWD="CVf^x$sW*b6CGHxh"; USER="ssotest_tmp"
|
|
||||||
run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e \"DELETE FROM sys_user WHERE username='{USER}'; INSERT INTO sys_user(username,create_time,create_user,password,disabled,expired) VALUES('{USER}',NOW(),0,'{h}',0,0);\" 2>&1")
|
|
||||||
|
|
||||||
pub=json.loads(urllib.request.urlopen(urllib.request.Request(BASE+"/api/public-key"),timeout=20,context=ctx).read().decode())["data"]["publicKey"]
|
|
||||||
rsa_pub=load_der_public_key(base64.b64decode(pub))
|
|
||||||
def rsa_enc(s): return base64.b64encode(rsa_pub.encrypt(s.encode(), padding.PKCS1v15())).decode()
|
|
||||||
|
|
||||||
body=f"loginname={rsa_enc(USER)}&pwd={rsa_enc(PLAIN)}"
|
|
||||||
req="POST /api/login HTTP/1.1\r\nHost: %s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s"%(HOST,len(body),body)
|
|
||||||
s=socket.create_connection((HOST,PORT),timeout=20); s.sendall(req.encode())
|
|
||||||
data=b""
|
|
||||||
while True:
|
|
||||||
c=s.recv(4096)
|
|
||||||
if not c: break
|
|
||||||
data+=c
|
|
||||||
s.close()
|
|
||||||
print("\nRESPONSE HEAD:\n", data.decode('utf-8','ignore').split("\r\n\r\n",1)[0][:600])
|
|
||||||
|
|
||||||
# fetch logs mentioning ssotest_tmp
|
|
||||||
print("\n=== CAS logs (ssotest_tmp) ===")
|
|
||||||
logs=run("docker logs --tail 60 cas-sso 2>&1 | grep -i 'ssotest_tmp\\|尝试登录\\|密码错误\\|Padding\\|Decryption\\|Illegal' | tail -20")
|
|
||||||
print(logs if logs.strip() else "(no matching lines)")
|
|
||||||
run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e \"DELETE FROM sys_user WHERE username='{USER}';\" 2>&1")
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import urllib.request, ssl, paramiko, json
|
|
||||||
|
|
||||||
BASE="http://sso.yunhaixingtong.com"
|
|
||||||
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
||||||
|
|
||||||
# 1) live RSA public key
|
|
||||||
r=urllib.request.urlopen(urllib.request.Request(BASE+"/api/public-key"), timeout=20, context=ctx)
|
|
||||||
pub=json.loads(r.read().decode())["data"]["publicKey"]
|
|
||||||
print("PUBLIC_KEY_LEN:", len(pub))
|
|
||||||
print("PUBLIC_KEY_HEAD:", pub[:40], "...")
|
|
||||||
|
|
||||||
# 2) sys_user schema + sample usernames (no passwords)
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); return o.read().decode('utf-8','ignore')+("ERR:"+e.read().decode('utf-8','ignore') if e.read() else "")
|
|
||||||
PWD="CVf^x$sW*b6CGHxh"
|
|
||||||
print("\n=== DESCRIBE sys_user ===")
|
|
||||||
print(run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e 'DESCRIBE sys_user;' 2>&1 | head -40"))
|
|
||||||
print("\n=== sample usernames (limit 10) ===")
|
|
||||||
print(run(f"docker exec mysql8 mysql -uroot -p'{PWD}' bsch_userbase -e 'SELECT username, disabled, expired FROM sys_user LIMIT 10;' 2>&1 | head -20"))
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import urllib.request, ssl, paramiko, json, base64, bcrypt, socket, time, re
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
|
|
||||||
BASE="http://sso.yunhaixingtong.com"; HOST="sso.yunhaixingtong.com"; PORT=80
|
|
||||||
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
||||||
PLAIN="SsoTest@2026"
|
|
||||||
h=bcrypt.hashpw(PLAIN.encode(), bcrypt.gensalt(rounds=10)).decode()
|
|
||||||
if h.startswith("$2b$"): h=h.replace("$2b$","$2a$",1)
|
|
||||||
USER="ssotest_tmp"; UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36"
|
|
||||||
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("192.168.10.157", port=22, username="yunhai", password="123456", timeout=20)
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c); out=o.read().decode('utf-8','ignore'); err=e.read().decode('utf-8','ignore'); return out+("ERR:"+err if err else "")
|
|
||||||
PWD="CVf^x$sW*b6CGHxh"
|
|
||||||
seed=f"DELETE FROM sys_user WHERE username='{USER}'; INSERT INTO sys_user(username,create_time,create_user,password,disabled,expired) VALUES('{USER}',NOW(),0,'{h}',0,0);"
|
|
||||||
sftp=ssh.open_sftp(); sftp.open('/tmp/seed.sql','w').write(seed); sftp.close()
|
|
||||||
print("seed:", run(f"docker exec -i mysql8 mysql -uroot -p'{PWD}' bsch_userbase < /tmp/seed.sql 2>&1").strip())
|
|
||||||
|
|
||||||
def raw_post(extra=None):
|
|
||||||
pub=json.loads(urllib.request.urlopen(urllib.request.Request(BASE+"/api/public-key"),timeout=20,context=ctx).read().decode())["data"]["publicKey"]
|
|
||||||
rsa_pub=load_der_public_key(base64.b64decode(pub))
|
|
||||||
def enc(s): return base64.b64encode(rsa_pub.encrypt(s.encode(), padding.PKCS1v15())).decode()
|
|
||||||
body=f"loginname={enc(USER)}&pwd={enc(PLAIN)}"
|
|
||||||
hh={"Host":HOST,"User-Agent":UA,"Content-Type":"application/x-www-form-urlencoded","Content-Length":str(len(body))}
|
|
||||||
if extra: hh.update(extra)
|
|
||||||
req="POST /api/login HTTP/1.1\r\n"+"\r\n".join(f"{k}: {v}" for k,v in hh.items())+"\r\nConnection: close\r\n\r\n"+body
|
|
||||||
s=socket.create_connection((HOST,PORT),timeout=20); s.sendall(req.encode()); data=b""
|
|
||||||
while True:
|
|
||||||
c=s.recv(4096)
|
|
||||||
if not c: break
|
|
||||||
data+=c
|
|
||||||
s.close(); return data.decode('utf-8','ignore')
|
|
||||||
|
|
||||||
for _ in range(20):
|
|
||||||
try:
|
|
||||||
r=raw_post({"X-Requested-With":"XMLHttpRequest","Accept":"application/json"})
|
|
||||||
if "200 OK" in r.split("\r\n\r\n",1)[0]: break
|
|
||||||
except Exception: pass
|
|
||||||
time.sleep(6)
|
|
||||||
|
|
||||||
print("\n=== AJAX (what the page SHOULD use) ===")
|
|
||||||
r=raw_post({"X-Requested-With":"XMLHttpRequest","Accept":"application/json"})
|
|
||||||
body=r.split("\r\n\r\n",1)[1] if "\r\n\r\n" in r else ""
|
|
||||||
print("RAW BODY:", body[:300])
|
|
||||||
m=re.search(r'"url"\s*:\s*"([^"]*)"', body)
|
|
||||||
print("-> data.url:", m.group(1) if m else "NONE / NOT PRESENT")
|
|
||||||
|
|
||||||
print("\n=== NATIVE FORM (what index.html currently does) ===")
|
|
||||||
r2=raw_post()
|
|
||||||
head=r2.split("\r\n\r\n",1)[0]; body2=r2.split("\r\n\r\n",1)[1] if "\r\n\r\n" in r2 else ""
|
|
||||||
print("STATUS LINE:", head.split("\r\n",1)[0])
|
|
||||||
print("BODY[:400]:", body2[:400])
|
|
||||||
|
|
||||||
sftp=ssh.open_sftp(); sftp.open('/tmp/del.sql','w').write(f"DELETE FROM sys_user WHERE username='{USER}';\n"); sftp.close()
|
|
||||||
print("cleanup:", run(f"docker exec -i mysql8 mysql -uroot -p'{PWD}' bsch_userbase < /tmp/del.sql 2>&1").strip())
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import socket
|
|
||||||
HOST="sso.yunhaixingtong.com"; PORT=80
|
|
||||||
body="loginname=AAA&pwd=BBB&captcha="
|
|
||||||
req=(
|
|
||||||
"POST /api/login HTTP/1.1\r\n"
|
|
||||||
f"Host: {HOST}\r\n"
|
|
||||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
|
||||||
f"Content-Length: {len(body)}\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n"
|
|
||||||
+ body
|
|
||||||
)
|
|
||||||
s=socket.create_connection((HOST,PORT),timeout=20)
|
|
||||||
s.sendall(req.encode())
|
|
||||||
data=b""
|
|
||||||
while True:
|
|
||||||
chunk=s.recv(4096)
|
|
||||||
if not chunk: break
|
|
||||||
data+=chunk
|
|
||||||
s.close()
|
|
||||||
text=data.decode('utf-8','ignore')
|
|
||||||
# print status line + headers (up to first blank line)
|
|
||||||
head=text.split("\r\n\r\n",1)[0]
|
|
||||||
print("=== RAW RESPONSE HEAD (native form POST) ===")
|
|
||||||
print(head[:1500])
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# 通过 SSH 在远端以 sudo 执行脚本(从 stdin 读取)
|
|
||||||
# 用法: python3 remote_run.py <host> <port> <user> < script.sh
|
|
||||||
# 依赖环境变量 SSHPASS
|
|
||||||
import os, sys, paramiko
|
|
||||||
|
|
||||||
def main():
|
|
||||||
host = sys.argv[1]
|
|
||||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 22
|
|
||||||
user = sys.argv[3] if len(sys.argv) > 3 else "root"
|
|
||||||
pw = os.environ.get("SSHPASS")
|
|
||||||
if not pw:
|
|
||||||
sys.stderr.write("ERROR: 请先设置环境变量 SSHPASS\n")
|
|
||||||
sys.exit(1)
|
|
||||||
script = sys.stdin.read()
|
|
||||||
c = paramiko.SSHClient()
|
|
||||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
c.connect(host, port, user, password=pw, timeout=30)
|
|
||||||
stdin, stdout, stderr = c.exec_command("sudo -S bash -s", timeout=1800)
|
|
||||||
stdin.write(pw + "\n")
|
|
||||||
stdin.write(script)
|
|
||||||
stdin.flush()
|
|
||||||
stdin.channel.shutdown_write()
|
|
||||||
sys.stdout.write(stdout.read().decode(errors="replace"))
|
|
||||||
err = stderr.read().decode(errors="replace")
|
|
||||||
if err.strip():
|
|
||||||
sys.stderr.write("STDERR:\n" + err)
|
|
||||||
c.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,130 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# CAS 启动入口:
|
# CAS 启动入口(原生 Nacos 配置注入版)
|
||||||
# 1) 从 Nacos 拉取「公共基础配置」(common.properties) 与「CAS 自身配置」(cas-prod.properties)
|
# 业务配置改由 org.apereo.cas.web.custom.config.nacos.NacosEnvironmentPostProcessor
|
||||||
# 2) 用 common.properties 的 MySQL 连接 + cas-prod.properties 的 database 拼出完整 JDBC URL
|
# 在 Spring Boot 环境准备阶段从 Nacos 原生拉取:
|
||||||
# 3) 写出 /etc/cas/config/cas.properties 后启动 CAS
|
# - 公共基础配置 common.yaml (namespace=NACOS_NAMESPACE, group=DEFAULT_GROUP)
|
||||||
# 说明:CAS 不自建 MySQL,统一读 Nacos 公共配置;库名写在 CAS 自己的配置里。
|
# - CAS 自身配置 cas.yaml (namespace=NACOS_NAMESPACE, group=CAS)
|
||||||
|
# 不再需要本脚本写 /etc/cas/config/cas.properties,CAS 镜像保持环境无关。
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
CONFIG_PATH=/etc/cas/config/cas.properties
|
echo "[run-cas] Nacos 原生配置注入:NACOS_ADDR=${NACOS_ADDR:-<unset>} NACOS_NAMESPACE=${NACOS_NAMESPACE:-<unset>} NACOS_USER=${NACOS_USER:-<unset>}"
|
||||||
# 备份镜像内烤好的兜底配置,供 Nacos 不可用时回退
|
|
||||||
cp -f "$CONFIG_PATH" "${CONFIG_PATH}.baked" 2>/dev/null || true
|
|
||||||
|
|
||||||
NACOS_ADDR="${NACOS_ADDR:-nacos:8848}"
|
|
||||||
NACOS_USER="${NACOS_USER:-nacos}"
|
|
||||||
NACOS_PASS="${NACOS_PASS:-nacos}"
|
|
||||||
|
|
||||||
# 公共基础配置(含 mysql / redis / rabbitmq 连接信息)
|
|
||||||
BASE_DATA_ID="${CAS_BASE_CONFIG_DATA_ID:-common.properties}"
|
|
||||||
BASE_GROUP="${CAS_BASE_CONFIG_GROUP:-DEFAULT_GROUP}"
|
|
||||||
BASE_NS="${CAS_BASE_CONFIG_NAMESPACE:-public}"
|
|
||||||
# CAS 自身配置
|
|
||||||
CAS_DATA_ID="${CAS_CONFIG_DATA_ID:-cas-prod.properties}"
|
|
||||||
CAS_GROUP="${CAS_CONFIG_GROUP:-CAS}"
|
|
||||||
CAS_NS="${CAS_CONFIG_NAMESPACE:-cas}"
|
|
||||||
|
|
||||||
TOKEN=""
|
|
||||||
|
|
||||||
# 取 Nacos 登录 token(鉴权关闭时返回空,fetch 会自动降级)
|
|
||||||
get_token() {
|
|
||||||
TOKEN=$(curl -s -m 10 -X POST "http://${NACOS_ADDR}/nacos/v1/auth/login?username=${NACOS_USER}&password=${NACOS_PASS}" \
|
|
||||||
| sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')
|
|
||||||
}
|
|
||||||
|
|
||||||
# 拉取单个配置到 $4;带 token 失败则自动不带 token 重试
|
|
||||||
fetch() {
|
|
||||||
local code
|
|
||||||
if [ -n "$TOKEN" ]; then
|
|
||||||
code=$(curl -s -m 10 -o "$4" -w "%{http_code}" "http://${NACOS_ADDR}/nacos/v1/cs/configs?dataId=$1&group=$2&tenant=$3&accessToken=${TOKEN}")
|
|
||||||
fi
|
|
||||||
if [ -z "$TOKEN" ] || [ "$code" != "200" ] || [ ! -s "$4" ]; then
|
|
||||||
code=$(curl -s -m 10 -o "$4" -w "%{http_code}" "http://${NACOS_ADDR}/nacos/v1/cs/configs?dataId=$1&group=$2&tenant=$3")
|
|
||||||
fi
|
|
||||||
if [ "$code" = "200" ] && [ -s "$4" ]; then
|
|
||||||
echo "[run-cas] 已加载 $1 (group=$2, ns=$3)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "[run-cas] 拉取 $1 失败 http=${code}"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 精确读取 key=value 中的 value(兼容 value 内含 =)
|
|
||||||
val() {
|
|
||||||
awk -v k="$2" 'index($0,"=")>0 && substr($0,1,index($0,"=")-1)==k {print substr($0,index($0,"=")+1); exit}' "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 读键:优先 spring.datasource.*,回退 mysql.* 风格(兼容两种命名习惯)
|
|
||||||
readkey() {
|
|
||||||
local f="$1" k="$2" v
|
|
||||||
v=$(val "$f" "$k"); [ -n "$v" ] && { echo "$v"; return; }
|
|
||||||
v=$(val "$f" "mysql.${k#spring.datasource.}"); [ -n "$v" ] && { echo "$v"; return; }
|
|
||||||
[ "$k" = "spring.datasource.username" ] && val "$f" "mysql.user"
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_baked() {
|
|
||||||
echo "[run-cas] 回退到镜像内烤好的默认配置"
|
|
||||||
cp -f "${CONFIG_PATH}.baked" "$CONFIG_PATH"
|
|
||||||
exec java -jar /app/cas.war
|
|
||||||
}
|
|
||||||
|
|
||||||
get_token
|
|
||||||
|
|
||||||
BASE_TMP="${CONFIG_PATH}.base"
|
|
||||||
CAS_TMP="${CONFIG_PATH}.cas"
|
|
||||||
|
|
||||||
# 1) 公共基础配置(MySQL 连接必须在此)
|
|
||||||
if ! fetch "$BASE_DATA_ID" "$BASE_GROUP" "$BASE_NS" "$BASE_TMP"; then
|
|
||||||
echo "[run-cas] 公共配置缺失,无法拼 JDBC,回退兜底"
|
|
||||||
restore_baked
|
|
||||||
fi
|
|
||||||
|
|
||||||
DS_HOST=$(readkey "$BASE_TMP" "spring.datasource.host")
|
|
||||||
DS_PORT=$(readkey "$BASE_TMP" "spring.datasource.port")
|
|
||||||
[ -z "$DS_PORT" ] && DS_PORT=3306
|
|
||||||
DS_USER=$(readkey "$BASE_TMP" "spring.datasource.username")
|
|
||||||
DS_PASS=$(readkey "$BASE_TMP" "spring.datasource.password")
|
|
||||||
DS_PARAMS=$(readkey "$BASE_TMP" "spring.datasource.params")
|
|
||||||
[ -z "$DS_PARAMS" ] && DS_PARAMS="useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
|
|
||||||
|
|
||||||
# 占位符保护:common.properties 未替换占位符时明确报错,避免静默用假凭据导致连不上 MySQL
|
|
||||||
case "${DS_USER}${DS_PASS}" in
|
|
||||||
*__SHARED_*) echo "[run-cas] 致命:common.properties 中 MySQL 用户名/密码仍为占位符(__SHARED_*),请在 Nacos 填入真实 mysql8 凭据后再启动"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# 2) CAS 自身配置(库名 database 在此;缺省回退 bsch_userbase)
|
|
||||||
DB_NAME=bsch_userbase
|
|
||||||
if fetch "$CAS_DATA_ID" "$CAS_GROUP" "$CAS_NS" "$CAS_TMP"; then
|
|
||||||
_db=$(val "$CAS_TMP" "cas.authn.jdbc.query[0].database")
|
|
||||||
[ -n "$_db" ] && DB_NAME="$_db"
|
|
||||||
else
|
|
||||||
echo "[run-cas] CAS 自身配置缺失,database 用默认 bsch_userbase"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$DS_HOST" ]; then
|
|
||||||
echo "[run-cas] 公共配置缺少 MySQL host,无法拼 JDBC,回退兜底"
|
|
||||||
restore_baked
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3) 拼完整 JDBC URL
|
|
||||||
JDBC_URL="jdbc:mysql://${DS_HOST}:${DS_PORT}/${DB_NAME}?${DS_PARAMS}"
|
|
||||||
|
|
||||||
# 4) 合并:CAS 配置为主,过滤其中旧的 JDBC 连接项,注入公共连接
|
|
||||||
{
|
|
||||||
if [ -f "$CAS_TMP" ]; then
|
|
||||||
grep -vF 'cas.authn.jdbc.query[0].url=' "$CAS_TMP" \
|
|
||||||
| grep -vF 'cas.authn.jdbc.query[0].user=' \
|
|
||||||
| grep -vF 'cas.authn.jdbc.query[0].password=' \
|
|
||||||
| grep -vF 'cas.authn.jdbc.query[0].driverClass='
|
|
||||||
fi
|
|
||||||
echo "cas.authn.jdbc.query[0].url=${JDBC_URL}"
|
|
||||||
[ -n "$DS_USER" ] && echo "cas.authn.jdbc.query[0].user=${DS_USER}"
|
|
||||||
[ -n "$DS_PASS" ] && echo "cas.authn.jdbc.query[0].password=${DS_PASS}"
|
|
||||||
echo "cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver"
|
|
||||||
# 透传公共配置里的 redis / rabbitmq 连接(自定义 /api/login 的 loginService 依赖 Redis 存验证码/失败计数/账号锁定)
|
|
||||||
grep -E '^spring\.redis\.' "$BASE_TMP" 2>/dev/null || true
|
|
||||||
grep -E '^spring\.rabbitmq\.' "$BASE_TMP" 2>/dev/null || true
|
|
||||||
} > "$CONFIG_PATH"
|
|
||||||
|
|
||||||
echo "[run-cas] 生效 JDBC URL: ${JDBC_URL}"
|
|
||||||
echo "[run-cas] 启动 CAS ..."
|
|
||||||
exec java -jar /app/cas.war
|
exec java -jar /app/cas.war
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import paramiko
|
|
||||||
|
|
||||||
HOST = "192.168.10.157"; PORT = 22; USER = "yunhai"; PASS = "123456"
|
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect(HOST, port=PORT, username=USER, password=PASS, timeout=20)
|
|
||||||
|
|
||||||
def run(cmd):
|
|
||||||
stdin, stdout, stderr = ssh.exec_command(cmd)
|
|
||||||
out = stdout.read().decode('utf-8', 'ignore')
|
|
||||||
err = stderr.read().decode('utf-8', 'ignore')
|
|
||||||
return out + ("ERR:" + err if err else "")
|
|
||||||
|
|
||||||
print("=== 1) running container image & status ===")
|
|
||||||
print(run("docker ps --filter name=cas-sso --format '{{.Image}} | {{.Status}}'"))
|
|
||||||
print("inspect image id:")
|
|
||||||
print(run("docker inspect cas-sso --format '{{.Image}}' 2>/dev/null"))
|
|
||||||
|
|
||||||
print("\n=== 2) effective cas.properties default-redirect-url (container) ===")
|
|
||||||
print(run("docker exec cas-sso grep -ri 'default-redirect-url' /etc/cas/config/ 2>/dev/null || echo 'NOT FOUND in /etc/cas/config'"))
|
|
||||||
print(run("docker exec cas-sso sh -c \"cat /etc/cas/config/cas.properties 2>/dev/null | grep -i redirect\" || echo none"))
|
|
||||||
|
|
||||||
print("\n=== 3) controller class feature in RUNNING war (v1.8 has window.location.href) ===")
|
|
||||||
print(run("docker cp cas-sso:/app/cas.war /tmp/live_cas.war 2>&1 && echo COPIED"))
|
|
||||||
# analyze with python on the box? simpler: grep for the literal bytecode string in the war zip entry
|
|
||||||
print(run("docker exec cas-sso sh -c \"cd /tmp && python3 - <<'PY'\nimport zipfile\nz=zipfile.ZipFile('/tmp/live_cas.war')\nc=z.read('WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class').decode('latin1')\nprint('has window.location.href :', 'window.location.href' in c)\nprint('has sendRedirect :', 'sendRedirect' in c)\nprint('has text/html :', 'text/html' in c)\nPY\n\" 2>&1 || echo 'python3 not on box'"))
|
|
||||||
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import paramiko, zipfile
|
|
||||||
|
|
||||||
HOST="192.168.10.157"; USER="yunhai"; PASS="123456"
|
|
||||||
LOCAL = "D:/SoftProject/yunhai/CasSso/cas-overlay-template-5.3/docker/live_cas.war"
|
|
||||||
ssh=paramiko.SSHClient(); ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect(HOST, port=22, username=USER, password=PASS, timeout=20)
|
|
||||||
|
|
||||||
def run(c):
|
|
||||||
i,o,e=ssh.exec_command(c)
|
|
||||||
return o.read().decode('utf-8','ignore') + (("ERR:"+e.read().decode('utf-8','ignore')) if e.read() else "")
|
|
||||||
|
|
||||||
print(run("docker cp cas-sso:/app/cas.war /tmp/live_cas.war && echo COPIED_OK"))
|
|
||||||
sftp=ssh.open_sftp()
|
|
||||||
sftp.get('/tmp/live_cas.war', LOCAL)
|
|
||||||
sftp.close()
|
|
||||||
print("local war size:", __import__('os').path.getsize(LOCAL))
|
|
||||||
|
|
||||||
z=zipfile.ZipFile(LOCAL)
|
|
||||||
c=z.read('WEB-INF/classes/org/apereo/cas/web/custom/controller/LoginController.class').decode('latin1')
|
|
||||||
print("\n=== RUNNING WAR LoginController features ===")
|
|
||||||
print("has window.location.href (v1.8 success):", 'window.location.href' in c)
|
|
||||||
print("has sendRedirect (error path):", 'sendRedirect' in c)
|
|
||||||
print("has text/html (v1.8 success):", 'text/html' in c)
|
|
||||||
idx=z.read('front/index.html').decode('utf-8','replace')
|
|
||||||
print("\n=== RUNNING WAR index.html ===")
|
|
||||||
print("has action=/api/login:", 'action="/api/login"' in idx)
|
|
||||||
print("has ?v=1.8:", '?v=1.8' in idx)
|
|
||||||
print("has name=username:", 'name="username"' in idx)
|
|
||||||
|
|
||||||
print("\n=== container filesystem: any default-redirect-url? ===")
|
|
||||||
print(run("grep -rni 'default-redirect-url' /etc/cas 2>/dev/null; grep -rni 'default-redirect-url' /app/*.properties 2>/dev/null; echo '---env---'; docker exec cas-sso env 2>/dev/null | grep -i 'REDIRECT\\|TGC\\|CAS_CUSTOM' "))
|
|
||||||
print("\n=== effective cas.properties (head) ===")
|
|
||||||
print(run("docker exec cas-sso sh -c 'cat /etc/cas/config/cas.properties 2>/dev/null | head -60'"))
|
|
||||||
print("\n=== JDBC url in container ===")
|
|
||||||
print(run("docker exec cas-sso sh -c 'cat /etc/cas/config/cas.properties 2>/dev/null | grep -i jdbc'"))
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import paramiko
|
|
||||||
HOST="192.168.10.157"; PORT=22; USER="yunhai"; PASS="123456"
|
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect(HOST, port=PORT, username=USER, password=PASS, timeout=20)
|
|
||||||
stdin, stdout, stderr = ssh.exec_command("docker logs --tail 80 cas-sso 2>&1 | tail -80")
|
|
||||||
print(stdout.read().decode('utf-8','ignore'))
|
|
||||||
ssh.close()
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# 通过 SFTP 上传文件到远端(参数: host port user local remote [local remote ...])
|
|
||||||
# 依赖环境变量 SSHPASS
|
|
||||||
import os, sys, paramiko
|
|
||||||
|
|
||||||
def main():
|
|
||||||
host, port, user = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 22, (sys.argv[3] if len(sys.argv) > 3 else "root")
|
|
||||||
pw = os.environ.get("SSHPASS")
|
|
||||||
if not pw:
|
|
||||||
sys.stderr.write("ERROR: 请先设置环境变量 SSHPASS\n"); sys.exit(1)
|
|
||||||
t = paramiko.Transport((host, port))
|
|
||||||
t.connect(username=user, password=pw)
|
|
||||||
sftp = paramiko.SFTPClient.from_transport(t)
|
|
||||||
args = sys.argv[4:]
|
|
||||||
for i in range(0, len(args), 2):
|
|
||||||
local, remote = args[i], args[i+1]
|
|
||||||
parts = [p for p in os.path.dirname(remote).split("/") if p]
|
|
||||||
cur = ""
|
|
||||||
for p in parts:
|
|
||||||
cur += "/" + p
|
|
||||||
try: sftp.stat(cur)
|
|
||||||
except IOError:
|
|
||||||
try: sftp.mkdir(cur)
|
|
||||||
except IOError: pass
|
|
||||||
sftp.put(local, remote)
|
|
||||||
print(f"PUT {local} -> {remote}")
|
|
||||||
t.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
package org.apereo.cas.web.custom.config.nacos;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.MapPropertySource;
|
||||||
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Spring Boot 环境准备阶段从 Nacos 拉取配置并注入 Environment,
|
||||||
|
* 使 CAS 5.3 能像 Spring Cloud 应用一样读到 Nacos 上的 common.yaml / cas.yaml。
|
||||||
|
*
|
||||||
|
* 背景:CAS 5.3 无原生 nacos config 模块,且不自带 nacos-client(其 Jackson 2.8/2.9 与
|
||||||
|
* nacos-client 期望的版本冲突)。这里直接用 Nacos 开放 HTTP API(与 run-cas.sh 同一套接口)
|
||||||
|
* 拉取配置,用 SnakeYAML(CAS webapp 已内置)解析,零额外依赖、无版本冲突风险。
|
||||||
|
*
|
||||||
|
* 注入优先级:插在 classpath application.yml 之前,因此 Nacos 配置会覆盖 application.yml 中的
|
||||||
|
* 同名键;但不覆盖系统属性/环境变量(更安全的兜底顺序)。
|
||||||
|
* 若 Nacos 不可达或配置缺失,fail-fast=false,直接跳过 -> 回退 application.yml(本地离线开发友好)。
|
||||||
|
*
|
||||||
|
* 环境隔离沿用 namespace 模型:NACOS_NAMESPACE 环境变量决定连哪个命名空间(dev/prod/空=public),
|
||||||
|
* 与 UserBase 共用同一套隔离逻辑。
|
||||||
|
*/
|
||||||
|
public class NacosEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
|
||||||
|
|
||||||
|
private static final String PROPERTY_SOURCE_NAME = "nacosConfig";
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(NacosEnvironmentPostProcessor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||||
|
// 总开关:默认开启;设为 false 可完全跳过(纯离线本地开发)
|
||||||
|
if (!Boolean.parseBoolean(firstNonNull(environment.getProperty("nacos.config.enabled"), "true"))) {
|
||||||
|
log.info("[nacos] nacos.config.enabled=false,跳过 Nacos 配置加载");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址 / 命名空间:优先读字面环境变量 NACOS_ADDR / NACOS_NAMESPACE(与 UserBase bootstrap、
|
||||||
|
// run-cas.sh 同一套约定),再回退到 Spring Cloud 风格键,最后给默认值。
|
||||||
|
// 本地默认连 dev 命名空间(零配置),生产显式传 NACOS_NAMESPACE=prod。
|
||||||
|
String addr = firstNonNull(
|
||||||
|
environment.getProperty("NACOS_ADDR"),
|
||||||
|
environment.getProperty("spring.cloud.nacos.config.server-addr"),
|
||||||
|
environment.getProperty("nacos.config.addr"),
|
||||||
|
"nacos.yunhaixingtong.com:8848");
|
||||||
|
String namespace = firstNonNull(
|
||||||
|
environment.getProperty("NACOS_NAMESPACE"),
|
||||||
|
environment.getProperty("spring.cloud.nacos.config.namespace"),
|
||||||
|
environment.getProperty("nacos.config.namespace"),
|
||||||
|
"dev");
|
||||||
|
String username = firstNonNull(
|
||||||
|
environment.getProperty("NACOS_USER"),
|
||||||
|
environment.getProperty("NACOS_USERNAME"),
|
||||||
|
environment.getProperty("spring.cloud.nacos.config.username"),
|
||||||
|
environment.getProperty("nacos.config.username"),
|
||||||
|
"yh-reader");
|
||||||
|
String password = firstNonNull(
|
||||||
|
environment.getProperty("NACOS_PASS"),
|
||||||
|
environment.getProperty("NACOS_PASSWORD"),
|
||||||
|
environment.getProperty("spring.cloud.nacos.config.password"),
|
||||||
|
environment.getProperty("nacos.config.password"),
|
||||||
|
"123456");
|
||||||
|
|
||||||
|
String commonDataId = firstNonNull(environment.getProperty("nacos.config.common-data-id"), "common.yaml");
|
||||||
|
String commonGroup = firstNonNull(environment.getProperty("nacos.config.common-group"), "DEFAULT_GROUP");
|
||||||
|
String casDataId = firstNonNull(environment.getProperty("nacos.config.cas-data-id"), "cas.yaml");
|
||||||
|
String casGroup = firstNonNull(environment.getProperty("nacos.config.cas-group"), "CAS");
|
||||||
|
|
||||||
|
try {
|
||||||
|
String token = login(addr, username, password);
|
||||||
|
// 把命名空间名字(dev/prod)解析成 Nacos 的 namespaceId:
|
||||||
|
// 配置接口 tenant 参数只认 ID 不认名字,直接传 "prod" 会查不到配置。
|
||||||
|
String resolvedNs = resolveNamespace(addr, namespace, token);
|
||||||
|
|
||||||
|
Map<String, Object> common = fetchYaml(addr, commonDataId, commonGroup, resolvedNs, token);
|
||||||
|
Map<String, Object> cas = fetchYaml(addr, casDataId, casGroup, resolvedNs, token);
|
||||||
|
|
||||||
|
Map<String, Object> merged = buildCasProperties(common, cas);
|
||||||
|
if (merged.isEmpty()) {
|
||||||
|
log.warn("[nacos] 未从 Nacos 解析出任何有效配置,回退 application.yml");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapPropertySource source = new MapPropertySource(PROPERTY_SOURCE_NAME, merged);
|
||||||
|
MutablePropertySources sources = environment.getPropertySources();
|
||||||
|
// 插在 application.yml 之前,使其覆盖 application.yml,但低于系统属性/环境变量
|
||||||
|
String appName = findApplicationConfigName(sources);
|
||||||
|
if (appName != null) {
|
||||||
|
sources.addBefore(appName, source);
|
||||||
|
} else {
|
||||||
|
sources.addFirst(source);
|
||||||
|
}
|
||||||
|
log.info("[nacos] 已从 Nacos 注入 {} 个配置项 (namespace='{}' -> '{}', addr={})", merged.size(), namespace, resolvedNs, addr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[nacos] 加载 Nacos 配置失败,回退 application.yml:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 common.yaml + cas.yaml 构造最终生效的 CAS 属性。
|
||||||
|
* 关键点:common.yaml 是跨服务共享的「Spring Boot 标准」配置(spring.datasource.* / spring.redis.*),
|
||||||
|
* 而 CAS 的 JDBC 认证只认 cas.authn.jdbc.query[0].*,因此这里把 spring.datasource.* 翻译成
|
||||||
|
* cas.authn.jdbc.query[0].url,逻辑与 run-cas.sh 一致。
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildCasProperties(Map<String, Object> common, Map<String, Object> cas) {
|
||||||
|
Map<String, Object> out = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// 1) cas.yaml 透传(cas.* 自有配置先放入,后续 JDBC 连接键再覆盖)
|
||||||
|
if (cas != null) {
|
||||||
|
out.putAll(cas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 从 common.yaml 抽取数据源(兼容 spring.datasource.* 与 mysql.* 两种命名)
|
||||||
|
String dsHost = firstNonNull(str(common, "spring.datasource.host"), str(common, "mysql.host"));
|
||||||
|
String dsPort = firstNonNull(str(common, "spring.datasource.port"), str(common, "mysql.port"), "3306");
|
||||||
|
String dsUser = firstNonNull(str(common, "spring.datasource.username"), str(common, "mysql.user"));
|
||||||
|
String dsPass = firstNonNull(str(common, "spring.datasource.password"), str(common, "mysql.password"));
|
||||||
|
String dsParams = firstNonNull(
|
||||||
|
str(common, "spring.datasource.params"),
|
||||||
|
str(common, "mysql.params"),
|
||||||
|
"useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");
|
||||||
|
|
||||||
|
// 3) 库名来自 cas.yaml(cas.authn.jdbc.query[0].database),缺省 bsch_userbase
|
||||||
|
String dbName = firstNonNull(str(cas, "cas.authn.jdbc.query[0].database"), "bsch_userbase");
|
||||||
|
|
||||||
|
// 4) 拼 JDBC URL 并覆盖连接键(与 run-cas.sh 逻辑一致)
|
||||||
|
if (dsHost != null && !dsHost.isEmpty()) {
|
||||||
|
String jdbcUrl = "jdbc:mysql://" + dsHost + ":" + dsPort + "/" + dbName + "?" + dsParams;
|
||||||
|
out.put("cas.authn.jdbc.query[0].url", jdbcUrl);
|
||||||
|
if (dsUser != null) {
|
||||||
|
out.put("cas.authn.jdbc.query[0].user", dsUser);
|
||||||
|
}
|
||||||
|
if (dsPass != null) {
|
||||||
|
out.put("cas.authn.jdbc.query[0].password", dsPass);
|
||||||
|
}
|
||||||
|
out.put("cas.authn.jdbc.query[0].driverClass", "com.mysql.jdbc.Driver");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) 透传 common.yaml 里的 redis / rabbitmq 连接(自定义 /api/login 依赖 Redis 存验证码/失败计数)
|
||||||
|
if (common != null) {
|
||||||
|
for (Map.Entry<String, Object> e : common.entrySet()) {
|
||||||
|
String k = e.getKey();
|
||||||
|
if (k.startsWith("spring.redis.") || k.startsWith("spring.rabbitmq.")) {
|
||||||
|
out.put(k, e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findApplicationConfigName(MutablePropertySources sources) {
|
||||||
|
for (PropertySource<?> ps : sources) {
|
||||||
|
if (ps.getName() != null && ps.getName().contains("applicationConfig")) {
|
||||||
|
return ps.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把命名空间名字(dev/prod/public)解析成 Nacos 的 namespaceId。
|
||||||
|
* Nacos 配置接口 tenant 参数只认 namespaceId,不认命名空间显示名,因此必须转换。
|
||||||
|
* 若无法解析(API 不可达/无权限),原样返回输入,调用方把它当作 ID 兜底。
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private String resolveNamespace(String addr, String namespace, String token) {
|
||||||
|
if (namespace == null || namespace.isEmpty()) {
|
||||||
|
return namespace; // public 的 namespaceId 即空串
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String url = "http://" + addr + "/nacos/v1/console/namespaces";
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
url += "?accessToken=" + enc(token);
|
||||||
|
}
|
||||||
|
String body = httpGet(url);
|
||||||
|
if (body == null || body.trim().isEmpty()) {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
// Nacos 返回的是 JSON,而 JSON 是 YAML 子集,可直接用内置 SnakeYAML 解析(零额外依赖)
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
Object loaded = yaml.load(body);
|
||||||
|
if (!(loaded instanceof Map)) {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
Object data = ((Map<String, Object>) loaded).get("data");
|
||||||
|
if (!(data instanceof List)) {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
for (Object item : (List<Object>) data) {
|
||||||
|
if (!(item instanceof Map)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<String, Object> m = (Map<String, Object>) item;
|
||||||
|
String id = str(m, "namespace");
|
||||||
|
String showName = str(m, "namespaceShowName");
|
||||||
|
if (namespace.equals(showName) || namespace.equals(id)) {
|
||||||
|
log.info("[nacos] 命名空间 '{}' 解析为 namespaceId='{}'", namespace, id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[nacos] 解析命名空间失败,按原值 '{}' 作为 namespaceId 兜底:{}", namespace, e.getMessage());
|
||||||
|
}
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 拉取并解析单个 Nacos 配置(YAML),失败返回 null */
|
||||||
|
private Map<String, Object> fetchYaml(String addr, String dataId, String group, String namespace, String token) {
|
||||||
|
String content = getConfig(addr, dataId, group, namespace, token);
|
||||||
|
if (content == null || content.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
Object loaded = yaml.load(content);
|
||||||
|
Map<String, Object> flat = new LinkedHashMap<>();
|
||||||
|
flatten("", loaded, flat);
|
||||||
|
return flat;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[nacos] 解析 {} 失败:{}", dataId, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 递归展平为点号键(列表用 [i] 下标,与 CAS RelaxedBinding 兼容,如 cas.authn.jdbc.query[0].url) */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void flatten(String prefix, Object value, Map<String, Object> out) {
|
||||||
|
if (value instanceof Map) {
|
||||||
|
for (Map.Entry<String, Object> e : ((Map<String, Object>) value).entrySet()) {
|
||||||
|
String key = String.valueOf(e.getKey());
|
||||||
|
String np = prefix.isEmpty() ? key : prefix + "." + key;
|
||||||
|
flatten(np, e.getValue(), out);
|
||||||
|
}
|
||||||
|
} else if (value instanceof List) {
|
||||||
|
List<Object> list = (List<Object>) value;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
flatten(prefix + "[" + i + "]", list.get(i), out);
|
||||||
|
}
|
||||||
|
} else if (value != null) {
|
||||||
|
out.put(prefix, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HTTP GET 拉取 Nacos 配置;鉴权开启时带 token,失败自动不带 token 重试 */
|
||||||
|
private String getConfig(String addr, String dataId, String group, String namespace, String token) {
|
||||||
|
String base = "http://" + addr + "/nacos/v1/cs/configs";
|
||||||
|
String qs = "dataId=" + enc(dataId) + "&group=" + enc(group) + "&tenant=" + enc(namespace);
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
String body = httpGet(base + "?" + qs + "&accessToken=" + enc(token));
|
||||||
|
if (body != null && !body.trim().isEmpty() && !body.contains("\"error\"")) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return httpGet(base + "?" + qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 登录 Nacos 换取 accessToken;鉴权关闭时返回 null(调用方自动降级) */
|
||||||
|
private String login(String addr, String username, String password) {
|
||||||
|
try {
|
||||||
|
String url = "http://" + addr + "/nacos/v1/auth/login?username=" + enc(username) + "&password=" + enc(password);
|
||||||
|
String resp = httpPost(url, "");
|
||||||
|
if (resp == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int i = resp.indexOf("\"accessToken\"");
|
||||||
|
if (i >= 0) {
|
||||||
|
int s = resp.indexOf('"', i + 14);
|
||||||
|
int e = resp.indexOf('"', s + 1);
|
||||||
|
if (s >= 0 && e > s) {
|
||||||
|
return resp.substring(s + 1, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[nacos] 登录 Nacos 失败(可能是鉴权关闭):{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String httpGet(String urlStr) {
|
||||||
|
HttpURLConnection conn = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(urlStr);
|
||||||
|
conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setReadTimeout(10000);
|
||||||
|
if (conn.getResponseCode() == 200) {
|
||||||
|
return readBody(conn);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[nacos] GET {} 失败:{}", urlStr, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String httpPost(String urlStr, String body) {
|
||||||
|
HttpURLConnection conn = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(urlStr);
|
||||||
|
conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setConnectTimeout(10000);
|
||||||
|
conn.setReadTimeout(10000);
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
if (body != null && !body.isEmpty()) {
|
||||||
|
try (OutputStream os = conn.getOutputStream()) {
|
||||||
|
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (conn.getResponseCode() == 200) {
|
||||||
|
return readBody(conn);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[nacos] POST {} 失败:{}", urlStr, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readBody(HttpURLConnection conn) throws Exception {
|
||||||
|
try (BufferedReader br = new BufferedReader(
|
||||||
|
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String enc(String s) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(s == null ? "" : s, "UTF-8");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return s == null ? "" : s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonNull(String... vals) {
|
||||||
|
for (String v : vals) {
|
||||||
|
if (v != null && !v.isEmpty()) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String str(Map<String, Object> m, String key) {
|
||||||
|
if (m == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object v = m.get(key);
|
||||||
|
return v == null ? null : String.valueOf(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
// 在 ConfigFileApplicationListener(HIGHEST_PRECEDENCE+10,负责加载 application.yml)之后执行,
|
||||||
|
// 确保 application.yml 已加载,从而能把它作为插入锚点并覆盖它
|
||||||
|
return Ordered.LOWEST_PRECEDENCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ public class LoginController {
|
|||||||
@Qualifier("ticketGrantingTicketCookieGenerator")
|
@Qualifier("ticketGrantingTicketCookieGenerator")
|
||||||
private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator;
|
private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator;
|
||||||
|
|
||||||
@Value("${custom.login.default-redirect-url:http://sso.yunhaixingtong.com/front/index.html}")
|
@Value("${custom.login.default-redirect-url}")
|
||||||
private String defaultRedirectUrl;
|
private String defaultRedirectUrl;
|
||||||
|
|
||||||
// 是否强制每次登录都必须校验验证码。默认 false = 维持原逻辑(仅会话失败达到阈值后才要求);
|
// 是否强制每次登录都必须校验验证码。默认 false = 维持原逻辑(仅会话失败达到阈值后才要求);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# 注册自定义 Nacos 配置注入器:在 Spring Boot 环境准备阶段把 Nacos 配置注入 Environment
|
||||||
|
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||||
|
org.apereo.cas.web.custom.config.nacos.NacosEnvironmentPostProcessor
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/index_.html
|
|
||||||
/.idea
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
a{text-decoration:none;color:#337ab7}a:hover{text-decoration:underline}*{box-sizing:border-box;-webkit-tap-highlight-color:transparent;outline:none}html{font-size:14px}body{background-color:#eff2f6}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.05);border-radius:10px;-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,0.1)}*:hover::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.2);border-radius:10px;-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,0.1)}*::-webkit-scrollbar-thumb:hover{background-color:rgba(0,0,0,0.3);-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,0.1)}*::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0);background-color:white}*::-webkit-scrollbar-track:hover{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.4);background-color:rgba(0,0,0,0.01)}.scroll2::-webkit-scrollbar{width:10px;height:10px}.scroll2::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0);background-color:white}.scroll2::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.05);border-radius:10px;-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,0.1)}.scroll2:hover::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.2);border-radius:10px;-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,0.1)}.scroll2::-webkit-scrollbar-track:hover{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.4)}.scroll2::-webkit-scrollbar-thumb:hover{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.4)}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@media (max-width:768px){body{min-width:100%}html,body,#app{height:100%}.app-down,.banner-box{display:none}.footer-container{height:58px}header.header-wrapper{height:58px;padding:0 10px}.banner-wrapper{padding-top:48px;height:calc(100% - 105px)}.banner-box,.tabs,.app-list-wrapper,.wait-app-wrapper{display:none !important}.main-wrapper{width:100% !important;margin:0 auto !important}.login-wrapper{width:90% !important;margin:0 auto !important ;float:none !important}.login-header ul{padding:14px 0 !important}.login-header ul li{margin-right:10px !important}.login-form{margin:20px 15px !important}.login-form .form-item{margin-top:15px !important}.login-form .icon-box{width:40px !important;height:40px !important}.login-form p.input-box input{padding:4px 10px 4px 12px !important}.operate-box{margin:12px 0 !important}.operate-box a{font-size:12px !important}.btn-submit{height:40px !important;line-height:40px !important;font-size:14px !important;margin-bottom:10px !important}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;line-height:1.5}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html,body{width:100%;height:100%;margin:0;scroll-behavior:smooth;-webkit-overflow-scrolling:touch}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain)*/
|
|
||||||
.html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var,b, u, i, center,dl, dt, dd, ol, ul, li,fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td,article, aside, canvas, details, embed,figure, figcaption, footer, header, hgroup,menu, nav, output, ruby, section, summary,time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline;line-height: 1.5;}/* HTML5 display-role reset for older browsers */article, aside, details, figcaption, figure,footer, header, hgroup, menu, nav, section { display: block;}body { line-height: 1;}ol, ul { list-style: none;}blockquote, q { quotes: none;}blockquote:before, blockquote:after,q:before, q:after { content: ''; content: none;}table { border-collapse: collapse; border-spacing: 0;}html, body { width: 100%; height: 100%; overflow: auto; margin: 0; scroll-behavior: smooth; -webkit-overflow-scrolling: touch;}
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
/* @import "https://fonts.googleapis.com/css?family=Montserrat:400,700|Raleway:300,400"; */
|
|
||||||
/* colors */
|
|
||||||
/* tab setting */
|
|
||||||
/* breakpoints */
|
|
||||||
/* selectors relative to radio inputs */
|
|
||||||
html {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
/*background: #efefef;*/
|
|
||||||
color: #333;
|
|
||||||
/* font-family: "Raleway"; */
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
body h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: #428BFF;
|
|
||||||
font-weight: 300;
|
|
||||||
padding: 40px 0 20px 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
left: 50%;
|
|
||||||
-webkit-transform: translateX(-50%);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
position: relative;
|
|
||||||
background: white;
|
|
||||||
padding: 50px;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
margin-top: 50px;
|
|
||||||
width: 60%;
|
|
||||||
/*height: 250px;*/
|
|
||||||
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
|
|
||||||
border-radius: 5px;
|
|
||||||
min-width: 240px;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tabs .content section h2,
|
|
||||||
.tabs ul li label {
|
|
||||||
font-family: "Montserrat";
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #428BFF;
|
|
||||||
}
|
|
||||||
.tabs ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 0;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-orient: horizontal;
|
|
||||||
-webkit-box-direction: normal;
|
|
||||||
-webkit-flex-direction: row;
|
|
||||||
-ms-flex-direction: row;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
-webkit-box-pack: justify;
|
|
||||||
-webkit-justify-content: space-between;
|
|
||||||
-ms-flex-pack: justify;
|
|
||||||
justify-content: space-between;
|
|
||||||
-webkit-box-align: end;
|
|
||||||
-webkit-align-items: flex-end;
|
|
||||||
-ms-flex-align: end;
|
|
||||||
align-items: flex-end;
|
|
||||||
-webkit-flex-wrap: wrap;
|
|
||||||
-ms-flex-wrap: wrap;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.tabs ul li {
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-webkit-flex: 1;
|
|
||||||
-ms-flex: 1;
|
|
||||||
flex: 1;
|
|
||||||
width: 25%;
|
|
||||||
padding: 0 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.tabs ul li label {
|
|
||||||
-webkit-transition: all 0.3s ease-in-out;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
color: #929daf;
|
|
||||||
padding: 5px auto;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: block;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-transition: all 0.2s ease-in-out;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
white-space: nowrap;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.tabs ul li label br {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tabs ul li label svg {
|
|
||||||
fill: #929daf;
|
|
||||||
height: 1.2em;
|
|
||||||
vertical-align: bottom;
|
|
||||||
margin-right: 0.2em;
|
|
||||||
-webkit-transition: all 0.2s ease-in-out;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
.tabs ul li label:hover, .tabs ul li label:focus, .tabs ul li label:active {
|
|
||||||
outline: 0;
|
|
||||||
color: #bec5cf;
|
|
||||||
}
|
|
||||||
.tabs ul li label:hover svg, .tabs ul li label:focus svg, .tabs ul li label:active svg {
|
|
||||||
fill: #bec5cf;
|
|
||||||
}
|
|
||||||
.tabs .slider {
|
|
||||||
position: relative;
|
|
||||||
width: 25%;
|
|
||||||
-webkit-transition: all 0.33s cubic-bezier(0.38, 0.8, 0.32, 1.07);
|
|
||||||
transition: all 0.33s cubic-bezier(0.38, 0.8, 0.32, 1.07);
|
|
||||||
}
|
|
||||||
.tabs .slider .indicator {
|
|
||||||
position: relative;
|
|
||||||
width: 50px;
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
height: 4px;
|
|
||||||
background: #428BFF;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
.tabs .content {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
.tabs .content section {
|
|
||||||
display: none;
|
|
||||||
-webkit-animation-name: content;
|
|
||||||
animation-name: content;
|
|
||||||
-webkit-animation-direction: normal;
|
|
||||||
animation-direction: normal;
|
|
||||||
-webkit-animation-duration: 0.3s;
|
|
||||||
animation-duration: 0.3s;
|
|
||||||
-webkit-animation-timing-function: ease-in-out;
|
|
||||||
animation-timing-function: ease-in-out;
|
|
||||||
-webkit-animation-iteration-count: 1;
|
|
||||||
animation-iteration-count: 1;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.tabs .content section h2 {
|
|
||||||
color: #428BFF;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tabs .content section h2::after {
|
|
||||||
content: "";
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
width: 30px;
|
|
||||||
height: 3px;
|
|
||||||
background: #428BFF;
|
|
||||||
margin-top: 5px;
|
|
||||||
left: 1px;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(1):checked ~ ul > li:nth-child(1) > label {
|
|
||||||
cursor: default;
|
|
||||||
color: #428BFF;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(1):checked ~ ul > li:nth-child(1) > label svg {
|
|
||||||
fill: #428BFF;
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(1):checked ~ ul > li:nth-child(1) > label {
|
|
||||||
background: rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(1):checked ~ .slider {
|
|
||||||
-webkit-transform: translateX(0%);
|
|
||||||
transform: translateX(0%);
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(1):checked ~ .content > section:nth-child(1) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(2):checked ~ ul > li:nth-child(2) > label {
|
|
||||||
cursor: default;
|
|
||||||
color: #428BFF;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(2):checked ~ ul > li:nth-child(2) > label svg {
|
|
||||||
fill: #428BFF;
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(2):checked ~ ul > li:nth-child(2) > label {
|
|
||||||
background: rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(2):checked ~ .slider {
|
|
||||||
-webkit-transform: translateX(100%);
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(2):checked ~ .content > section:nth-child(2) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(3):checked ~ ul > li:nth-child(3) > label {
|
|
||||||
cursor: default;
|
|
||||||
color: #428BFF;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(3):checked ~ ul > li:nth-child(3) > label svg {
|
|
||||||
fill: #428BFF;
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(3):checked ~ ul > li:nth-child(3) > label {
|
|
||||||
background: rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(3):checked ~ .slider {
|
|
||||||
-webkit-transform: translateX(200%);
|
|
||||||
transform: translateX(200%);
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(3):checked ~ .content > section:nth-child(3) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(4):checked ~ ul > li:nth-child(4) > label {
|
|
||||||
cursor: default;
|
|
||||||
color: #428BFF;
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(4):checked ~ ul > li:nth-child(4) > label svg {
|
|
||||||
fill: #428BFF;
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(4):checked ~ ul > li:nth-child(4) > label {
|
|
||||||
background: rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(4):checked ~ .slider {
|
|
||||||
-webkit-transform: translateX(300%);
|
|
||||||
transform: translateX(300%);
|
|
||||||
}
|
|
||||||
.tabs input[name="tab-control"]:nth-of-type(4):checked ~ .content > section:nth-child(4) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
@-webkit-keyframes content {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transform: translateY(5%);
|
|
||||||
transform: translateY(5%);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-transform: translateY(0%);
|
|
||||||
transform: translateY(0%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes content {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transform: translateY(5%);
|
|
||||||
transform: translateY(5%);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
-webkit-transform: translateY(0%);
|
|
||||||
transform: translateY(0%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
.tabs ul li label {
|
|
||||||
white-space: initial;
|
|
||||||
}
|
|
||||||
.tabs ul li label br {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
.tabs ul li label svg {
|
|
||||||
height: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.tabs ul li label {
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.tabs ul li label span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tabs .slider {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tabs .content {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.tabs .content section h2 {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 880 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 38 KiB |
@@ -1,281 +0,0 @@
|
|||||||
<!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>
|
|
||||||
<script src="./libs/jquery.min.js?v=2.1"></script>
|
|
||||||
<script src="./libs/vue.min.js?v=2.1"></script>
|
|
||||||
<script src="js/jsencrypt.min.js?v=2.1"></script>
|
|
||||||
<script src="js/conf.js?v=2.2"></script>
|
|
||||||
<script src="js/bschlogin.js?v=2.1"></script>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
background: #f5f6f7;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box {
|
|
||||||
background: #fff;
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
width: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
color: #333;
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item input:focus {
|
|
||||||
border-color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.captcha-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.captcha-row input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.captcha-row img {
|
|
||||||
height: 42px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
background: #409eff;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit:hover {
|
|
||||||
background: #66b1ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit:disabled {
|
|
||||||
background: #a0cfff;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
color: #f56c6c;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 12px;
|
|
||||||
text-align: center;
|
|
||||||
min-height: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="login-box">
|
|
||||||
<h2>用户登录</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" onclick="refreshCaptcha()" title="点击刷新" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn-submit" type="button" id="loginBtn" onclick="doLogin()">登录</button>
|
|
||||||
<div class="warning" id="warningLabel"></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 记住密码最佳努力:真实表单提交到隐藏 iframe(不跳转页面);主流程用 AJAX 拿 data.url 手动跳转,互不干扰 -->
|
|
||||||
<iframe name="pwdMgrFrame" style="display:none"></iframe>
|
|
||||||
<form id="pwdMgrForm" method="post" action="/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>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.onerror = function (msg, src, line, col, err) {
|
|
||||||
var w = document.getElementById("warningLabel");
|
|
||||||
if (w) w.textContent = "页面脚本异常:" + msg;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var bschLogin;
|
|
||||||
try {
|
|
||||||
bschLogin = new BschLogin(ssoServerHost);
|
|
||||||
} catch (e) {
|
|
||||||
var w0 = document.getElementById("warningLabel");
|
|
||||||
if (w0) w0.textContent = "初始化失败:" + (e && e.message ? e.message : e);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = function () {
|
|
||||||
document.getElementById("service_hidden").value = bschLogin.getQueryVariable("service") || "";
|
|
||||||
bschLogin.getPublicKey();
|
|
||||||
bschLogin.isNeedCaptcha(function (isNeed) {
|
|
||||||
setCaptcha(isNeed);
|
|
||||||
});
|
|
||||||
var err = bschLogin.getQueryVariable("error");
|
|
||||||
if (err) setWarning(decodeURIComponent(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
function setCaptcha(isVisible) {
|
|
||||||
var panel = document.getElementById("captchaPanel");
|
|
||||||
if (isVisible) {
|
|
||||||
document.getElementById("captchaImg").src = bschLogin.captchaSrc();
|
|
||||||
panel.style.display = "block";
|
|
||||||
panel.dataset.captcha = "true";
|
|
||||||
} else {
|
|
||||||
panel.style.display = "none";
|
|
||||||
panel.dataset.captcha = "false";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshCaptcha() {
|
|
||||||
document.getElementById("captchaImg").src = bschLogin.captchaSrc();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWarning(msg) {
|
|
||||||
document.getElementById("warningLabel").textContent = msg || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLogin() {
|
|
||||||
var username = document.getElementById("user_input").value.trim();
|
|
||||||
var pwd = document.getElementById("password_input").value;
|
|
||||||
var captcha = document.getElementById("captcha_input").value;
|
|
||||||
var needCaptcha = document.getElementById("captchaPanel").dataset.captcha === "true";
|
|
||||||
|
|
||||||
if (!username) { setWarning("请输入用户名!"); return; }
|
|
||||||
if (!pwd) { setWarning("请输入密码!"); return; }
|
|
||||||
if (needCaptcha && !captcha) { setWarning("请输入验证码!"); return; }
|
|
||||||
if (!bschLogin.publicKey) { setWarning("公钥加载中,请稍候重试"); return; }
|
|
||||||
|
|
||||||
var btn = document.getElementById("loginBtn");
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = "登录中...";
|
|
||||||
|
|
||||||
try {
|
|
||||||
var encrypt = new JSEncrypt();
|
|
||||||
encrypt.setPublicKey(bschLogin.publicKey);
|
|
||||||
var encUser = encrypt.encrypt(username);
|
|
||||||
var encPwd = encrypt.encrypt(pwd);
|
|
||||||
document.getElementById("loginname_input").value = encUser;
|
|
||||||
document.getElementById("pwd_input").value = encPwd;
|
|
||||||
var params = "loginname=" + encodeURIComponent(encUser)
|
|
||||||
+ "&pwd=" + encodeURIComponent(encPwd)
|
|
||||||
+ "&captcha=" + encodeURIComponent(captcha)
|
|
||||||
+ "&service=" + encodeURIComponent(document.getElementById("service_hidden").value);
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("POST", "/api/login", true);
|
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
||||||
xhr.onreadystatechange = function () {
|
|
||||||
if (xhr.readyState !== 4) return;
|
|
||||||
btn.disabled = false; btn.textContent = "登录";
|
|
||||||
if (xhr.status !== 200) { setWarning("登录请求失败"); return; }
|
|
||||||
try {
|
|
||||||
var resp = JSON.parse(xhr.responseText);
|
|
||||||
if (resp.code === 0 && resp.data && resp.data.url) {
|
|
||||||
setWarning("登录成功,正在跳转…");
|
|
||||||
// 最佳努力触发浏览器记住密码(真实表单提交到隐藏 iframe),再 JS 手动跳到 data.url
|
|
||||||
triggerPasswordSave(username, pwd, encUser, encPwd, captcha);
|
|
||||||
setTimeout(function () { window.location.href = resp.data.url; }, 400);
|
|
||||||
} else {
|
|
||||||
setWarning(resp.msg || "登录失败");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setWarning("登录响应解析失败");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send(params);
|
|
||||||
} catch (e) {
|
|
||||||
btn.disabled = false; btn.textContent = "登录";
|
|
||||||
setWarning("登录初始化失败,请重试");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerPasswordSave(username, pwd, encUser, encPwd, captcha) {
|
|
||||||
try {
|
|
||||||
document.getElementById("pm_user").value = username;
|
|
||||||
document.getElementById("pm_pwd").value = pwd;
|
|
||||||
document.getElementById("pm_loginname").value = encUser;
|
|
||||||
document.getElementById("pm_pwd_enc").value = encPwd;
|
|
||||||
document.getElementById("pm_captcha").value = captcha;
|
|
||||||
document.getElementById("pm_service").value = document.getElementById("service_hidden").value;
|
|
||||||
document.getElementById("pwdMgrForm").submit();
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("user_input").addEventListener("keydown", function (e) {
|
|
||||||
if (e.key === "Enter") document.getElementById("password_input").focus();
|
|
||||||
});
|
|
||||||
document.getElementById("password_input").addEventListener("keydown", function (e) {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
if (document.getElementById("captchaPanel").style.display !== "none") {
|
|
||||||
document.getElementById("captcha_input").focus();
|
|
||||||
} else {
|
|
||||||
doLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.getElementById("captcha_input").addEventListener("keydown", function (e) {
|
|
||||||
if (e.key === "Enter") doLogin();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,746 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="Expires" content="0" />
|
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
|
||||||
<meta http-equiv="Cache-control" content="no-cache" />
|
|
||||||
<meta http-equiv="Cache" content="no-cache" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"
|
|
||||||
/>
|
|
||||||
<title>登录</title>
|
|
||||||
<link rel="stylesheet" href="./libs/swiper.min.css" />
|
|
||||||
<link rel="stylesheet" href="./css/index.css" />
|
|
||||||
<link rel="stylesheet" href="./css//mobile.css" />
|
|
||||||
<script src="./libs/swiper.min.js"></script>
|
|
||||||
<script src="./libs/jquery.min.js"></script>
|
|
||||||
<script src="./libs/vue.min.js"></script>
|
|
||||||
<script src="./libs/polyfill.object-fit.js"></script>
|
|
||||||
<script src="js/jquery.qrcode.js"></script>
|
|
||||||
<script src="js/qrcode.js"></script>
|
|
||||||
<script src="js/jsencrypt.min.js"></script>
|
|
||||||
<script src="js/conf.js"></script>
|
|
||||||
<script src="js/bschlogin.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var bschLogin = new BschLogin(loginPageTag, ssoServerHost, loginServerHost, baseServerHost);
|
|
||||||
|
|
||||||
function onNameKeyup(event) {
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
if (!$(event.target).val()) {
|
|
||||||
setWarning("请输入用户名!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var v = $("#password_input").val();
|
|
||||||
$("#password_input").val("").focus().val(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPwdKeyup(event) {
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
if (!$(event.target).val()) {
|
|
||||||
setWarning("请输入密码!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log($("#captchaPanel").data("captcha"));
|
|
||||||
if (!!$("#captchaPanel").data("captcha")) {
|
|
||||||
$("#captcha_input").val("").focus();
|
|
||||||
} else {
|
|
||||||
doLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCaptchaKeyup(event) {
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
if (!$(event.target).val()) {
|
|
||||||
setWarning("请输入验证码!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function needCaptcha() {
|
|
||||||
bschLogin.isNeedCaptcha(function (isNeed) {
|
|
||||||
setCaptcha(isNeed);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCaptcha(isVisible) {
|
|
||||||
if (isVisible) {
|
|
||||||
$("#captchaImg").attr("src", bschLogin.captchaSrc());
|
|
||||||
$("#captchaPanel").css("display", "block").data("captcha", true);
|
|
||||||
// $(".login-wrapper").height(400);
|
|
||||||
} else {
|
|
||||||
$("#captchaPanel").css("display", "none").data("captcha", false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLogin() {
|
|
||||||
var username = $("#user_input").val();
|
|
||||||
var pwd = $("#password_input").val();
|
|
||||||
var captcha = $("#captcha_input").val();
|
|
||||||
if (!username) {
|
|
||||||
setWarning("请输入用户名!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!pwd) {
|
|
||||||
setWarning("请输入密码!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!!$("#captchaPanel").data("captcha") && !captcha) {
|
|
||||||
setWarning("请输入验证码!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bschLogin.login(
|
|
||||||
username,
|
|
||||||
pwd,
|
|
||||||
captcha,
|
|
||||||
function (url) {
|
|
||||||
window.location.href = url;
|
|
||||||
},
|
|
||||||
function (msg, needCaptcha) {
|
|
||||||
setWarning(msg);
|
|
||||||
setCaptcha(needCaptcha);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLoginQRCode() {
|
|
||||||
setQRCodeHint("请扫描二维码确认登录");
|
|
||||||
bschLogin.loginQrcode(
|
|
||||||
"#qrcode",
|
|
||||||
function (msg) {
|
|
||||||
setQRCodeHint("获取登录二维码失败");
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
setQRCodeHint("二维码已失效,点击刷新");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setQRCodeHint(hint) {
|
|
||||||
$("#qrcodeHint").text(hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWarning(msg) {
|
|
||||||
if (typeof msg == "undefined" || msg == "") $("#warningPanel").css("display", "none");
|
|
||||||
else $("#warningPanel").css("display", "block");
|
|
||||||
|
|
||||||
$("#warningLabel").text(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCountDown() {
|
|
||||||
var second = 60,
|
|
||||||
timer = null;
|
|
||||||
$("#js_vercode_btn")
|
|
||||||
.attr("disabled", true)
|
|
||||||
.text(second + "s");
|
|
||||||
timer = setInterval(function () {
|
|
||||||
second--;
|
|
||||||
if (second > 0) {
|
|
||||||
$("#js_vercode_btn").text(second + "s");
|
|
||||||
} else {
|
|
||||||
clearInterval(timer);
|
|
||||||
$("#js_vercode_btn").removeAttr("disabled").text("获取验证码");
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app" v-cloak>
|
|
||||||
<header class="header-wrapper">
|
|
||||||
<div class="main-wrapper fix">
|
|
||||||
<div class="logo-box align-middle fl" :style="{ height: logoHeight+'px' }">
|
|
||||||
<img v-if="settingMap.logoUrl" :src="settingMap.logoUrl" />
|
|
||||||
</div>
|
|
||||||
<div class="app-down align-middle fr">
|
|
||||||
<a href="javascript:void(0)" id="js_app_down">{{downTitle}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="down-app-box">
|
|
||||||
<div class="tips-box"></div>
|
|
||||||
<img src="image/wem3.png" />
|
|
||||||
<h3>{{downInfo}}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section class="banner-wrapper">
|
|
||||||
<div class="main-wrapper">
|
|
||||||
<div class="login-wrapper align-middle fr">
|
|
||||||
<div class="login-header">
|
|
||||||
<ul class="fix" id="js_login_tab">
|
|
||||||
<li class="fl active">密码登录</li>
|
|
||||||
<li class="fl">扫码登录</li>
|
|
||||||
<!-- <li class="fl">
|
|
||||||
<a
|
|
||||||
style="color: #656667; text-decoration: none"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href="https://platform.xhedu.sh.cn/platform/app/login.php?url=https%3A%2F%2Fplatform-xhzx.xhedu.sh.cn&from=node3&ep_appid=100100289"
|
|
||||||
>
|
|
||||||
区统一登录</a
|
|
||||||
>
|
|
||||||
</li> -->
|
|
||||||
</ul>
|
|
||||||
<div class="header-slider active-1">
|
|
||||||
<div class="indicator"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="login-body js_pwd_login_box">
|
|
||||||
<form class="login-form" method="post" onsubmit="return doLogin()">
|
|
||||||
<div class="form-item">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="user_input"
|
|
||||||
placeholder="用户名/手机号/身份证号"
|
|
||||||
onkeyup="onNameKeyup(event)"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-item">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="password_input"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
onkeyup="onPwdKeyup(event)"
|
|
||||||
/>
|
|
||||||
<span class="pwd-icon-box js_pwd_switch_btn">
|
|
||||||
<svg
|
|
||||||
id="js_open_eye"
|
|
||||||
style="display: none"
|
|
||||||
t="1619748143692"
|
|
||||||
class="icon"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="3873"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M938.666667 501.333333c0-2.133333 0-2.133333-2.133334-4.266666-91.733333-142.933333-256-232.533333-426.666666-232.533334-177.066667 0-337.066667 89.6-428.8 243.2-2.133333 2.133333-2.133333 4.266667-2.133334 6.4v8.533334c0 2.133333 0 4.266667 2.133334 6.4C174.933333 680.533333 334.933333 770.133333 512 770.133333c66.133333 0 66.133333 0 145.066667-12.8 10.666667-2.133333 19.2-12.8 17.066666-23.466666-2.133333-10.666667-12.8-19.2-23.466666-17.066667-74.666667 12.8-74.666667 12.8-136.533334 12.8-157.866667 0-300.8-78.933333-386.133333-211.2 85.333333-132.266667 228.266667-211.2 386.133333-211.2 153.6 0 302.933333 81.066667 386.133334 209.066667-23.466667 38.4-32 46.933333-70.4 87.466666-8.533333 8.533333-8.533333 21.333333 0 29.866667 4.266667 4.266667 10.666667 6.4 14.933333 6.4 6.4 0 10.666667-2.133333 14.933333-6.4 44.8-44.8 51.2-53.333333 83.2-106.666667 4.266667-8.533333 2.133333-19.2-4.266666-25.6z"
|
|
||||||
fill="#B4B6B9"
|
|
||||||
p-id="3874"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M377.6 514.133333c0 74.666667 61.866667 136.533333 136.533333 136.533334s136.533333-61.866667 136.533334-136.533334-61.866667-136.533333-136.533334-136.533333-136.533333 61.866667-136.533333 136.533333z m232.533333 0c0 51.2-42.666667 93.866667-93.866666 93.866667-51.2 0-93.866667-42.666667-93.866667-93.866667s42.666667-93.866667 93.866667-93.866666c51.2 0 93.866667 42.666667 93.866666 93.866666z"
|
|
||||||
fill="#B4B6B9"
|
|
||||||
p-id="3875"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M757.333333 686.933333m-32 0a32 32 0 1 0 64 0 32 32 0 1 0-64 0Z"
|
|
||||||
fill="#B4B6B9"
|
|
||||||
p-id="3876"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
id="js_close_eye"
|
|
||||||
t="1619764005511"
|
|
||||||
class="icon"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="2031"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M938.666667 317.866667m-29.866667 0a29.866667 29.866667 0 1 0 59.733333 0 29.866667 29.866667 0 1 0-59.733333 0Z"
|
|
||||||
fill="#B4B6B9"
|
|
||||||
p-id="2032"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M876.8 454.4c6.4-6.4 12.8-14.933333 19.2-21.333333 8.533333-8.533333 6.4-23.466667-2.133333-29.866667-8.533333-8.533333-23.466667-6.4-29.866667 2.133333-85.333333 100.266667-209.066667 157.866667-341.333333 157.866667-166.4 0-317.866667-91.733333-394.666667-238.933333-6.4-10.666667-19.2-14.933333-27.733333-8.533334-10.666667 6.4-14.933333 19.2-8.533334 27.733334 14.933333 27.733333 32 53.333333 49.066667 76.8L64 499.2c-8.533333 8.533333-8.533333 23.466667 0 32 4.266667 4.266667 8.533333 6.4 14.933333 6.4 6.4 0 12.8-2.133333 17.066667-8.533333l72.533333-76.8c36.266667 40.533333 81.066667 72.533333 128 98.133333l-55.466666 102.4c-6.4 10.666667-2.133333 25.6 8.533333 32 2.133333 2.133333 6.4 2.133333 8.533333 2.133333 8.533333 0 17.066667-4.266667 21.333334-12.8l55.466666-104.533333c51.2 21.333333 106.666667 34.133333 162.133334 36.266667v106.666666c0 12.8 10.666667 23.466667 21.333333 23.466667 12.8 0 21.333333-10.666667 21.333333-23.466667v-106.666666c53.333333-2.133333 104.533333-12.8 151.466667-29.866667l55.466667 102.4c4.266667 8.533333 12.8 12.8 21.333333 12.8 2.133333 0 6.4 0 8.533333-2.133333 10.666667-6.4 12.8-19.2 8.533334-32l-53.333334-98.133334c40.533333-19.2 78.933333-44.8 113.066667-74.666666l74.666667 78.933333c4.266667 4.266667 10.666667 8.533333 17.066666 8.533333 4.266667 0 10.666667-2.133333 14.933334-6.4 8.533333-8.533333 8.533333-23.466667 0-32l-74.666667-78.933333z"
|
|
||||||
fill="#B4B6B9"
|
|
||||||
p-id="2033"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="hide" id="captchaPanel" data-captcha="false">
|
|
||||||
<div class="form-item" style="width: 65%; display: inline-block">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="captcha_input"
|
|
||||||
placeholder="输入右侧验证码"
|
|
||||||
onkeyup="onCaptchaKeyup(event)"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img
|
|
||||||
id="captchaImg"
|
|
||||||
style="width: 70px; height: 30px; float: right; margin-top: 20px"
|
|
||||||
onClick="setCaptcha(true)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="operate-box fix">
|
|
||||||
<!-- <label>
|
|
||||||
<input type="checkbox" class="login-checkbox" />记住密码
|
|
||||||
</label> -->
|
|
||||||
<a href="javascript:;" id="js_forget_pw">忘记密码?</a>
|
|
||||||
</div>
|
|
||||||
<button class="btn-submit" type="submit">登录</button>
|
|
||||||
|
|
||||||
<div id="warningPanel" class="warning-box hide">
|
|
||||||
<span id="warningLabel"></span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="login-body js_qr_login_box hide">
|
|
||||||
<div class="qrcode-box">
|
|
||||||
<div id="qrcode" style="margin-bottom: 10px" onclick="getLoginQRCode()"></div>
|
|
||||||
<p id="qrcodeHint">扫码登录</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="forgetpw-wrapper align-middle fr hide">
|
|
||||||
<div class="login-header">
|
|
||||||
<ul class="fix" id="js_login_tab">
|
|
||||||
<li class="fl">忘记密码</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="login-body js_forgetpw_box">
|
|
||||||
<p class="tips-info-box">密码规则:长度8-18位且包括数字、字母和符号!</p>
|
|
||||||
<div class="login-form">
|
|
||||||
<div class="form-item">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
maxlength="11"
|
|
||||||
id="tel"
|
|
||||||
name="tel"
|
|
||||||
v-model="reset.phone"
|
|
||||||
placeholder="请输入手机号"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-item">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
maxlength="18"
|
|
||||||
v-model="reset.password"
|
|
||||||
placeholder="请输入新密码"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-item">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
maxlength="18"
|
|
||||||
v-model="reset.passworded"
|
|
||||||
placeholder="请再次确认新密码"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="vercode-wrap fix">
|
|
||||||
<div class="form-item fl">
|
|
||||||
<p class="input-box">
|
|
||||||
<input
|
|
||||||
v-model="reset.verifyCode"
|
|
||||||
type="text"
|
|
||||||
maxlength="6"
|
|
||||||
placeholder="输入验证码"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button id="js_vercode_btn" @click="sendVerifyCode" class="btn-vercode fr">
|
|
||||||
获取验证码
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn-submit" style="margin-top: 20px" @click="doForgetPw" type="submit">
|
|
||||||
下一步
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="!!warningMsg" class="warning-box">
|
|
||||||
<span>{{warningMsg}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="forgetpw-stats-box js_forgetpw_stats_box hide">
|
|
||||||
<img class="tc" src="./image/success-stats.png" alt="" />
|
|
||||||
<h4>密码修改成功</h4>
|
|
||||||
<p>恭喜您密码修改成功,请返回首页重新登录</p>
|
|
||||||
<a href="./index.html">
|
|
||||||
<button class="btn-submit">返回登录</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="banner-box">
|
|
||||||
<img v-if="settingMap.banner.length==1" :src="settingMap.banner[0]" class="js_fit" />
|
|
||||||
<div v-else-if="settingMap.banner.length>1" class="swiper-container">
|
|
||||||
<div class="swiper-wrapper">
|
|
||||||
<div class="swiper-slide" v-for="item in settingMap.banner">
|
|
||||||
<img :src="item" class="js_fit" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 如果需要分页器 -->
|
|
||||||
<div class="swiper-pagination"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div id="js_tabs" class="main-wrapper tabs" v-if="settingMap.isShowApp">
|
|
||||||
<ul class="tabs-nav fix">
|
|
||||||
<template v-for="(item,index) in tabs">
|
|
||||||
<li :class="[index==0?'active':'','apptype-li']">{{item}}</li>
|
|
||||||
<li v-if="index!=tabs.length-1" class="spacer-li">
|
|
||||||
<span>/</span>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
<div class="tabs-slider" v-if="tabs.length>0">
|
|
||||||
<div class="indicator"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<section id="app_panel" class="app-list-wrapper" v-if="settingMap.isShowApp">
|
|
||||||
<ul class="app-ul fix">
|
|
||||||
<li class="" v-for="app in appList">
|
|
||||||
<a href="#">
|
|
||||||
<div class="icon-box">
|
|
||||||
<img v-bind:src="app.icon" class="" data-load="1" />
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<h4 class="info">{{app.name}}</h4>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer :class="{'footer-container':true,'theme-light':!settingMap.isShowApp}">
|
|
||||||
<!-- <p class="align-middle">
|
|
||||||
©2018bestsch.com inc All Rights reserved 备案号:沪ICP备13000443号-1
|
|
||||||
</p> -->
|
|
||||||
<p class="align-middle">
|
|
||||||
{{beianText}}
|
|
||||||
<a target="_blank" href="https://beian.miit.gov.cn/">{{beianLink}}</a>
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
needCaptcha();
|
|
||||||
getLoginQRCode();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var vm = new Vue({
|
|
||||||
el: "#app",
|
|
||||||
data: {
|
|
||||||
appTypeList: [],
|
|
||||||
appList: [],
|
|
||||||
tabs: [],
|
|
||||||
appName: "",
|
|
||||||
downTitle: "",
|
|
||||||
downInfo: "",
|
|
||||||
|
|
||||||
settingMap: {
|
|
||||||
logoUrl: "",
|
|
||||||
isShowApp: true,
|
|
||||||
banner: [],
|
|
||||||
},
|
|
||||||
beianText: window.beianText,
|
|
||||||
beianLink: window.beianLink,
|
|
||||||
logoHeight: window.logoHeight || 35,
|
|
||||||
|
|
||||||
warningMsg: null,
|
|
||||||
reset: {
|
|
||||||
phone: null,
|
|
||||||
verifyCode: null,
|
|
||||||
password: null,
|
|
||||||
passworded: null,
|
|
||||||
},
|
|
||||||
windowWidth: window.innerWidth,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isMobile() {
|
|
||||||
return this.windowWidth < 768;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted: function () {
|
|
||||||
var _this = this;
|
|
||||||
_this.appName = window.appName;
|
|
||||||
_this.downTitle = window.downTitle;
|
|
||||||
_this.downInfo = window.downInfo;
|
|
||||||
_this.getSetting();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getApp: function () {
|
|
||||||
var _this = this;
|
|
||||||
var url = loginServerHost + "api/v2/getSchoolApp";
|
|
||||||
$.get(
|
|
||||||
url,
|
|
||||||
{
|
|
||||||
schId: schId,
|
|
||||||
},
|
|
||||||
function (resp) {
|
|
||||||
if (resp.code == 0) {
|
|
||||||
_this.appTypeList = resp.data;
|
|
||||||
_this.showAppType();
|
|
||||||
} else console.log("获取应用列表失败:" + resp.msg);
|
|
||||||
},
|
|
||||||
"json"
|
|
||||||
).error(function () {
|
|
||||||
console.log("获取应用列表失败");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getSetting: function () {
|
|
||||||
var _this = this;
|
|
||||||
var setSettingMap = function (data) {
|
|
||||||
_this.settingMap = data;
|
|
||||||
var bannerHeight = data.isShowApp ? 480 : $("body").height() - 186;
|
|
||||||
if (data.isShowApp) _this.getApp();
|
|
||||||
|
|
||||||
_this.$nextTick(function () {
|
|
||||||
objectFitImages("img.js_fit"); //IE图片拉伸
|
|
||||||
if (!_this.isMobile) {
|
|
||||||
$(".banner-wrapper,.swiper-container,.js_fit").height(bannerHeight);
|
|
||||||
if (data.banner.length > 1) {
|
|
||||||
var mySwiper = new Swiper(".swiper-container", {
|
|
||||||
pagination: ".swiper-pagination",
|
|
||||||
loop: true,
|
|
||||||
paginationClickable: true,
|
|
||||||
autoplay: 2800,
|
|
||||||
speed: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$(".banner-wrapper .main-wrapper").css({
|
|
||||||
"background-image": "url(" + data.banner[0] + ")",
|
|
||||||
"background-size": "cover",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// setSettingMap(window.defaultSetting);
|
|
||||||
|
|
||||||
var url = loginServerHost + "api/v2/getSchoolLoginPageConfig";
|
|
||||||
$.get(
|
|
||||||
url,
|
|
||||||
{
|
|
||||||
schId: schId,
|
|
||||||
},
|
|
||||||
function (resp) {
|
|
||||||
if (resp.code == 0) {
|
|
||||||
var rData = resp.data;
|
|
||||||
rData.banner = rData.bannerUrls;
|
|
||||||
setSettingMap(resp.data);
|
|
||||||
} else setSettingMap(window.defaultSetting);
|
|
||||||
},
|
|
||||||
"json"
|
|
||||||
).error(function () {
|
|
||||||
setSettingMap(window.defaultSetting);
|
|
||||||
console.log("获取设置失败");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
showAppType: function () {
|
|
||||||
var _this = this;
|
|
||||||
|
|
||||||
_this.tabs.push("全部应用");
|
|
||||||
var len = _this.appTypeList.length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
var appType = _this.appTypeList[i];
|
|
||||||
_this.tabs.push(appType.name);
|
|
||||||
}
|
|
||||||
_this.switchApp(0);
|
|
||||||
},
|
|
||||||
|
|
||||||
switchApp: function (index) {
|
|
||||||
var _this = this;
|
|
||||||
if (index == 0) {
|
|
||||||
var appList = [];
|
|
||||||
var len = _this.appTypeList.length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
var appType = _this.appTypeList[i];
|
|
||||||
var appCount = appType.app.length;
|
|
||||||
for (var j = 0; j < appCount; j++) appList.push(appType.app[j]);
|
|
||||||
}
|
|
||||||
_this.appList = appList;
|
|
||||||
} else {
|
|
||||||
var appType = _this.appTypeList[index - 1];
|
|
||||||
_this.appList = appType.app;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
sendVerifyCode: function () {
|
|
||||||
var _this = this;
|
|
||||||
var telreg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
|
|
||||||
if (!telreg.test(_this.reset.phone)) {
|
|
||||||
this.warningMsg = "请输入正确的手机号!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$("#js_vercode_btn").attr("disabled", true);
|
|
||||||
bschLogin.requestSmsCode(
|
|
||||||
_this.reset.phone,
|
|
||||||
function () {
|
|
||||||
onCountDown();
|
|
||||||
},
|
|
||||||
function (msg) {
|
|
||||||
alert("验证码获取失败!");
|
|
||||||
// alert("验证码获取失败:" + msg);
|
|
||||||
$("#js_vercode_btn").removeAttr("disabled").text("获取验证码");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
doForgetPw: function () {
|
|
||||||
var _this = this;
|
|
||||||
if (!_this.reset.phone) {
|
|
||||||
this.warningMsg = "请输入手机号!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var telreg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
|
|
||||||
if (!telreg.test(_this.reset.phone)) {
|
|
||||||
this.warningMsg = "请输入正确的手机号!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!_this.reset.password) {
|
|
||||||
this.warningMsg = "请输入新密码!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!_this.reset.passworded) {
|
|
||||||
this.warningMsg = "请再次确认新密码!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_this.reset.password !== _this.reset.passworded) {
|
|
||||||
this.warningMsg = "两次输入的密码不一致!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var pwreg = /^(?=.*[a-z])(?=.*[0-9])(?=.*).{8,}$/;
|
|
||||||
if (!pwreg.test(_this.reset.password)) {
|
|
||||||
this.warningMsg = "密码长度应大于8位且应包括数字、字母和符号!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!_this.reset.verifyCode) {
|
|
||||||
this.warningMsg = "请输入收到的验证码!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = JSON.parse(JSON.stringify(_this.reset));
|
|
||||||
delete item.passworded;
|
|
||||||
bschLogin.updatePwd(
|
|
||||||
item.phone,
|
|
||||||
item.verifyCode,
|
|
||||||
item.password,
|
|
||||||
function () {
|
|
||||||
$(".js_forgetpw_box").addClass("hide");
|
|
||||||
$(".js_forgetpw_stats_box").removeClass("hide");
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
// alert(error);
|
|
||||||
_this.warningMsg = error;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// $.post(
|
|
||||||
// "url",
|
|
||||||
// item,
|
|
||||||
// function (resp) {
|
|
||||||
// $(".js_forgetpw_box").addClass("hide");
|
|
||||||
// $(".js_forgetpw_stats_box").removeClass("hide");
|
|
||||||
// },
|
|
||||||
// function (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// alert("验证码获取失败!");
|
|
||||||
// $("#js_vercode_btn").removeAttr("disabled").text("获取验证码");
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$("#js_login_tab").on("click", "li", function (a, b) {
|
|
||||||
$("#js_login_tab .active").removeClass("active");
|
|
||||||
$(this).addClass("active");
|
|
||||||
var index = parseInt($(this).index()) + 1;
|
|
||||||
if (index == 3) return;
|
|
||||||
$(".login-wrapper .login-body").hide();
|
|
||||||
if (index == 1) {
|
|
||||||
$(".js_pwd_login_box").fadeToggle();
|
|
||||||
} else if (index == 2) {
|
|
||||||
$(".js_qr_login_box").fadeToggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
var slider = $("#js_login_tab + .header-slider");
|
|
||||||
slider.attr({
|
|
||||||
class: "",
|
|
||||||
});
|
|
||||||
slider.addClass("header-slider active-" + index);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#js_tabs").on("click", "li.apptype-li", function (ele) {
|
|
||||||
var index = $(this).index(),
|
|
||||||
sliderWidth = 0,
|
|
||||||
currWidth = 0;
|
|
||||||
|
|
||||||
$("#js_tabs .active").removeClass("active");
|
|
||||||
|
|
||||||
$("#js_tabs .tabs-nav")
|
|
||||||
.children()
|
|
||||||
.each(function (i, dom) {
|
|
||||||
if (i < index) {
|
|
||||||
sliderWidth += $(dom).width();
|
|
||||||
}
|
|
||||||
if (i == index) {
|
|
||||||
$(dom).addClass("active");
|
|
||||||
currWidth = $(dom).width();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#js_tabs .tabs-slider").css({
|
|
||||||
transform: "translateX(" + sliderWidth + "px)",
|
|
||||||
width: currWidth,
|
|
||||||
});
|
|
||||||
|
|
||||||
vm.switchApp(index / 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".js_pwd_switch_btn").click(function () {
|
|
||||||
var pwd = $("#password_input");
|
|
||||||
if (pwd.attr("type") === "password") {
|
|
||||||
$("#password_input").attr("type", "text");
|
|
||||||
} else {
|
|
||||||
$("#password_input").attr("type", "password");
|
|
||||||
}
|
|
||||||
$("#js_close_eye,#js_open_eye").toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#js_app_down").click(function (e) {
|
|
||||||
$(".down-app-box").fadeIn(300);
|
|
||||||
e.stopPropagation();
|
|
||||||
document.onclick = function () {
|
|
||||||
$(".down-app-box").fadeOut(300);
|
|
||||||
document.onclick = null;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//setWarning
|
|
||||||
$("#user_input,#password_input").change(function () {
|
|
||||||
setWarning("");
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".js_forgetpw_box input").change(function () {
|
|
||||||
vm.warningMsg = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#js_forget_pw").click(function () {
|
|
||||||
$(".login-wrapper").addClass("hide");
|
|
||||||
$(".forgetpw-wrapper").removeClass("hide");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
function BschLogin(ssoServerHost) {
|
|
||||||
this.isDebug = false;
|
|
||||||
this.ssoServerHost = ssoServerHost;
|
|
||||||
this.captchaUrl = ssoServerHost + "api/captcha";
|
|
||||||
this.loginUrl = ssoServerHost + "api/login";
|
|
||||||
this.publicKeyUrl = ssoServerHost + "api/public-key";
|
|
||||||
this.needCaptchaUrl = ssoServerHost + "api/need-captcha";
|
|
||||||
this.publicKey = "";
|
|
||||||
// this.setCookie("loginPageTag", loginPageTag, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
BschLogin.prototype = {
|
|
||||||
getPublicKey: function () {
|
|
||||||
var _this = this;
|
|
||||||
if (_this.isDebug) {
|
|
||||||
_this.publicKey =
|
|
||||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqytbHgQWZQmkwmp6WncVaUmUNgExYDB0Sp8h1qfyKfufbX2MEM6UzSPQHIwurFqkzcHljdrGDCXWZ2Xv8c8C27+MOPLf4VyYQ7invqtvmEwS6c3km4QqQVdrh0DJjVYhgO8reE8LhlDFc9Vh9FwySb8pbC1EM0s2lN6ig7/9NR8+CqxW61ku+7VYnLzcRgpc0cF3K/mQwAjVf1pKjGtPG1mpLNcHpT2yp5u950GfFtdqYgAte7k4CDlwMDKfpf+2Pg5kZAzogO5DTq/QolOWqjZfgT2wBNpYVT+3glbhhRbc9T3/SqFeT9KlXcamBXLvKI6WuKxlAEeKTiosUgMKCQIDAQAB";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.get(
|
|
||||||
_this.publicKeyUrl,
|
|
||||||
function (resp) {
|
|
||||||
_this.publicKey = resp.data.publicKey;
|
|
||||||
},
|
|
||||||
"json",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
//是否需要验证校验码
|
|
||||||
isNeedCaptcha: function (callback) {
|
|
||||||
if (this.isDebug) return callback(false);
|
|
||||||
$.get(
|
|
||||||
this.needCaptchaUrl,
|
|
||||||
function (resp) {
|
|
||||||
if (resp.code == 0) {
|
|
||||||
var needCaptcha = resp.data.needCaptcha;
|
|
||||||
callback(needCaptcha);
|
|
||||||
} else {
|
|
||||||
callback(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
//登录
|
|
||||||
login: function (username, pwd, captcha, onSuccess, onFailed) {
|
|
||||||
var service = this.getQueryVariable("service");
|
|
||||||
var encrypt = new JSEncrypt();
|
|
||||||
encrypt.setPublicKey(this.publicKey);
|
|
||||||
username = encrypt.encrypt(username);
|
|
||||||
pwd = encrypt.encrypt(pwd);
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
loginname: username,
|
|
||||||
pwd: pwd,
|
|
||||||
captcha: captcha,
|
|
||||||
};
|
|
||||||
if (typeof service === "string") params.service = service;
|
|
||||||
console.log(JSON.stringify(params));
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: this.loginUrl,
|
|
||||||
type: "POST",
|
|
||||||
data: params,
|
|
||||||
dataType: "json",
|
|
||||||
success: function (resp, textStatus, jqXHR) {
|
|
||||||
if (resp && typeof resp.code !== "undefined") {
|
|
||||||
if (resp.code == 0) {
|
|
||||||
var url = (resp.data && resp.data.url) || "";
|
|
||||||
// 兜底:后端未返回有效地址或返回了 CAS 原生登录页路径时,跳回同源根
|
|
||||||
if (!url || url.indexOf("/cas/login") === 0) {
|
|
||||||
url = "/";
|
|
||||||
}
|
|
||||||
console.log("login success, redirect =>", url);
|
|
||||||
onSuccess(url);
|
|
||||||
} else {
|
|
||||||
var needCaptcha = resp.data ? resp.data.needCaptcha : false;
|
|
||||||
onFailed(resp.msg, needCaptcha);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSuccess(jqXHR.responseURL || "");
|
|
||||||
},
|
|
||||||
error: function (jqXHR) {
|
|
||||||
var resp = jqXHR.responseJSON;
|
|
||||||
if (resp && typeof resp.code !== "undefined") {
|
|
||||||
var needCaptcha = resp.data ? resp.data.needCaptcha : false;
|
|
||||||
onFailed(resp.msg, needCaptcha);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onFailed("登录请求失败", false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//请求短信验证码
|
|
||||||
requestSmsCode: function (phoneCode, onSuccess, onFailed) {
|
|
||||||
var params = {
|
|
||||||
phone: phoneCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
$.get(
|
|
||||||
this.smsCodeUrl,
|
|
||||||
params,
|
|
||||||
function (resp) {
|
|
||||||
if (resp.code == 0) {
|
|
||||||
onSuccess();
|
|
||||||
} else {
|
|
||||||
onFailed(resp.msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
//按短信验证码,修改密码
|
|
||||||
updatePwd: function (phoneCode, smsCode, newPwd, onSuccess, onFailed) {
|
|
||||||
var params = {
|
|
||||||
phone: phoneCode,
|
|
||||||
smsCode: smsCode,
|
|
||||||
newPwd: newPwd,
|
|
||||||
};
|
|
||||||
|
|
||||||
$.post(
|
|
||||||
this.updatePwdUrl,
|
|
||||||
params,
|
|
||||||
function (resp) {
|
|
||||||
if (resp.code == 0) {
|
|
||||||
onSuccess();
|
|
||||||
} else {
|
|
||||||
onFailed(resp.msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
captchaSrc: function () {
|
|
||||||
return this.captchaUrl + "?k=" + new Date().getTime();
|
|
||||||
},
|
|
||||||
|
|
||||||
getQueryVariable: function (paramName) {
|
|
||||||
var query = window.location.search.substring(1);
|
|
||||||
var params = query.split("&");
|
|
||||||
for (var i = 0; i < params.length; i++) {
|
|
||||||
var param = params[i];
|
|
||||||
|
|
||||||
var pos = param.indexOf("=");
|
|
||||||
if (pos == -1) continue;
|
|
||||||
|
|
||||||
var key = param.slice(0, pos);
|
|
||||||
var value = param.slice(pos + 1, param.length);
|
|
||||||
|
|
||||||
if (key == paramName) return value;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
setCookie: function (c_name, value, expiredays) {
|
|
||||||
var exdate = new Date();
|
|
||||||
exdate.setDate(exdate.getDate() + expiredays);
|
|
||||||
document.cookie =
|
|
||||||
c_name + "=" + escape(value) + (expiredays == null ? "" : ";expires=" + exdate.toGMTString()) + ";path=/"; //+";domain=";
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// CAS context-path 为根 "/",前端由 CAS 同源托管在 /front/,接口在 /api/*。
|
|
||||||
// 用同源相对路径,避免写死 host/协议,且天然规避跨域(LoginController 未开 CORS)。
|
|
||||||
window.ssoServerHost = "/cas/";
|
|
||||||
|
|
||||||
window.beianText = "©2021 bestsch.com inc All Rights reserved";
|
|
||||||
window.beianLink = "【沪ICP备13000443号-1】";
|
|
||||||
window.logoHeight = 35;
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
(function( $ )
|
|
||||||
{
|
|
||||||
var element;
|
|
||||||
$.fn.qrcode = function(options)
|
|
||||||
{
|
|
||||||
// if options is string,
|
|
||||||
if( typeof options === 'string' ){
|
|
||||||
options = { text: options };
|
|
||||||
}
|
|
||||||
|
|
||||||
// set default values
|
|
||||||
// typeNumber < 1 for automatic calculation
|
|
||||||
options = $.extend( {}, {
|
|
||||||
render : "canvas",
|
|
||||||
width : 256,
|
|
||||||
height : 256,
|
|
||||||
typeNumber : -1,
|
|
||||||
correctLevel : QRErrorCorrectLevel.H,
|
|
||||||
background : "#ffffff",
|
|
||||||
foreground : "#000000"
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
var createCanvas = function(){
|
|
||||||
// create the qrcode itself
|
|
||||||
var qrcode = new QRCode(options.typeNumber, options.correctLevel);
|
|
||||||
qrcode.addData(options.text);
|
|
||||||
qrcode.make();
|
|
||||||
|
|
||||||
// create canvas element
|
|
||||||
var canvas = document.createElement('canvas');
|
|
||||||
canvas.width = options.width;
|
|
||||||
canvas.height = options.height;
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// compute tileW/tileH based on options.width/options.height
|
|
||||||
var tileW = options.width / qrcode.getModuleCount();
|
|
||||||
var tileH = options.height / qrcode.getModuleCount();
|
|
||||||
|
|
||||||
// draw in the canvas
|
|
||||||
for( var row = 0; row < qrcode.getModuleCount(); row++ ){
|
|
||||||
for( var col = 0; col < qrcode.getModuleCount(); col++ ){
|
|
||||||
ctx.fillStyle = qrcode.isDark(row, col) ? options.foreground : options.background;
|
|
||||||
var w = (Math.ceil((col+1)*tileW) - Math.floor(col*tileW));
|
|
||||||
var h = (Math.ceil((row+1)*tileW) - Math.floor(row*tileW));
|
|
||||||
ctx.fillRect(Math.round(col*tileW),Math.round(row*tileH), w, h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return just built canvas
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
// from Jon-Carlos Rivera (https://github.com/imbcmdth)
|
|
||||||
var createTable = function(){
|
|
||||||
// create the qrcode itself
|
|
||||||
var qrcode = new QRCode(options.typeNumber, options.correctLevel);
|
|
||||||
qrcode.addData(options.text);
|
|
||||||
qrcode.make();
|
|
||||||
|
|
||||||
// create table element
|
|
||||||
var $table = $('<table></table>')
|
|
||||||
.css("width", options.width+"px")
|
|
||||||
.css("height", options.height+"px")
|
|
||||||
.css("border", "0px")
|
|
||||||
.css("border-collapse", "collapse")
|
|
||||||
.css('background-color', options.background);
|
|
||||||
|
|
||||||
// compute tileS percentage
|
|
||||||
var tileW = options.width / qrcode.getModuleCount();
|
|
||||||
var tileH = options.height / qrcode.getModuleCount();
|
|
||||||
|
|
||||||
// draw in the table
|
|
||||||
for(var row = 0; row < qrcode.getModuleCount(); row++ ){
|
|
||||||
var $row = $('<tr></tr>').css('height', tileH+"px").appendTo($table);
|
|
||||||
|
|
||||||
for(var col = 0; col < qrcode.getModuleCount(); col++ ){
|
|
||||||
$('<td></td>')
|
|
||||||
.css('width', tileW+"px")
|
|
||||||
.css('background-color', qrcode.isDark(row, col) ? options.foreground : options.background)
|
|
||||||
.appendTo($row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return just built canvas
|
|
||||||
return $table;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return this.each(function()
|
|
||||||
{
|
|
||||||
if (typeof(element) != "undefined")
|
|
||||||
element.remove();
|
|
||||||
|
|
||||||
element = options.render == "canvas" ? createCanvas() : createTable();
|
|
||||||
$(element).appendTo(this);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
})( jQuery );
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// 一些全局设置样式
|
|
||||||
a{
|
|
||||||
text-decoration: none;
|
|
||||||
color: #337ab7;
|
|
||||||
&:hover{
|
|
||||||
text-decoration:underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*{
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
html{font-size: 14px;}
|
|
||||||
body{background-color: #eff2f6;}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*滚动条样式 */
|
|
||||||
|
|
||||||
/*第一种样式,所有的滚动条,都默认这个样子*/
|
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*正常情况下滑块的样式*/
|
|
||||||
*::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(0, 0, 0, .05);
|
|
||||||
border-radius: 10px;
|
|
||||||
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*鼠标悬浮在该类指向的控件上时滑块的样式*/
|
|
||||||
*:hover::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(0, 0, 0, .2);
|
|
||||||
border-radius: 10px;
|
|
||||||
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*鼠标悬浮在滑块上时滑块的样式*/
|
|
||||||
*::-webkit-scrollbar-thumb:hover {
|
|
||||||
background-color: rgba(0, 0, 0, .3);
|
|
||||||
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*正常时候的主干部分*/
|
|
||||||
*::-webkit-scrollbar-track {
|
|
||||||
border-radius: 10px;
|
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*鼠标悬浮在滚动条上的主干部分*/
|
|
||||||
*::-webkit-scrollbar-track:hover {
|
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .4);
|
|
||||||
background-color: rgba(0, 0, 0, .01);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*第一种样式结束*/
|
|
||||||
|
|
||||||
|
|
||||||
/*第二种样式*/
|
|
||||||
|
|
||||||
.scroll2::-webkit-scrollbar {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*正常时候的主干部分*/
|
|
||||||
.scroll2::-webkit-scrollbar-track {
|
|
||||||
border-radius: 10px;
|
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*正常情况下滑块的样式*/
|
|
||||||
.scroll2::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(0, 0, 0, .05);
|
|
||||||
border-radius: 10px;
|
|
||||||
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*鼠标悬浮在该类指向的控件上时滑块的样式*/
|
|
||||||
.scroll2:hover::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(0, 0, 0, .2);
|
|
||||||
border-radius: 10px;
|
|
||||||
-webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*鼠标悬浮在滚动条上的主干部分*/
|
|
||||||
.scroll2::-webkit-scrollbar-track:hover {
|
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*鼠标悬浮在滑块上时滑块的样式*/
|
|
||||||
.scroll2::-webkit-scrollbar-thumb:hover {
|
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*第二种样式结束*/
|
|
||||||
@@ -1,663 +0,0 @@
|
|||||||
@import "reset.less";
|
|
||||||
// @import "normalize.less";
|
|
||||||
@import "bscroll.less";
|
|
||||||
|
|
||||||
.align-middle {
|
|
||||||
position: relative;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
.align-middle-wrap {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.text-ellipsis {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
|
||||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
|
||||||
// font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", "Hiragino Sans GB", "Microsoft YaHei" , "微软雅黑";
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #272829;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
min-width: 1002px;
|
|
||||||
line-height: 1.5;
|
|
||||||
|
|
||||||
font-size: 14px;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
[v-cloak] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 浮动与清除浮动
|
|
||||||
.fl {
|
|
||||||
float: left !important;
|
|
||||||
}
|
|
||||||
.fr {
|
|
||||||
float: right !important;
|
|
||||||
}
|
|
||||||
.fix {
|
|
||||||
*zoom: 1;
|
|
||||||
}
|
|
||||||
.fix:after {
|
|
||||||
display: table;
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-wrapper {
|
|
||||||
width: 1050px;
|
|
||||||
margin: 0 auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.base-slider {
|
|
||||||
width: 48px;
|
|
||||||
position: absolute;
|
|
||||||
left: 7px;
|
|
||||||
bottom: -1px;
|
|
||||||
z-index: 1;
|
|
||||||
transition: transform 0.3s ease-in-out;
|
|
||||||
transform-origin: 0 0;
|
|
||||||
.indicator {
|
|
||||||
width: 40px;
|
|
||||||
height: 2px;
|
|
||||||
background: #272829;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header.header-wrapper {
|
|
||||||
height: 88px;
|
|
||||||
background: #fff;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0px 10px 20px 0px rgba(239, 239, 239, 0.09);
|
|
||||||
.logo-box {
|
|
||||||
height: 35px;
|
|
||||||
max-height: 60px;
|
|
||||||
overflow: hidden;
|
|
||||||
img {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.schname-box {
|
|
||||||
line-height: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-weight: 700;
|
|
||||||
font-family: SimSun;
|
|
||||||
font-family: SourceHanSerifCN-Bold, SourceHanSerifCN;
|
|
||||||
}
|
|
||||||
.app-down {
|
|
||||||
margin-right: 5px;
|
|
||||||
font-size: 15px;
|
|
||||||
height: 35px;
|
|
||||||
line-height: 35px;
|
|
||||||
font-weight: 400;
|
|
||||||
a {
|
|
||||||
color: #272829;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.down-app-box {
|
|
||||||
width: 200px;
|
|
||||||
height: 222px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: absolute;
|
|
||||||
top: 65px;
|
|
||||||
right: 0px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 2px;
|
|
||||||
display: none;
|
|
||||||
.tips-box {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 40px;
|
|
||||||
&::after {
|
|
||||||
display: block;
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: 0px;
|
|
||||||
top: -9px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-right: 8px solid transparent;
|
|
||||||
border-bottom: 8px solid #e8e8e8;
|
|
||||||
border-left: 8px solid transparent;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
&::before {
|
|
||||||
display: block;
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: 0px;
|
|
||||||
top: -8px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-right: 8px solid transparent;
|
|
||||||
border-bottom: 8px solid #fff;
|
|
||||||
border-left: 8px solid transparent;
|
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
height: 20px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #272829;
|
|
||||||
line-height: 20px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
margin-top: 26px;
|
|
||||||
width: 140px;
|
|
||||||
height: 140px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.main-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 480px;
|
|
||||||
min-height: 480px;
|
|
||||||
// background: url("../image/banner-bg.png") no-repeat center center/ cover;
|
|
||||||
position: relative;
|
|
||||||
padding-top: 88px;
|
|
||||||
.main-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-box {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
left: 0;
|
|
||||||
top: 88px;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 1;
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
overflow: hidden;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
font-family: "object-fit: cover;";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-box {
|
|
||||||
span {
|
|
||||||
color: #ff0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.qrcode-box {
|
|
||||||
margin-top: 35px;
|
|
||||||
margin-bottom: 35px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.login-wrapper {
|
|
||||||
width: 320px;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
.login-header {
|
|
||||||
border-bottom: 1px solid #f6f6f6;
|
|
||||||
padding-top: 10px;
|
|
||||||
position: relative;
|
|
||||||
margin: 0 30px;
|
|
||||||
// margin-bottom: 28px;
|
|
||||||
ul {
|
|
||||||
padding: 14px 0;
|
|
||||||
li {
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
height: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
margin-right: 14px;
|
|
||||||
color: #656667;
|
|
||||||
font-weight: 550;
|
|
||||||
}
|
|
||||||
li.active {
|
|
||||||
color: #272829;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-slider {
|
|
||||||
.base-slider;
|
|
||||||
left: 0;
|
|
||||||
.indicator {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
&.active-1 {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
&.active-2 {
|
|
||||||
transform: translateX(70px);
|
|
||||||
}
|
|
||||||
&.active-3 {
|
|
||||||
transform: translateX(140px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.login-body {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.login-footer {
|
|
||||||
height: 22px;
|
|
||||||
margin: 15px 40px;
|
|
||||||
}
|
|
||||||
.login-form {
|
|
||||||
margin:40px 30px 20px;
|
|
||||||
.form-item {
|
|
||||||
margin-top: 17px;
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #f1f1f1;
|
|
||||||
}
|
|
||||||
.icon-box {
|
|
||||||
background: #fff;
|
|
||||||
width: 48px;
|
|
||||||
height: 40px;
|
|
||||||
float: left;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-right: 1px solid #f1f1f1;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
p.input-box {
|
|
||||||
height: 40px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
padding: 4px 10px 4px 16px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
#password_input {
|
|
||||||
width: calc(100% - 48px);
|
|
||||||
&::-ms-clear {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
&::-ms-reveal {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pwd-icon-box {
|
|
||||||
.icon-box;
|
|
||||||
float: right;
|
|
||||||
background-color: #fafafa;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.operate-box{
|
|
||||||
margin: 12px 0;
|
|
||||||
.fix;
|
|
||||||
label{
|
|
||||||
color: #b1b1b1;
|
|
||||||
font-size: 12px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
.login-checkbox {
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
top: -1px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
a{
|
|
||||||
font-size: 12px;
|
|
||||||
float: right;
|
|
||||||
color: #6d8cf0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.btn-submit {
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 42px;
|
|
||||||
height: 40px;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
outline: none;
|
|
||||||
padding: 0px 20px;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #6b8cf4;
|
|
||||||
border: none;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0px;
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
&:hover::before {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
&:active::after {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
.btn-vercode{
|
|
||||||
overflow: hidden;
|
|
||||||
background: #6b8cf4;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
width:100px;
|
|
||||||
height: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
border-radius: 36px;
|
|
||||||
margin:0;
|
|
||||||
margin-top:17px;
|
|
||||||
|
|
||||||
&[disabled]{
|
|
||||||
background: #b6c5f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vercode-wrap{
|
|
||||||
.form-item{
|
|
||||||
width:calc(100% - 116px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// .login-form,
|
|
||||||
// .login-form .btn-submit {
|
|
||||||
// margin-top: 40px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.qrcode-box {
|
|
||||||
text-align: center;
|
|
||||||
img {
|
|
||||||
margin: 42px auto 25px;
|
|
||||||
width: 130px;
|
|
||||||
height: 130px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #656667;
|
|
||||||
margin: 0 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.forgetpw-wrapper {
|
|
||||||
width: 320px;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
z-index: 3;
|
|
||||||
.login-form{
|
|
||||||
margin-top:10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.forgetpw-stats-box{
|
|
||||||
text-align: center;
|
|
||||||
img{
|
|
||||||
width: 90px;
|
|
||||||
height: 90px;
|
|
||||||
object-fit: cover;
|
|
||||||
margin: 60px 0 30px;
|
|
||||||
}
|
|
||||||
h4{
|
|
||||||
height: 32px;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(0, 0, 0, 0.8500000238418579);
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
height: 22px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(0, 0, 0, 0.44999998807907104);
|
|
||||||
line-height: 22px;
|
|
||||||
margin:10px 0;
|
|
||||||
}
|
|
||||||
button{
|
|
||||||
width:260px;
|
|
||||||
margin: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tips-info-box{
|
|
||||||
color: #858585;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 15px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
height: 40px;
|
|
||||||
margin-top: 15px;
|
|
||||||
border-bottom: 1px solid #ececec;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 7px;
|
|
||||||
position: relative;
|
|
||||||
.tabs-nav {
|
|
||||||
li {
|
|
||||||
float: left;
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
user-select: none;
|
|
||||||
height: 25px;
|
|
||||||
line-height: 25px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.spacer-li {
|
|
||||||
width: 16px;
|
|
||||||
span {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
li.active {
|
|
||||||
color: #272829;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabs-slider {
|
|
||||||
.base-slider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-ul {
|
|
||||||
padding-left: 60px;
|
|
||||||
background: #fff;
|
|
||||||
padding-bottom: 38px;
|
|
||||||
& > li {
|
|
||||||
float: left;
|
|
||||||
height: 100px;
|
|
||||||
width: (1/6) * 100%;
|
|
||||||
padding-right: 60px;
|
|
||||||
margin-top: 38px;
|
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
a {
|
|
||||||
.fix;
|
|
||||||
display: block;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #272829;
|
|
||||||
height: 64px;
|
|
||||||
width: 64px;
|
|
||||||
margin: 0 auto;
|
|
||||||
// box-shadow:0px 10px 20px 0px rgba(0,0,0,0.05);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
outline: none;
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
#f8f8f9 0%,
|
|
||||||
#ffffff 21%,
|
|
||||||
#f7f7f7 100%
|
|
||||||
);
|
|
||||||
box-shadow: 0px 10px 20px 0px rgba(227, 230, 236, 0.33);
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #eaedf1;
|
|
||||||
// border-image: linear-gradient(180deg, rgba(228, 231, 237, 1), rgba(228, 231, 237, 1)) 1 1;
|
|
||||||
|
|
||||||
.icon-box {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
text-align: center;
|
|
||||||
margin: 11px;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
// box-shadow:0 0 15px red;-moz-transition:all .3s;-webkit-transition:all .3s;-transition:all .3s;
|
|
||||||
// background: darken($color: #fff, $amount: 8) ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h4.info {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-top: 12px;
|
|
||||||
color: #272829;
|
|
||||||
.text-ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-list-wrapper {
|
|
||||||
.main-wrapper;
|
|
||||||
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.04);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 18px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.wait-app-wrapper {
|
|
||||||
.main-wrapper;
|
|
||||||
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.04);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
h3.title {
|
|
||||||
height: 61px;
|
|
||||||
line-height: 25px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
background: #fff;
|
|
||||||
text-align: center;
|
|
||||||
padding: 18px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-bottom: 1px solid #ececec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-container {
|
|
||||||
background: #374a5f;
|
|
||||||
width: 100%;
|
|
||||||
height: 98px;
|
|
||||||
p {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(255, 255, 255, 0.35);
|
|
||||||
text-align: center;
|
|
||||||
font-family: SimSun;
|
|
||||||
font-family: SourceHanSansCN-Regular, SourceHanSansCN;
|
|
||||||
a{
|
|
||||||
color: rgba(255, 255, 255, 0.35);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-container.theme-light {
|
|
||||||
background: #fff;
|
|
||||||
p {
|
|
||||||
color: #272829;
|
|
||||||
a{
|
|
||||||
color: #272829;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container {
|
|
||||||
min-height: 480px;
|
|
||||||
}
|
|
||||||
.swiper-slide {
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
text-align: center;
|
|
||||||
bottom: 5px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.swiper-pagination-bullet {
|
|
||||||
margin: 0 3px;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #000;
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
.swiper-pagination-bullet-active {
|
|
||||||
background: #f1f1f1;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
/* 移除不必要的部分 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body{
|
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
html,body,#app{
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-down,.banner-box{display: none;}
|
|
||||||
.footer-container{
|
|
||||||
height: 58px;
|
|
||||||
}
|
|
||||||
header.header-wrapper{
|
|
||||||
height: 58px;
|
|
||||||
padding:0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-wrapper{
|
|
||||||
padding-top:48px;
|
|
||||||
height: calc(100% - 105px);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.banner-box, .tabs, .app-list-wrapper, .wait-app-wrapper {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-wrapper {
|
|
||||||
width: 100% !important;
|
|
||||||
margin: 0 auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-wrapper {
|
|
||||||
width: 90% !important;
|
|
||||||
margin: 0 auto !important ;
|
|
||||||
float: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header ul {
|
|
||||||
padding: 14px 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header ul li {
|
|
||||||
margin-right: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
margin: 20px 15px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form .form-item {
|
|
||||||
margin-top: 15px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form .icon-box {
|
|
||||||
width: 40px !important;
|
|
||||||
height: 40px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form p.input-box input {
|
|
||||||
padding: 4px 10px 4px 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operate-box {
|
|
||||||
margin: 12px 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operate-box a {
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit {
|
|
||||||
height: 40px !important;
|
|
||||||
line-height: 40px !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
margin-bottom: 10px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain)*/
|
|
||||||
.html,
|
|
||||||
body,
|
|
||||||
div,
|
|
||||||
span,
|
|
||||||
applet,
|
|
||||||
object,
|
|
||||||
iframe,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
p,
|
|
||||||
blockquote,
|
|
||||||
pre,
|
|
||||||
a,
|
|
||||||
abbr,
|
|
||||||
acronym,
|
|
||||||
address,
|
|
||||||
big,
|
|
||||||
cite,
|
|
||||||
code,
|
|
||||||
del,
|
|
||||||
dfn,
|
|
||||||
em,
|
|
||||||
img,
|
|
||||||
ins,
|
|
||||||
kbd,
|
|
||||||
q,
|
|
||||||
s,
|
|
||||||
samp,
|
|
||||||
small,
|
|
||||||
strike,
|
|
||||||
strong,
|
|
||||||
sub,
|
|
||||||
sup,
|
|
||||||
tt,
|
|
||||||
var,
|
|
||||||
b,
|
|
||||||
u,
|
|
||||||
i,
|
|
||||||
center,
|
|
||||||
dl,
|
|
||||||
dt,
|
|
||||||
dd,
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
li,
|
|
||||||
fieldset,
|
|
||||||
form,
|
|
||||||
label,
|
|
||||||
legend,
|
|
||||||
table,
|
|
||||||
caption,
|
|
||||||
tbody,
|
|
||||||
tfoot,
|
|
||||||
thead,
|
|
||||||
tr,
|
|
||||||
th,
|
|
||||||
td,
|
|
||||||
article,
|
|
||||||
aside,
|
|
||||||
canvas,
|
|
||||||
details,
|
|
||||||
embed,
|
|
||||||
figure,
|
|
||||||
figcaption,
|
|
||||||
footer,
|
|
||||||
header,
|
|
||||||
hgroup,
|
|
||||||
menu,
|
|
||||||
nav,
|
|
||||||
output,
|
|
||||||
ruby,
|
|
||||||
section,
|
|
||||||
summary,
|
|
||||||
time,
|
|
||||||
mark,
|
|
||||||
audio,
|
|
||||||
video {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
font-size: 100%;
|
|
||||||
font: inherit;
|
|
||||||
vertical-align: baseline;
|
|
||||||
line-height: 1.5;
|
|
||||||
} /* HTML5 display-role reset for older browsers */
|
|
||||||
article,
|
|
||||||
aside,
|
|
||||||
details,
|
|
||||||
figcaption,
|
|
||||||
figure,
|
|
||||||
footer,
|
|
||||||
header,
|
|
||||||
hgroup,
|
|
||||||
menu,
|
|
||||||
nav,
|
|
||||||
section {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
blockquote,
|
|
||||||
q {
|
|
||||||
quotes: none;
|
|
||||||
}
|
|
||||||
blockquote:before,
|
|
||||||
blockquote:after,
|
|
||||||
q:before,
|
|
||||||
q:after {
|
|
||||||
content: "";
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// utils 工具
|
|
||||||
.text-black{color:#000}
|
|
||||||
.text-dark{color:#333}
|
|
||||||
.text-deep{color:#555}
|
|
||||||
.text-weak{color:#B3B3B3}
|
|
||||||
.text-white{color:#fff}
|
|
||||||
|
|
||||||
// 文字对齐
|
|
||||||
.tl{text-align:left}
|
|
||||||
.tc{text-align:center}
|
|
||||||
.tr{text-align:right}
|
|
||||||
// 浮动与清除浮动
|
|
||||||
.fl{float:left}
|
|
||||||
.fr{float:right}
|
|
||||||
.fix{*zoom:1}
|
|
||||||
.fix:after{display:table;content:'';clear:both}
|
|
||||||
// 显示
|
|
||||||
.dn{display:none}
|
|
||||||
.di{display:inline}
|
|
||||||
.db{display:block}
|
|
||||||
.dib{display:inline-block}
|
|
||||||
.dt{display:table}
|
|
||||||
div.dib{*display:inline;*zoom:1}
|
|
||||||
.vm{vertical-align:middle}
|
|
||||||
.vib{display:inline-block;vertical-align:middle}
|
|
||||||
// 定位
|
|
||||||
.pr{position:relative}
|
|
||||||
.pa{position:absolute}
|
|
||||||
.pf{position:fixed}
|
|
||||||
|
|
||||||
.for(@count){
|
|
||||||
.width-@{count} { width: @count *1%;}
|
|
||||||
.height-@{count} { height: @count *1%;}
|
|
||||||
|
|
||||||
.ml-@{count} { margin-left: @count * 1px; }
|
|
||||||
.mr-@{count} { margin-right: @count * 1px; }
|
|
||||||
.mt-@{count} { margin-top: @count* 1px; }
|
|
||||||
.mb-@{count} { margin-bottom: @count * 1px; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.loop(50);
|
|
||||||
.loop(@n, @i: 0) when (@i =< @n) {
|
|
||||||
.ml-@{i} { margin-left: @i * 1px !important; }
|
|
||||||
.mr-@{i} { margin-right: @i * 1px!important; }
|
|
||||||
.mt-@{i} { margin-top: @i* 1px!important; }
|
|
||||||
.mb-@{i} { margin-bottom: @i * 1px!important; }
|
|
||||||
|
|
||||||
.loop(@n, (@i+1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-ellipsis{
|
|
||||||
overflow: hidden;white-space: nowrap;text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.col-12{width:100%}
|
|
||||||
.col-11{width:91.66666667%}
|
|
||||||
.col-10{width:83.33333333%}
|
|
||||||
.col-9{width:75%}
|
|
||||||
.col-8{width:66.66666667%}
|
|
||||||
.col-7{width:58.33333333%}
|
|
||||||
.col-6{width:50%}
|
|
||||||
.col-5{width:41.66666667%}
|
|
||||||
.col-4{width:33.33333333%}
|
|
||||||
.col-3{width:25%}
|
|
||||||
.col-2{width:16.66666667%}
|
|
||||||
.col-1{width:8.33333333%}
|
|
||||||
|
|
||||||
.col-4, .col-8, .col-12{
|
|
||||||
position: relative;
|
|
||||||
min-height: 1px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/*! npm.im/object-fit-images 3.2.4 */
|
|
||||||
var objectFitImages=function(){"use strict";function t(t,e){return"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='"+t+"' height='"+e+"'%3E%3C/svg%3E"}function e(t){if(t.srcset&&!p&&window.picturefill){var e=window.picturefill._;t[e.ns]&&t[e.ns].evaled||e.fillImg(t,{reselect:!0}),t[e.ns].curSrc||(t[e.ns].supported=!1,e.fillImg(t,{reselect:!0})),t.currentSrc=t[e.ns].curSrc||t.src}}function i(t){for(var e,i=getComputedStyle(t).fontFamily,r={};null!==(e=u.exec(i));)r[e[1]]=e[2];return r}function r(e,i,r){var n=t(i||1,r||0);b.call(e,"src")!==n&&h.call(e,"src",n)}function n(t,e){t.naturalWidth?e(t):setTimeout(n,100,t,e)}function c(t){var c=i(t),o=t[l];if(c["object-fit"]=c["object-fit"]||"fill",!o.img){if("fill"===c["object-fit"])return;if(!o.skipTest&&f&&!c["object-position"])return}if(!o.img){o.img=new Image(t.width,t.height),o.img.srcset=b.call(t,"data-ofi-srcset")||t.srcset,o.img.src=b.call(t,"data-ofi-src")||t.src,h.call(t,"data-ofi-src",t.src),t.srcset&&h.call(t,"data-ofi-srcset",t.srcset),r(t,t.naturalWidth||t.width,t.naturalHeight||t.height),t.srcset&&(t.srcset="");try{s(t)}catch(t){window.console&&console.warn("https://bit.ly/ofi-old-browser")}}e(o.img),t.style.backgroundImage='url("'+(o.img.currentSrc||o.img.src).replace(/"/g,'\\"')+'")',t.style.backgroundPosition=c["object-position"]||"center",t.style.backgroundRepeat="no-repeat",t.style.backgroundOrigin="content-box",/scale-down/.test(c["object-fit"])?n(o.img,function(){o.img.naturalWidth>t.width||o.img.naturalHeight>t.height?t.style.backgroundSize="contain":t.style.backgroundSize="auto"}):t.style.backgroundSize=c["object-fit"].replace("none","auto").replace("fill","100% 100%"),n(o.img,function(e){r(t,e.naturalWidth,e.naturalHeight)})}function s(t){var e={get:function(e){return t[l].img[e?e:"src"]},set:function(e,i){return t[l].img[i?i:"src"]=e,h.call(t,"data-ofi-"+i,e),c(t),e}};Object.defineProperty(t,"src",e),Object.defineProperty(t,"currentSrc",{get:function(){return e.get("currentSrc")}}),Object.defineProperty(t,"srcset",{get:function(){return e.get("srcset")},set:function(t){return e.set(t,"srcset")}})}function o(){function t(t,e){return t[l]&&t[l].img&&("src"===e||"srcset"===e)?t[l].img:t}d||(HTMLImageElement.prototype.getAttribute=function(e){return b.call(t(this,e),e)},HTMLImageElement.prototype.setAttribute=function(e,i){return h.call(t(this,e),e,String(i))})}function a(t,e){var i=!y&&!t;if(e=e||{},t=t||"img",d&&!e.skipTest||!m)return!1;"img"===t?t=document.getElementsByTagName("img"):"string"==typeof t?t=document.querySelectorAll(t):"length"in t||(t=[t]);for(var r=0;r<t.length;r++)t[r][l]=t[r][l]||{skipTest:e.skipTest},c(t[r]);i&&(document.body.addEventListener("load",function(t){"IMG"===t.target.tagName&&a(t.target,{skipTest:e.skipTest})},!0),y=!0,t="img"),e.watchMQ&&window.addEventListener("resize",a.bind(null,t,{skipTest:e.skipTest}))}var l="fregante:object-fit-images",u=/(object-fit|object-position)\s*:\s*([-.\w\s%]+)/g,g="undefined"==typeof Image?{style:{"object-position":1}}:new Image,f="object-fit"in g.style,d="object-position"in g.style,m="background-size"in g.style,p="string"==typeof g.currentSrc,b=g.getAttribute,h=g.setAttribute,y=!1;return a.supportsObjectFit=f,a.supportsObjectPosition=d,o(),a}();
|
|
||||||
@@ -1,571 +0,0 @@
|
|||||||
/**
|
|
||||||
* Swiper 3.4.2
|
|
||||||
* Most modern mobile touch slider and framework with hardware accelerated transitions
|
|
||||||
*
|
|
||||||
* http://www.idangero.us/swiper/
|
|
||||||
*
|
|
||||||
* Copyright 2017, Vladimir Kharlampidi
|
|
||||||
* The iDangero.us
|
|
||||||
* http://www.idangero.us/
|
|
||||||
*
|
|
||||||
* Licensed under MIT
|
|
||||||
*
|
|
||||||
* Released on: March 10, 2017
|
|
||||||
*/
|
|
||||||
.swiper-container {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-no-flexbox .swiper-slide {
|
|
||||||
float: left
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-vertical>.swiper-wrapper {
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-moz-box-orient: vertical;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
flex-direction: column
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -moz-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: flex;
|
|
||||||
-webkit-transition-property: -webkit-transform;
|
|
||||||
-moz-transition-property: -moz-transform;
|
|
||||||
-o-transition-property: -o-transform;
|
|
||||||
-ms-transition-property: -ms-transform;
|
|
||||||
transition-property: transform;
|
|
||||||
-webkit-box-sizing: content-box;
|
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
box-sizing: content-box
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-android .swiper-slide,.swiper-wrapper {
|
|
||||||
-webkit-transform: translate3d(0,0,0);
|
|
||||||
-moz-transform: translate3d(0,0,0);
|
|
||||||
-o-transform: translate(0,0);
|
|
||||||
-ms-transform: translate3d(0,0,0);
|
|
||||||
transform: translate3d(0,0,0)
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-multirow>.swiper-wrapper {
|
|
||||||
-webkit-box-lines: multiple;
|
|
||||||
-moz-box-lines: multiple;
|
|
||||||
-ms-flex-wrap: wrap;
|
|
||||||
-webkit-flex-wrap: wrap;
|
|
||||||
flex-wrap: wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-free-mode>.swiper-wrapper {
|
|
||||||
-webkit-transition-timing-function: ease-out;
|
|
||||||
-moz-transition-timing-function: ease-out;
|
|
||||||
-ms-transition-timing-function: ease-out;
|
|
||||||
-o-transition-timing-function: ease-out;
|
|
||||||
transition-timing-function: ease-out;
|
|
||||||
margin: 0 auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-slide {
|
|
||||||
-webkit-flex-shrink: 0;
|
|
||||||
-ms-flex: 0 0 auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-autoheight,.swiper-container-autoheight .swiper-slide {
|
|
||||||
height: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-autoheight .swiper-wrapper {
|
|
||||||
-webkit-box-align: start;
|
|
||||||
-ms-flex-align: start;
|
|
||||||
-webkit-align-items: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
-webkit-transition-property: -webkit-transform,height;
|
|
||||||
-moz-transition-property: -moz-transform;
|
|
||||||
-o-transition-property: -o-transform;
|
|
||||||
-ms-transition-property: -ms-transform;
|
|
||||||
transition-property: transform,height
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container .swiper-notification {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0;
|
|
||||||
z-index: -1000
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-wp8-horizontal {
|
|
||||||
-ms-touch-action: pan-y;
|
|
||||||
touch-action: pan-y
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-wp8-vertical {
|
|
||||||
-ms-touch-action: pan-x;
|
|
||||||
touch-action: pan-x
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-next,.swiper-button-prev {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 27px;
|
|
||||||
height: 44px;
|
|
||||||
margin-top: -22px;
|
|
||||||
z-index: 10;
|
|
||||||
cursor: pointer;
|
|
||||||
-moz-background-size: 27px 44px;
|
|
||||||
-webkit-background-size: 27px 44px;
|
|
||||||
background-size: 27px 44px;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-next.swiper-button-disabled,.swiper-button-prev.swiper-button-disabled {
|
|
||||||
opacity: .35;
|
|
||||||
cursor: auto;
|
|
||||||
pointer-events: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-prev,.swiper-container-rtl .swiper-button-next {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E");
|
|
||||||
left: 10px;
|
|
||||||
right: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-prev.swiper-button-black,.swiper-container-rtl .swiper-button-next.swiper-button-black {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E")
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-prev.swiper-button-white,.swiper-container-rtl .swiper-button-next.swiper-button-white {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E")
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-next,.swiper-container-rtl .swiper-button-prev {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E");
|
|
||||||
right: 10px;
|
|
||||||
left: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-next.swiper-button-black,.swiper-container-rtl .swiper-button-prev.swiper-button-black {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E")
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-button-next.swiper-button-white,.swiper-container-rtl .swiper-button-prev.swiper-button-white {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E")
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination {
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-transition: .3s;
|
|
||||||
-moz-transition: .3s;
|
|
||||||
-o-transition: .3s;
|
|
||||||
transition: .3s;
|
|
||||||
-webkit-transform: translate3d(0,0,0);
|
|
||||||
-ms-transform: translate3d(0,0,0);
|
|
||||||
-o-transform: translate3d(0,0,0);
|
|
||||||
transform: translate3d(0,0,0);
|
|
||||||
z-index: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination.swiper-pagination-hidden {
|
|
||||||
opacity: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-horizontal>.swiper-pagination-bullets,.swiper-pagination-custom,.swiper-pagination-fraction {
|
|
||||||
bottom: 10px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-bullet {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 100%;
|
|
||||||
background: #000;
|
|
||||||
opacity: .2
|
|
||||||
}
|
|
||||||
|
|
||||||
button.swiper-pagination-bullet {
|
|
||||||
border: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
-ms-appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-clickable .swiper-pagination-bullet {
|
|
||||||
cursor: pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-white .swiper-pagination-bullet {
|
|
||||||
background: #fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-bullet-active {
|
|
||||||
opacity: 1;
|
|
||||||
background: #007aff
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-white .swiper-pagination-bullet-active {
|
|
||||||
background: #fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-black .swiper-pagination-bullet-active {
|
|
||||||
background: #000
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-vertical>.swiper-pagination-bullets {
|
|
||||||
right: 10px;
|
|
||||||
top: 50%;
|
|
||||||
-webkit-transform: translate3d(0,-50%,0);
|
|
||||||
-moz-transform: translate3d(0,-50%,0);
|
|
||||||
-o-transform: translate(0,-50%);
|
|
||||||
-ms-transform: translate3d(0,-50%,0);
|
|
||||||
transform: translate3d(0,-50%,0)
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-vertical>.swiper-pagination-bullets .swiper-pagination-bullet {
|
|
||||||
margin: 5px 0;
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-horizontal>.swiper-pagination-bullets .swiper-pagination-bullet {
|
|
||||||
margin: 0 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-progress {
|
|
||||||
background: rgba(0,0,0,.25);
|
|
||||||
position: absolute
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-progress .swiper-pagination-progressbar {
|
|
||||||
background: #007aff;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
-webkit-transform: scale(0);
|
|
||||||
-ms-transform: scale(0);
|
|
||||||
-o-transform: scale(0);
|
|
||||||
transform: scale(0);
|
|
||||||
-webkit-transform-origin: left top;
|
|
||||||
-moz-transform-origin: left top;
|
|
||||||
-ms-transform-origin: left top;
|
|
||||||
-o-transform-origin: left top;
|
|
||||||
transform-origin: left top
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-rtl .swiper-pagination-progress .swiper-pagination-progressbar {
|
|
||||||
-webkit-transform-origin: right top;
|
|
||||||
-moz-transform-origin: right top;
|
|
||||||
-ms-transform-origin: right top;
|
|
||||||
-o-transform-origin: right top;
|
|
||||||
transform-origin: right top
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-horizontal>.swiper-pagination-progress {
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
left: 0;
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-vertical>.swiper-pagination-progress {
|
|
||||||
width: 4px;
|
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-progress.swiper-pagination-white {
|
|
||||||
background: rgba(255,255,255,.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-progress.swiper-pagination-white .swiper-pagination-progressbar {
|
|
||||||
background: #fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-pagination-progress.swiper-pagination-black .swiper-pagination-progressbar {
|
|
||||||
background: #000
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d {
|
|
||||||
-webkit-perspective: 1200px;
|
|
||||||
-moz-perspective: 1200px;
|
|
||||||
-o-perspective: 1200px;
|
|
||||||
perspective: 1200px
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d .swiper-cube-shadow,.swiper-container-3d .swiper-slide,.swiper-container-3d .swiper-slide-shadow-bottom,.swiper-container-3d .swiper-slide-shadow-left,.swiper-container-3d .swiper-slide-shadow-right,.swiper-container-3d .swiper-slide-shadow-top,.swiper-container-3d .swiper-wrapper {
|
|
||||||
-webkit-transform-style: preserve-3d;
|
|
||||||
-moz-transform-style: preserve-3d;
|
|
||||||
-ms-transform-style: preserve-3d;
|
|
||||||
transform-style: preserve-3d
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d .swiper-slide-shadow-bottom,.swiper-container-3d .swiper-slide-shadow-left,.swiper-container-3d .swiper-slide-shadow-right,.swiper-container-3d .swiper-slide-shadow-top {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d .swiper-slide-shadow-left {
|
|
||||||
background-image: -webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,0)));
|
|
||||||
background-image: -webkit-linear-gradient(right,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -moz-linear-gradient(right,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -o-linear-gradient(right,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: linear-gradient(to left,rgba(0,0,0,.5),rgba(0,0,0,0))
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d .swiper-slide-shadow-right {
|
|
||||||
background-image: -webkit-gradient(linear,right top,left top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,0)));
|
|
||||||
background-image: -webkit-linear-gradient(left,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -moz-linear-gradient(left,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -o-linear-gradient(left,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: linear-gradient(to right,rgba(0,0,0,.5),rgba(0,0,0,0))
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d .swiper-slide-shadow-top {
|
|
||||||
background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,.5)),to(rgba(0,0,0,0)));
|
|
||||||
background-image: -webkit-linear-gradient(bottom,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -moz-linear-gradient(bottom,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -o-linear-gradient(bottom,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: linear-gradient(to top,rgba(0,0,0,.5),rgba(0,0,0,0))
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-3d .swiper-slide-shadow-bottom {
|
|
||||||
background-image: -webkit-gradient(linear,left bottom,left top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,0)));
|
|
||||||
background-image: -webkit-linear-gradient(top,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -moz-linear-gradient(top,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: -o-linear-gradient(top,rgba(0,0,0,.5),rgba(0,0,0,0));
|
|
||||||
background-image: linear-gradient(to bottom,rgba(0,0,0,.5),rgba(0,0,0,0))
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-coverflow .swiper-wrapper,.swiper-container-flip .swiper-wrapper {
|
|
||||||
-ms-perspective: 1200px
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube,.swiper-container-flip {
|
|
||||||
overflow: visible
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-slide,.swiper-container-flip .swiper-slide {
|
|
||||||
pointer-events: none;
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
-moz-backface-visibility: hidden;
|
|
||||||
-ms-backface-visibility: hidden;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
z-index: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-slide .swiper-slide,.swiper-container-flip .swiper-slide .swiper-slide {
|
|
||||||
pointer-events: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-slide-active,.swiper-container-cube .swiper-slide-active .swiper-slide-active,.swiper-container-flip .swiper-slide-active,.swiper-container-flip .swiper-slide-active .swiper-slide-active {
|
|
||||||
pointer-events: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-slide-shadow-bottom,.swiper-container-cube .swiper-slide-shadow-left,.swiper-container-cube .swiper-slide-shadow-right,.swiper-container-cube .swiper-slide-shadow-top,.swiper-container-flip .swiper-slide-shadow-bottom,.swiper-container-flip .swiper-slide-shadow-left,.swiper-container-flip .swiper-slide-shadow-right,.swiper-container-flip .swiper-slide-shadow-top {
|
|
||||||
z-index: 0;
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
-moz-backface-visibility: hidden;
|
|
||||||
-ms-backface-visibility: hidden;
|
|
||||||
backface-visibility: hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-slide {
|
|
||||||
visibility: hidden;
|
|
||||||
-webkit-transform-origin: 0 0;
|
|
||||||
-moz-transform-origin: 0 0;
|
|
||||||
-ms-transform-origin: 0 0;
|
|
||||||
transform-origin: 0 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube.swiper-container-rtl .swiper-slide {
|
|
||||||
-webkit-transform-origin: 100% 0;
|
|
||||||
-moz-transform-origin: 100% 0;
|
|
||||||
-ms-transform-origin: 100% 0;
|
|
||||||
transform-origin: 100% 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-slide-active,.swiper-container-cube .swiper-slide-next,.swiper-container-cube .swiper-slide-next+.swiper-slide,.swiper-container-cube .swiper-slide-prev {
|
|
||||||
pointer-events: auto;
|
|
||||||
visibility: visible
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-cube .swiper-cube-shadow {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: #000;
|
|
||||||
opacity: .6;
|
|
||||||
-webkit-filter: blur(50px);
|
|
||||||
filter: blur(50px);
|
|
||||||
z-index: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-fade.swiper-container-free-mode .swiper-slide {
|
|
||||||
-webkit-transition-timing-function: ease-out;
|
|
||||||
-moz-transition-timing-function: ease-out;
|
|
||||||
-ms-transition-timing-function: ease-out;
|
|
||||||
-o-transition-timing-function: ease-out;
|
|
||||||
transition-timing-function: ease-out
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-fade .swiper-slide {
|
|
||||||
pointer-events: none;
|
|
||||||
-webkit-transition-property: opacity;
|
|
||||||
-moz-transition-property: opacity;
|
|
||||||
-o-transition-property: opacity;
|
|
||||||
transition-property: opacity
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-fade .swiper-slide .swiper-slide {
|
|
||||||
pointer-events: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-fade .swiper-slide-active,.swiper-container-fade .swiper-slide-active .swiper-slide-active {
|
|
||||||
pointer-events: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-zoom-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -moz-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
-moz-box-pack: center;
|
|
||||||
-ms-flex-pack: center;
|
|
||||||
-webkit-justify-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-moz-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-zoom-container>canvas,.swiper-zoom-container>img,.swiper-zoom-container>svg {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
object-fit: contain
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-scrollbar {
|
|
||||||
border-radius: 10px;
|
|
||||||
position: relative;
|
|
||||||
-ms-touch-action: none;
|
|
||||||
background: rgba(0,0,0,.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-horizontal>.swiper-scrollbar {
|
|
||||||
position: absolute;
|
|
||||||
left: 1%;
|
|
||||||
bottom: 3px;
|
|
||||||
z-index: 50;
|
|
||||||
height: 5px;
|
|
||||||
width: 98%
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-container-vertical>.swiper-scrollbar {
|
|
||||||
position: absolute;
|
|
||||||
right: 3px;
|
|
||||||
top: 1%;
|
|
||||||
z-index: 50;
|
|
||||||
width: 5px;
|
|
||||||
height: 98%
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-scrollbar-drag {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
background: rgba(0,0,0,.5);
|
|
||||||
border-radius: 10px;
|
|
||||||
left: 0;
|
|
||||||
top: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-scrollbar-cursor-drag {
|
|
||||||
cursor: move
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-lazy-preloader {
|
|
||||||
width: 42px;
|
|
||||||
height: 42px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
margin-left: -21px;
|
|
||||||
margin-top: -21px;
|
|
||||||
z-index: 10;
|
|
||||||
-webkit-transform-origin: 50%;
|
|
||||||
-moz-transform-origin: 50%;
|
|
||||||
transform-origin: 50%;
|
|
||||||
-webkit-animation: swiper-preloader-spin 1s steps(12,end) infinite;
|
|
||||||
-moz-animation: swiper-preloader-spin 1s steps(12,end) infinite;
|
|
||||||
animation: swiper-preloader-spin 1s steps(12,end) infinite
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-lazy-preloader:after {
|
|
||||||
display: block;
|
|
||||||
content: "";
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
|
|
||||||
background-position: 50%;
|
|
||||||
-webkit-background-size: 100%;
|
|
||||||
background-size: 100%;
|
|
||||||
background-repeat: no-repeat
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-lazy-preloader-white:after {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E")
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes swiper-preloader-spin {
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes swiper-preloader-spin {
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||