Browse Source

代码重构

dev-V1.0.1
庄锐波 5 months ago
parent
commit
792360dfea
  1. 4
      ruoyi-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/LoginController.java
  2. 130
      ruoyi-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/supervision/AppSupLoginController.java
  3. 10
      zdxt-web-client/zdxt-enforcement-code-app/src/api/supervision/index.js
  4. 2
      zdxt-web-client/zdxt-enforcement-code-app/src/common/globalExpandRouter.js
  5. 23
      zdxt-web-client/zdxt-enforcement-code-app/src/router/supervision-app/sup-main-router.js
  6. 14
      zdxt-web-client/zdxt-enforcement-code-app/src/views/enterprise/login.vue
  7. 129
      zdxt-web-client/zdxt-enforcement-code-app/src/views/supervision/login.vue
  8. 822
      zdxt-web-client/zdxt-enforcement-code-app/src/views/supervision/supervisionHomeV2.vue
  9. 2
      zdxt-web-client/zdxt-enforcement-code-app/vite-env/.env.supervision-dev
  10. 12
      zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/ResetUserPasswordTest.java

4
ruoyi-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/LoginController.java

@ -0,0 +1,4 @@
package com.zdxt.enforcementcode.controller.app;
public class LoginController {
}

130
ruoyi-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/supervision/AppSupLoginController.java

@ -1,12 +1,33 @@
package com.zdxt.enforcementcode.controller.app.supervision;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysDeptService;
import org.dromara.system.service.ISysUserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* 企业端-用户登录
* 监督-用户登录
*/
@Slf4j
@RequiredArgsConstructor
@ -14,9 +35,116 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/app-sup/login")
public class AppSupLoginController {
private final ISysUserService sysUserService;
private final ISysDeptService sysDeptService;
/**
* AES密钥与前端一致
*/
private static final String AES_KEY = "1234567898745621";
/**
* 监督端用户登录
*
* @param params 包含 secretName用户名 secretKeyAES加密后的密码
* @return 登录结果token + 用户信息
*/
@SaIgnore
@PostMapping("/do")
public R<Map<String, Object>> validate(@RequestBody Map<String, String> params) {
String secretName = params.get("secretName");
String secretKey = params.get("secretKey");
if (secretName == null || secretName.isBlank()) {
return R.fail("用户名不能为空");
}
if (secretKey == null || secretKey.isBlank()) {
return R.fail("密码不能为空");
}
// 1. 根据用户名查询 sys_user 表
SysUserVo user = sysUserService.selectUserByUserName(secretName);
if (user == null) {
return R.fail("用户不存在");
}
// 2. 检查用户状态(0正常 1停用)
if ("1".equals(user.getStatus())) {
return R.fail("该用户已被停用");
}
// 3. AES解密前端传来的密码
String decryptedPassword;
try {
decryptedPassword = decryptByAes(secretKey);
} catch (Exception e) {
log.error("密码解密失败", e);
return R.fail("密码解密失败");
}
// 4. MD5加盐校验密码: MD5(username + password + salt)
String md5Password = DigestUtil.md5Hex(user.getUserName() + decryptedPassword + user.getSalt());
if (!md5Password.equals(user.getPassword())) {
return R.fail("用户名或密码错误");
}
// 5. 查询部门信息
String deptName = "";
if (user.getDeptId() != null && !user.getDeptId().isBlank()) {
SysDeptVo dept = sysDeptService.selectDeptById(user.getDeptId());
if (dept != null) {
deptName = dept.getDeptName();
}
}
// 6. 构建LoginUser并生成SaToken
LoginUser loginUser = new LoginUser();
loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType("supervision_user");
loginUser.setDeptId(user.getDeptId());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType("supervision_app");
model.setExtra("clientid", "supervision");
LoginHelper.login(loginUser, model);
// 7. 构造返回数据
Map<String, Object> data = new HashMap<>();
data.put("token", StpUtil.getTokenValue());
data.put("userId", user.getUserId());
data.put("userName", user.getUserName());
data.put("nickName", user.getNickName());
data.put("phonenumber", user.getPhonenumber() != null ? user.getPhonenumber() : "");
data.put("deptId", user.getDeptId() != null ? user.getDeptId() : "");
data.put("deptName", deptName);
return R.ok(data);
}
/**
* AES/CBC/ZeroPadding 解密与前端CryptoJS对应
* 前端使用 CryptoJS.pad.ZeroPaddingJava中用NoPadding后手动去除尾部0字节
*/
private String decryptByAes(String encryptedBase64) throws Exception {
byte[] keyBytes = AES_KEY.getBytes(StandardCharsets.UTF_8);
byte[] ivBytes = AES_KEY.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = Base64.getDecoder().decode(encryptedBase64);
byte[] decrypted = cipher.doFinal(encrypted);
// 去除ZeroPadding尾部的0字节
int len = decrypted.length;
while (len > 0 && decrypted[len - 1] == 0) {
len--;
}
return new String(decrypted, 0, len, StandardCharsets.UTF_8);
}
}

10
zdxt-web-client/zdxt-enforcement-code-app/src/api/supervision/index.js

@ -0,0 +1,10 @@
import { _get, _post, _form, _delete } from "../../http/index";
// region 监督端接口
let supervisionAppBaseUrl = "/app-sup";
// 监督端用户登录
export const supervisionLogin = (params, success, error) => _post(supervisionAppBaseUrl + "/login/do", params, success, error);
// endregion

2
zdxt-web-client/zdxt-enforcement-code-app/src/common/globalExpandRouter.js

@ -4,7 +4,7 @@ const $globalRouterModules = (routes) => {
let pathArr = [];
let nameArr = [];
const modulesRouter = import.meta.glob(
["../router/enterprise-app/ent-main-router.js", "../router/demo/demo-main-router.js", "../router/enforcement-app/enf-main-router.js"]
["../router/enterprise-app/ent-main-router.js", "../router/demo/demo-main-router.js", "../router/enforcement-app/enf-main-router.js", "../router/supervision-app/sup-main-router.js"]
,{
eager: true,
});

23
zdxt-web-client/zdxt-enforcement-code-app/src/router/supervision-app/sup-main-router.js

@ -0,0 +1,23 @@
const routes = [
// 监督端登录
{
path: "/supervision/login",
name: "supervisionLogin",
component: () => import("@/views/supervision/login.vue"),
meta: {
isKeepAlive: false,
},
},
// 监督端首页
{
path: "/supervision/supervisionHome",
name: "supervisionHome",
component: () => import("@/views/supervision/supervisionHomeV2.vue"),
meta: {
isKeepAlive: false,
},
},
];
export {routes};

14
zdxt-web-client/zdxt-enforcement-code-app/src/views/enterprise/login.vue

@ -52,13 +52,13 @@
const showTestPicker = ref(false);
const testEnterprises = [
{ account: 'weil45', name: '深圳农牧美益肉业有限公司', password: '123456' },
{ account: 'DGD440881199506180622', name: '深圳市深水龙岗污水处理有限公司', password: '123456' },
{ account: 'smiledzb1982', name: '深圳康泰生物制品股份有限公司', password: '123456' },
{ account: 'A7271888', name: '深圳市里程食品有限公司', password: '123456' },
{ account: 'qintongzhen138', name: '欧姆龙电子部件(深圳)有限公司', password: '123456' },
{ account: 'XP073215', name: '深圳市强达电路股份有限公司', password: '123456' },
{ account: '33ef1970fd449676fa4bc197', name: '吉田拉链(深圳)有限公司公明工厂', password: '123456' },
{ account: 'weil45', name: '深圳农牧美益肉业有限公司', password: 'szsf@zfjd' },
{ account: 'DGD440881199506180622', name: '深圳市深水龙岗污水处理有限公司', password: 'szsf@zfjd' },
{ account: 'smiledzb1982', name: '深圳康泰生物制品股份有限公司', password: 'szsf@zfjd' },
{ account: 'A7271888', name: '深圳市里程食品有限公司', password: 'szsf@zfjd' },
{ account: 'qintongzhen138', name: '欧姆龙电子部件(深圳)有限公司', password: 'szsf@zfjd' },
{ account: 'XP073215', name: '深圳市强达电路股份有限公司', password: 'szsf@zfjd' },
{ account: '33ef1970fd449676fa4bc197', name: '吉田拉链(深圳)有限公司公明工厂', password: 'szsf@zfjd' },
];
const selectTestEnterprise = (item) => {

129
zdxt-web-client/zdxt-enforcement-code-app/src/views/supervision/login.vue

@ -1,11 +1,134 @@
<template>
<div class="login-page">
<div class="login-content">
<van-nav-bar>
<template #title>
<div class="title">
监督端登录
</div>
</template>
</van-nav-bar>
<van-form @submit="onSubmit">
<van-field v-model="loginParam.secretName" name="secretName" label="用户名" placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]"/>
<van-field v-model="loginParam.secretKey" type="password" name="secretKey" label="密码" placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"/>
<div style="margin: 0.42rem">
<van-button block type="primary" native-type="submit">登录</van-button>
</div>
</van-form>
<div style="margin: 0.42rem 0.42rem 0; text-align: center;">
<van-button plain type="default" size="small" @click="showTestPicker = true" style="background-color: darkred;color: white">选择测试用户</van-button>
</div>
<van-action-sheet v-model:show="showTestPicker" title="选择测试用户">
<div class="test-enterprise-list">
<van-cell
v-for="(item, index) in testUsers"
:key="index"
:title="item.name"
:label="'账号:' + item.account"
is-link
@click="selectTestUser(item)"
/>
</div>
</van-action-sheet>
</div>
</div>
</template>
<script setup>
import {reactive, ref} from 'vue';
import {supervisionLogin} from '../../api/supervision';
import CryptoJS from "crypto-js";
import {showFailToast} from 'vant';
import 'vant/es/toast/style';
const {useCommon} = $globalStore;
const router = useRouter();
const loginParam = reactive({
secretName: "",
secretKey: ""
})
const showTestPicker = ref(false);
const testUsers = [
{ account: 'admin', name: '管理员', password: '123456' },
];
const selectTestUser = (item) => {
loginParam.secretName = item.account;
loginParam.secretKey = item.password;
showTestPicker.value = false;
//
doLogin(item.account, item.password);
}
const doLogin = (secretName, secretKey) => {
const encryptedKey = Encrypt(secretKey);
supervisionLogin({"secretName": secretName, "secretKey": encryptedKey}, response => {
if (response.code === 200) {
//
useCommon.SET_USER_PHONENUMBER(response.data.phonenumber.toString());
useCommon.SET_USER_ID(response.data.userId);
useCommon.SET_TOKEN(response.data.token);
useCommon.SET_ORG_ID(response.data.deptId || "");
console.log("supervision login success", useCommon.userId);
router.push({name: "supervisionHome"});
} else {
showFailToast(response.msg || "登录失败");
}
}, error => {
showFailToast(error?.msg || "登录失败,请检查网络");
})
};
const onSubmit = async () => {
doLogin(loginParam.secretName, loginParam.secretKey);
};
const Encrypt = (passWord) => {
var keyStr = '1234567898745621';
var ivStr = keyStr;
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
var srcs = CryptoJS.enc.Utf8.parse(passWord);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
</script>
<template>
<style lang="less" scoped>
.login-page {
width: 100%;
height: 100%;
display: flex;
overflow: hidden;
background-image: url("~@/assets/images/login.jpg");
background-repeat: no-repeat;
background-size: cover;
opacity: 0.9;
</template>
.login-content {
margin: auto;
padding: 0.27rem 0.27rem 0.52rem 0.27rem;
background-color: #fff;
border-radius: 0.27rem;
box-shadow: 0 0 0.27rem rgba(0, 0, 0, 0.2);
opacity: 0.9;
<style scoped lang="less">
.title {
font-size: 0.52rem;
font-weight: 600;
}
}
}
.test-enterprise-list {
max-height: 60vh;
overflow-y: auto;
padding: 0.2rem;
}
</style>

822
zdxt-web-client/zdxt-enforcement-code-app/src/views/supervision/supervisionHomeV2.vue

@ -0,0 +1,822 @@
<template>
<div class="home app_content">
<!-- 顶部蓝色背景区域 -->
<div class="header-bg">
<div class="orbit-line"></div>
<div class="orbit-line"></div>
<div class="cityscape"></div>
<!-- 盾牌图标 -->
<div class="shield-wrapper">
<div class="shield">
<div class="shield-icon">
<div class="shield-text">执法<br>监督</div>
</div>
</div>
</div>
<!-- 监督端标签 -->
<div class="supervision-tag">监督端</div>
<!-- 标语 -->
<div class="slogan">
<span class="white-text">执法受监督</span>&nbsp;
<span class="gold-text">入企须扫码</span>
</div>
</div>
<!-- 内容区域 -->
<div class="content-area">
<!-- 通知栏 -->
<div class="notice-bar" v-if="noticeVisible">
<div class="notice-icon-wrap">
<svg class="notice-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
</div>
<span class="notice-text">深圳市司法局2026年第二季度行政执法监督计划</span>
<div class="notice-close" @click="noticeVisible = false">
<svg viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</div>
</div>
<!-- 轮播图 -->
<div class="banner-card">
<van-swipe :autoplay="3000" indicator-color="rgba(255,255,255,0.9)" :loop="true">
<van-swipe-item v-for="(item, index) in bannerList" :key="index">
<div class="banner-inner">
<img v-if="item.image" :src="item.image" class="banner-img" alt="" />
<div v-else class="banner-placeholder">
<div class="banner-text-group">
<span class="banner-year">{{ item.year }}</span>
<span class="banner-subtitle">{{ item.subtitle }}</span>
</div>
<div class="banner-main-title">{{ item.title }}</div>
<div class="banner-tag" v-if="item.tag">{{ item.tag }}</div>
</div>
</div>
</van-swipe-item>
</van-swipe>
</div>
<!-- 执法监督 + 投诉举报 双卡片行 -->
<div class="card-row">
<!-- 执法监督 -->
<div class="scan-card">
<div class="scan-card-label">执法监督</div>
<div class="scan-card-body">
<div class="scan-card-icon">
<div class="scan-icon-main">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.35-4.35"/>
<line x1="11" y1="8" x2="11" y2="14"/>
<line x1="8" y1="11" x2="14" y2="11"/>
</svg>
</div>
<div class="scan-icon-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
</div>
<div class="scan-card-text">监督检查</div>
</div>
<!-- 背景装饰 -->
<div class="scan-card-bg-decor">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.35-4.35"/>
</svg>
</div>
</div>
<!-- 投诉举报 -->
<div class="scan-card scan-card--orange">
<div class="scan-card-label scan-card-label--orange">投诉举报</div>
<div class="scan-card-body">
<div class="scan-card-icon">
<div class="scan-icon-main scan-icon-main--orange">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M4 4h16v12H5.17L4 17.17V4z" />
<line x1="8" y1="9" x2="16" y2="9"/>
<line x1="8" y1="12" x2="13" y2="12"/>
</svg>
</div>
<div class="scan-icon-check scan-icon-check--orange">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
</div>
<div class="scan-card-text">投诉举报</div>
</div>
<!-- 背景装饰 -->
<div class="scan-card-bg-decor scan-card-bg-decor--orange">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M4 4h16v12H5.17L4 17.17V4z" />
<line x1="8" y1="9" x2="16" y2="9"/>
</svg>
</div>
</div>
</div>
<!-- 监督记录 + 统计分析 双卡片行 -->
<div class="card-row">
<!-- 监督记录 -->
<div class="info-card info-card--teal">
<div class="info-card-icon">
<div class="info-icon-main info-icon-main--teal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<line x1="7" y1="8" x2="17" y2="8"/>
<line x1="7" y1="12" x2="14" y2="12"/>
<line x1="7" y1="16" x2="11" y2="16"/>
</svg>
</div>
<div class="info-icon-check info-icon-check--teal">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
</div>
<div class="info-card-text">监督记录</div>
<!-- 背景装饰 -->
<div class="info-card-bg-decor">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<line x1="7" y1="8" x2="17" y2="8"/>
<line x1="7" y1="12" x2="14" y2="12"/>
</svg>
</div>
</div>
<!-- 统计分析 -->
<div class="info-card info-card--green">
<div class="info-card-icon">
<div class="info-icon-main info-icon-main--green">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<rect x="3" y="4" width="18" height="16" rx="2"/>
<line x1="7" y1="16" x2="7" y2="10"/>
<line x1="11" y1="16" x2="11" y2="8"/>
<line x1="15" y1="16" x2="15" y2="12"/>
<path d="M6 11 Q9 7 12 9 Q15 11 18 7"/>
</svg>
</div>
<div class="info-icon-check info-icon-check--green">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
</div>
<div class="info-card-text">统计分析</div>
<!-- 背景装饰 -->
<div class="info-card-bg-decor info-card-bg-decor--green">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="3" y="4" width="18" height="16" rx="2"/>
<line x1="7" y1="16" x2="7" y2="10"/>
<line x1="11" y1="16" x2="11" y2="8"/>
<line x1="15" y1="16" x2="15" y2="12"/>
</svg>
</div>
</div>
</div>
</div>
<!-- 底部导航栏 -->
<div class="footer-bar">
<div class="footer-item footer-item--active">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
<span>首页</span>
</div>
<div class="footer-item">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>
<span>查询</span>
</div>
<div class="footer-item">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/></svg>
<span>消息</span>
</div>
<div class="footer-item">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
<span>我的</span>
</div>
</div>
</div>
</template>
<script setup name="supervisionHome">
const noticeVisible = ref(true);
// ========== ==========
const bannerList = ref([
{ year: '2026', subtitle: '法治课堂', title: '潮起深圳 领航出海', tag: '4月活动预告', image: '', link: '' },
{ year: '2026', subtitle: '执法监督', title: '阳光执法 规范行为', tag: '', image: '', link: '' },
{ year: '2026', subtitle: '营商环境', title: '优化服务 提质增效', tag: '', image: '', link: '' },
]);
</script>
<style lang="less" scoped>
.home {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
background: #f0f6ff;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
/* ===== 顶部蓝色背景区域 ===== */
.header-bg {
background: linear-gradient(180deg, #2b7de9 0%, #5ba3f5 40%, #a0cdf8 70%, #dceefb 90%, #eef5ff 100%);
padding: 20px 20px 20px;
text-align: center;
position: relative;
overflow: hidden;
flex-shrink: 0;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 80px;
background: linear-gradient(180deg, transparent, rgba(255,255,255,0.3));
}
}
/* 城市天际线装饰 */
.cityscape {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 120px;
opacity: 0.15;
background: linear-gradient(0deg, rgba(255,255,255,0.5) 0%, transparent 100%);
&::before {
content: '';
position: absolute;
bottom: 0;
left: 5%;
width: 8%;
height: 60px;
background: rgba(255,255,255,0.4);
border-radius: 2px 2px 0 0;
}
&::after {
content: '';
position: absolute;
bottom: 0;
right: 15%;
width: 6%;
height: 100px;
background: rgba(255,255,255,0.3);
border-radius: 2px 2px 0 0;
box-shadow:
-30px 20px 0 rgba(255,255,255,0.25),
-55px 35px 0 rgba(255,255,255,0.2),
30px 40px 0 rgba(255,255,255,0.2),
60px 15px 0 rgba(255,255,255,0.15);
}
}
/* 轨道线装饰 */
.orbit-line {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 120px;
border: 1.5px solid rgba(255,255,255,0.2);
border-radius: 50%;
z-index: 0;
&:nth-child(2) {
width: 260px;
height: 155px;
border-color: rgba(255,255,255,0.12);
}
}
/* 盾牌图标 */
.shield-wrapper {
position: relative;
z-index: 1;
margin-bottom: 8px;
}
.shield {
display: inline-block;
width: 90px;
height: 100px;
position: relative;
}
.shield-icon {
width: 90px;
height: 100px;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
clip-path: polygon(50% 0%, 100% 20%, 100% 65%, 50% 100%, 0% 65%, 0% 20%);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: white;
font-size: 18px;
font-weight: bold;
letter-spacing: 4px;
text-shadow: 0 1px 3px rgba(0,0,0,0.2);
&::before {
content: '';
position: absolute;
inset: 3px;
background: linear-gradient(135deg, #60a5fa 0%, #2563eb 100%);
clip-path: polygon(50% 2%, 98% 21%, 98% 64%, 50% 98%, 2% 64%, 2% 21%);
z-index: -1;
}
}
.shield-text {
line-height: 1.3;
margin-top: 12px;
}
/* 监督端标签 */
.supervision-tag {
display: inline-block;
background: linear-gradient(135deg, #2563eb, #1d4ed8);
color: white;
padding: 5px 22px;
border-radius: 4px;
font-size: 15px;
font-weight: 500;
letter-spacing: 2px;
margin: 6px 0 12px;
position: relative;
z-index: 1;
}
/* 标语 */
.slogan {
position: relative;
z-index: 1;
font-size: 22px;
font-weight: bold;
letter-spacing: 3px;
.white-text {
color: #ffffff;
text-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
.gold-text {
color: #fbbf24;
text-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
}
/* ===== 内容区域 ===== */
.content-area {
padding: 0 16px;
margin-top: -10px;
position: relative;
z-index: 2;
flex: 1;
overflow-y: auto;
padding-bottom: 16px;
}
/* 通知栏 */
.notice-bar {
background: #ffffff;
border-radius: 12px;
padding: 14px 16px;
margin-bottom: 14px;
display: flex;
align-items: center;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
.notice-icon-wrap {
width: 40px;
height: 40px;
min-width: 40px;
background: linear-gradient(135deg, #e0edff, #c8ddf8);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.notice-icon {
width: 22px;
height: 22px;
color: #3b82f6;
}
.notice-text {
flex: 1;
font-size: 14px;
color: #333;
line-height: 1.4;
}
.notice-close {
width: 24px;
height: 24px;
min-width: 24px;
display: flex;
align-items: center;
justify-content: center;
color: #c0c0c0;
margin-left: 8px;
cursor: pointer;
svg {
width: 16px;
height: 16px;
}
}
}
/* 轮播图 */
.banner-card {
border-radius: 16px;
overflow: hidden;
margin-bottom: 14px;
position: relative;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}
.banner-inner {
width: 100%;
height: 180px;
background: linear-gradient(135deg, #c8e2f8 0%, #a0c8f0 30%, #7bb3e8 60%, #5a9de0 100%);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.banner-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.banner-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 24px;
.banner-text-group {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 6px;
}
.banner-year {
font-size: 28px;
font-weight: 900;
color: #d97706;
letter-spacing: 1px;
}
.banner-subtitle {
font-size: 18px;
font-weight: 600;
color: #d97706;
letter-spacing: 2px;
}
.banner-main-title {
font-size: 24px;
color: #1a1a2e;
font-weight: 800;
letter-spacing: 4px;
margin-bottom: 10px;
}
.banner-tag {
display: inline-block;
background: linear-gradient(135deg, #f59e0b, #d97706);
color: #fff;
padding: 4px 16px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
align-self: center;
}
}
:deep(.van-swipe__indicator) {
width: 8px;
height: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.5);
transition: all 0.3s;
}
:deep(.van-swipe__indicator--active) {
width: 20px;
background: rgba(255, 255, 255, 0.9);
}
/* ===== 卡片行 ===== */
.card-row {
display: flex;
gap: 12px;
margin-bottom: 14px;
}
/* 功能卡片(蓝色默认) */
.scan-card {
flex: 1;
background: linear-gradient(135deg, #eaf3ff 0%, #d6eaff 60%, #c8e0f8 100%);
border-radius: 16px;
padding: 0 16px 18px;
position: relative;
overflow: hidden;
min-height: 150px;
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
.scan-card-label {
display: inline-block;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: #fff;
font-size: 12px;
font-weight: 500;
padding: 4px 12px;
border-radius: 0 0 8px 8px;
margin-bottom: 12px;
letter-spacing: 1px;
}
.scan-card-body {
position: relative;
z-index: 1;
}
.scan-card-icon {
width: 60px;
height: 60px;
position: relative;
margin-bottom: 10px;
}
.scan-icon-main {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #3b82f6, #2563eb);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(37,99,235,0.3);
svg {
width: 32px;
height: 32px;
color: white;
}
}
.scan-icon-check {
position: absolute;
bottom: -4px;
right: -4px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #60a5fa, #3b82f6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #eaf3ff;
svg {
width: 12px;
height: 12px;
color: white;
}
}
.scan-card-text {
font-size: 15px;
color: #1a1a2e;
font-weight: 600;
}
.scan-card-bg-decor {
position: absolute;
bottom: 8px;
right: 0;
width: 80px;
height: 80px;
opacity: 0.1;
color: #3b82f6;
svg {
width: 100%;
height: 100%;
}
}
}
/* 橙色卡片变体(投诉举报) */
.scan-card--orange {
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 60%, #fed7aa 100%);
.scan-card-label--orange {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.scan-icon-main--orange {
background: linear-gradient(135deg, #f59e0b, #d97706);
box-shadow: 0 4px 12px rgba(217,119,6,0.3);
}
.scan-icon-check--orange {
background: linear-gradient(135deg, #fbbf24, #f59e0b);
border-color: #fff7ed;
}
.scan-card-bg-decor--orange {
color: #d97706;
}
}
/* 信息卡片(监督记录 / 统计分析) */
.info-card {
flex: 1;
border-radius: 16px;
padding: 18px 16px;
position: relative;
overflow: hidden;
min-height: 130px;
box-shadow: 0 2px 12px rgba(0,0,0,0.05);
.info-card-icon {
position: relative;
width: 60px;
height: 60px;
margin-bottom: 10px;
}
.info-card-text {
font-size: 15px;
color: #1a1a2e;
font-weight: 600;
position: relative;
z-index: 1;
}
.info-card-bg-decor {
position: absolute;
bottom: 8px;
right: 0;
width: 80px;
height: 80px;
opacity: 0.1;
color: #0d9488;
svg {
width: 100%;
height: 100%;
}
}
}
/* 青色(监督记录) */
.info-card--teal {
background: linear-gradient(135deg, #e8faf7 0%, #ccf0ea 60%, #b2e6de 100%);
}
.info-icon-main--teal {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #14b8a6, #0d9488);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(13,148,136,0.3);
svg {
width: 32px;
height: 32px;
color: white;
}
}
.info-icon-check--teal {
position: absolute;
bottom: -4px;
right: -4px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #2dd4bf, #14b8a6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #e8faf7;
svg {
width: 12px;
height: 12px;
color: white;
}
}
/* 绿色(统计分析) */
.info-card--green {
background: linear-gradient(135deg, #e8faf0 0%, #c6f0da 60%, #a8e6c8 100%);
}
.info-icon-main--green {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #10b981, #059669);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(5,150,105,0.3);
svg {
width: 32px;
height: 32px;
color: white;
}
}
.info-icon-check--green {
position: absolute;
bottom: -4px;
right: -4px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #34d399, #10b981);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #e8faf0;
svg {
width: 12px;
height: 12px;
color: white;
}
}
.info-card-bg-decor--green {
color: #059669;
}
/* ===== 底部导航栏 ===== */
.footer-bar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-around;
background: #fff;
padding: 8px 0;
padding-bottom: calc(8px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 12px rgba(0,0,0,0.06);
.footer-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
color: #999;
font-size: 11px;
svg {
width: 24px;
height: 24px;
}
&--active {
color: #3b82f6;
}
}
}
</style>

2
zdxt-web-client/zdxt-enforcement-code-app/vite-env/.env.supervision-dev

@ -1,4 +1,4 @@
# 监督端开发环境
VITE_PORT = 12003
VITE_BASE_URL = 'http://localhost:17000/sup-ep'
VITE_DEFAULT_LOGIN = '/enforce/login'
VITE_DEFAULT_LOGIN = '/supervision/login'

12
zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/ResetUserPasswordTest.java

@ -56,7 +56,7 @@ public class ResetUserPasswordTest {
// 2. 根据ID列表查询企业用户
List<EnterpriseUser> userList = enterpriseUserMapper.selectList(
new LambdaQueryWrapper<EnterpriseUser>()
.select(EnterpriseUser::getUserId, EnterpriseUser::getUserName, EnterpriseUser::getSalt)
.select(EnterpriseUser::getUserId, EnterpriseUser::getLoginName, EnterpriseUser::getUserName, EnterpriseUser::getSalt)
.in(EnterpriseUser::getUserId, userIds)
);
@ -66,11 +66,11 @@ public class ResetUserPasswordTest {
int skipCount = 0;
for (EnterpriseUser user : userList) {
String userName = user.getUserName();
String loginName = user.getLoginName();
String salt = user.getSalt();
if (userName == null || userName.isBlank()) {
System.out.println("跳过: userId=" + user.getUserId() + ", userName为空");
if (loginName == null || loginName.isBlank()) {
System.out.println("跳过: userId=" + user.getUserId() + ", loginName为空");
skipCount++;
continue;
}
@ -80,8 +80,8 @@ public class ResetUserPasswordTest {
continue;
}
// 3. 按规则生成新密码: MD5(userName + newPassword + salt)
String newMd5Password = DigestUtil.md5Hex(userName + NEW_PASSWORD + salt);
// 3. 按规则生成新密码: MD5(loginName + newPassword + salt)
String newMd5Password = DigestUtil.md5Hex(loginName + NEW_PASSWORD + salt);
// 4. 更新密码
int rows = enterpriseUserMapper.update(null,

Loading…
Cancel
Save