数据填报首页

This commit is contained in:
王兴凯 2026-04-21 14:42:10 +08:00
parent abc91840ab
commit 58b8cebc49
10 changed files with 1933 additions and 14 deletions

1170
frontend-process/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -14,6 +14,7 @@ import { useTagsViewStore } from "@/store/modules/tagsView";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import {getPath,removePath } from '@/utils/auth';
const url = import.meta.env.VITE_APP_BASE_API; const url = import.meta.env.VITE_APP_BASE_API;
const username = Cookies.get("username"); const username = Cookies.get("username");
@ -35,7 +36,9 @@ function logout() {
tagsViewStore.delAllViews(); tagsViewStore.delAllViews();
}) })
.then(() => { .then(() => {
router.push(`/login?redirect=${route.fullPath}`); // router.push(`/login?redirect=${route.fullPath}`);
router.push(`${getPath()}?redirect=${route.fullPath}`);
removePath()
}); });
}); });
} }

View File

@ -2,7 +2,6 @@ import router from '@/router';
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
import { useUserStoreHook } from '@/store/modules/user'; import { useUserStoreHook } from '@/store/modules/user';
import { usePermissionStoreHook } from '@/store/modules/permission'; import { usePermissionStoreHook } from '@/store/modules/permission';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
NProgress.configure({ showSpinner: false }); NProgress.configure({ showSpinner: false });
@ -10,7 +9,7 @@ NProgress.configure({ showSpinner: false });
const permissionStore = usePermissionStoreHook(); const permissionStore = usePermissionStoreHook();
// 白名单路由 // 白名单路由
const whiteList = ['/login']; const whiteList = ['/login', '/login-sjtb']; //login
// 查找第一个可用路由 // 查找第一个可用路由
function findFirstAvailableRoute(routes: RouteRecordRaw[]): string | undefined { function findFirstAvailableRoute(routes: RouteRecordRaw[]): string | undefined {
@ -36,12 +35,12 @@ router.beforeEach(async (to, from, next) => {
if (userStore.Token) { if (userStore.Token) {
// 登录成功,跳转到首页 // 登录成功,跳转到首页
if (to.path === '/login') { if (to.path === '/login-sjtb' || to.path === '/login') {//login
next({ path: '/' }); next({ path: '/' });
NProgress.done(); NProgress.done();
} else { } else {
const hasGetUserInfo = userStore.roles.length > 0;
const hasGetUserInfo = userStore.roles.length > 0;
if (hasGetUserInfo) { if (hasGetUserInfo) {
// 已获取用户信息,检查路由匹配 // 已获取用户信息,检查路由匹配
if (to.matched.length === 0) { if (to.matched.length === 0) {
@ -81,17 +80,18 @@ router.beforeEach(async (to, from, next) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
await userStore.resetToken(); await userStore.resetToken();
next(`/login?redirect=${to.path}`); to.path === '/login-sjtb' ? next(`/login-sjtb?redirect=${to.path}`) : next(`/login?redirect=${to.path}`);
NProgress.done(); NProgress.done();
} }
} }
} }
} else { } else {
// 未登录可以访问白名单页面 // 未登录可以访问白名单页面
if (whiteList.indexOf(to.path) !== -1) { if (whiteList.indexOf(to.path) !== -1) {
next(); next();
} else { } else {
next(`/login?redirect=${to.path}`); to.path === '/login-sjtb' ? next(`/login-sjtb?redirect=${to.path}`) : next(`/login?redirect=${to.path}`);
NProgress.done(); NProgress.done();
} }
} }

View File

@ -20,6 +20,11 @@ export const constantRoutes: RouteRecordRaw[] = [
path: '/login', path: '/login',
component: () => import('@/views/login/index.vue'), component: () => import('@/views/login/index.vue'),
meta: { hidden: true } meta: { hidden: true }
},
{
path: '/login-sjtb',
component: () => import('@/views/login-sjtb/index.vue'),
meta: { hidden: true }
}, },
{ {
path: '/404', path: '/404',

View File

@ -1,7 +1,7 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
const TokenKey = 'token'; const TokenKey = 'token';
const PathKey = 'path';
export function getToken() { export function getToken() {
return Cookies.get(TokenKey); return Cookies.get(TokenKey);
} }
@ -13,3 +13,12 @@ export function setToken(token: any) {
export function removeToken() { export function removeToken() {
return Cookies.remove(TokenKey); return Cookies.remove(TokenKey);
} }
export function setPath(path:any) {
Cookies.set(PathKey,path);
}
export function getPath() {
return Cookies.get(PathKey);
}
export function removePath() {
return Cookies.remove(PathKey);
}

View File

@ -0,0 +1,724 @@
<template>
<div class="login-container">
<!-- <h1>
<div>
<img :src="loginImg" alt="Logo" class="logo-img" />
<a class="system-title">{{ $t("login.title") }}</a>
</div>
</h1> -->
<div class="login-wrapper">
<!-- 左侧背景图区域 -->
<div class="left-section">
<div class="slogan">
<p>采集网站及数据管理子系统</p>
</div>
</div>
<!-- 右侧登录表单区域 -->
<div class="right-section">
<!-- 忘记密码页面 -->
<div v-if="showForgotPassword" class="forgot-password-container">
<div class="forgot-password-header">
<a-button type="link" @click="backToLogin" class="back-button">
<span> 返回</span>
</a-button>
<!-- <h3 class="forgot-password-title">忘记密码</h3> -->
</div>
<a-form
:model="forgotPasswordForm"
:rules="forgotPasswordRules"
layout="vertical"
class="form-container"
>
<a-form-item label="" name="phone">
<a-input placeholder="请输入手机号" size="large">
<template #prefix>
<MobileOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item label="" name="captcha">
<div class="captcha-row">
<a-input
v-model="forgotPasswordForm.captcha"
placeholder="请输入验证码"
size="large"
class="captcha-input"
>
<template #prefix>
<LockOutlined />
</template>
<template #suffix>
<a-button
type="text"
size="small"
@click="sendForgotPasswordSms"
:disabled="smsButtonDisabled"
:loading="loading"
>
{{
smsCountdown > 0 ? `${smsCountdown}秒后重新获取` : "获取验证码"
}}
</a-button>
</template>
</a-input>
</div>
</a-form-item>
<a-form-item label="" name="newPassword">
<a-input-password
v-model="forgotPasswordForm.newPassword"
placeholder="请输入新密码"
size="large"
>
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
<a-button
type="primary"
size="large"
block
@click="handleResetPassword"
:loading="loading"
>
<span>提交</span>
</a-button>
</a-form>
</div>
<!-- 登录页面 -->
<div v-else>
<!-- Tabs 切换账号登录 / 短信登录 -->
<a-tabs v-model:activeKey="activeTab" class="login-tabs">
<a-tab-pane key="account" tab="账号登录">
<a-form
:model="loginData"
:rules="loginRules"
layout="vertical"
class="form-container"
@finish="onFinish"
>
<!-- 用户名/账号/手机号输入框 -->
<a-form-item label="" name="username">
<a-input
ref="username"
v-model:value="loginData.username"
clearable
type="text"
placeholder="请输入用户账号/身份证号/手机号"
size="large"
>
<template #prefix>
<UserOutlined />
</template>
</a-input>
</a-form-item>
<!-- 密码输入框 -->
<a-form-item label="" name="password">
<a-input-password
type="password"
v-model:value="loginData.password"
placeholder="请输入密码"
size="large"
>
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
<!-- 验证码区域 -->
<a-form-item label="" name="captcha">
<div class="captcha-row">
<a-row :gutter="24" align="middle">
<a-col :span="10"
><a-input
v-model:value="loginData.code"
placeholder="请输入验证码"
size="large"
class="captcha-input"
/></a-col>
<a-col :span="10"
><img :src="codeUrl" alt="验证码" class="captcha-img"
/></a-col>
<a-col :span="4"> <a @click="getCode">换一张</a></a-col>
</a-row>
</div>
</a-form-item>
<a-button
type="primary"
size="large"
block
htmlType="submit"
:loading="loading"
>
<span>登录</span>
</a-button>
<!-- <a-button
type="link"
size="mini"
block
@click="showForgotPasswordPage"
:style="{ marginTop: '10px', border: 'none' }"
>
忘记密码
</a-button> -->
<!-- 忘记密码 -->
</a-form>
</a-tab-pane>
<!-- 短信登录 Tab (占位) -->
<!-- <a-tab-pane key="sms" tab="短信登录">
<a-form
:model="loginData"
:rules="loginRules"
layout="vertical"
class="form-container"
>
<a-form-item label="" name="username">
<a-input
v-model:value="loginData.username"
placeholder="请输入手机号"
size="large"
>
<template #prefix>
<MobileOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item label="" name="captcha">
<div class="captcha-row">
<a-input
v-model="loginData.code"
placeholder="请输入验证码"
size="large"
class="captcha-input"
>
<template #prefix>
<LockOutlined />
</template>
<template #suffix>
<a-button
type="text"
size="small"
@click="sendSms"
:disabled="smsButtonDisabled"
:loading="loading"
>
{{
smsCountdown > 0
? `${smsCountdown}秒后重新获取`
: "获取验证码"
}}
</a-button>
</template>
</a-input>
</div>
</a-form-item>
<a-button
type="primary"
size="large"
block
htmlType="submit"
:loading="loading"
>
<span>登录</span>
</a-button>
</a-form>
</a-tab-pane> -->
</a-tabs>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs, watch, nextTick } from "vue";
import loginImg from "@/assets/images/logo.png";
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
import { getCaptcha } from "@/api/auth";
import { message } from "ant-design-vue";
import { setPath } from '@/utils/auth';
//
import router from "@/router";
import Cookies from "js-cookie";
// API
import { useRoute } from "vue-router";
import { LoginData } from "@/api/auth/types";
//
import { encrypt, decrypt } from "@/utils/rsaEncrypt";
//
import { useUserStore } from "@/store/modules/user";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const userStore = useUserStore();
const route = useRoute();
//
const codeUrl = ref("");
const smsCountdown = ref(0); //
const smsButtonDisabled = ref(false); //
//
let remember = ref(false);
//
const forgotPasswordForm = ref({
phone: "",
captcha: "",
newPassword: "",
});
//
const showForgotPassword = ref(false);
//
const activeTab = ref("account");
const state = reactive({
redirect: "",
loginData: {
uuid: "",
username: "admin",
password: "123456",
code: "",
} as LoginData,
loginRules: {
username: [{ required: true, trigger: "blur", message: t("login.rulesUsername") }],
password: [{ required: true, trigger: "blur", message: t("login.rulesPassword") }],
code: [{ required: true, trigger: "blur", message: "请输入验证码" }],
},
loginImg: loginImg[0],
loading: false,
passwordType: "password",
//
capslockTooltipDisabled: true,
otherQuery: {},
clientHeight: document.documentElement.clientHeight,
showDialog: false,
cookiePass: "",
});
//
const forgotPasswordRules = ref({
phone: [
{ required: true, message: "手机号不能为空", trigger: "blur" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号", trigger: "blur" },
],
captcha: [{ required: true, message: "验证码不能为空", trigger: "blur" }],
newPassword: [
{ required: true, message: "新密码不能为空", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
});
const { loginData, loginRules, loading, passwordType, capslockTooltipDisabled } = toRefs(
state
);
function checkCapslock(e: any) {
const { key } = e;
state.capslockTooltipDisabled = key && key.length === 1 && key >= "A" && key <= "Z";
}
function showPwd() {
if (passwordType.value === "password") {
passwordType.value = "";
} else {
passwordType.value = "password";
}
nextTick(() => {
passwordRef.value.focus();
});
}
/**
* 登录
*/
function onFinish() {
state.loading = true;
const user = {
username: state.loginData.username,
password: state.loginData.password,
// rememberMe: state.loginData.rememberMe,
code: state.loginData.code,
uuid: state.loginData.uuid,
};
if (user.password !== state.cookiePass) {
user.password = encrypt(user.password);
}
console.log(user);
userStore
.login(user)
.then(() => {
Cookies.set("username", user.username);
setPath('/login-sjtb')
router.push({ path: "/" });
state.loading = false;
})
.catch(() => {
getCode();
state.loading = false;
});
}
watch(
route,
() => {
const query = route.query;
if (query) {
state.redirect = query.redirect as string;
state.otherQuery = getOtherQuery(query);
}
},
{
immediate: true,
}
);
function getOtherQuery(query: any) {
return Object.keys(query).reduce((acc: any, cur: any) => {
if (cur !== "redirect") {
acc[cur] = query[cur];
}
return acc;
}, {});
}
function getCookie() {
const username = Cookies.get("username");
let password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
rememberMe == "true" ? (remember.value = Boolean(rememberMe)) : false;
// cookie
state.cookiePass = password === undefined ? "" : password;
password = password === undefined ? state.loginData.password : password;
state.loginData = {
username: username === undefined ? state.loginData.username : username,
password: decrypt(password),
code: "",
uuid: "",
};
remember.value = rememberMe === undefined ? false : Boolean(rememberMe);
}
function getCode() {
getCaptcha().then((result: any) => {
codeUrl.value = result.data.img;
state.loginData.uuid = result.data.uuid;
});
}
//
const startCountdown = () => {
smsCountdown.value = 60;
const timer = setInterval(() => {
smsCountdown.value--;
if (smsCountdown.value <= 0) {
clearInterval(timer);
smsButtonDisabled.value = false;
}
}, 1000);
};
//
const showForgotPasswordPage = () => {
showForgotPassword.value = true;
};
//
const backToLogin = () => {
showForgotPassword.value = false;
//
forgotPasswordForm.value = {
phone: "",
captcha: "",
newPassword: "",
};
};
//
const sendSms = async () => {
//
if (!loginData.value.username) {
message.error("请输入手机号");
return;
}
//
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(loginData.value.username)) {
message.error("请输入正确的手机号");
return;
}
//
if (smsCountdown.value > 0) {
return;
}
loading.value = true;
smsButtonDisabled.value = true;
try {
//
// await axios.post('/sms/send', { phone: loginForm.value.username })
//
message.success("验证码发送成功");
//
startCountdown();
} catch (error) {
console.error("发送验证码失败", error);
message.error("验证码发送失败,请重试");
smsButtonDisabled.value = false;
} finally {
loading.value = false;
}
};
//
const sendForgotPasswordSms = async () => {
//
if (!forgotPasswordForm.value.phone) {
message.error("请输入手机号");
return;
}
//
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(forgotPasswordForm.value.phone)) {
message.error("请输入正确的手机号");
return;
}
//
if (smsCountdown.value > 0) {
return;
}
loading.value = true;
smsButtonDisabled.value = true;
try {
//
// await axios.post('/sms/forgot-password', { phone: forgotPasswordForm.value.phone })
//
message.success("验证码发送成功");
//
startCountdown();
} catch (error) {
console.error("发送验证码失败", error);
message.error("验证码发送失败,请重试");
smsButtonDisabled.value = false;
} finally {
loading.value = false;
}
};
//
const handleResetPassword = async () => {
loading.value = true;
try {
// 2. ()
// await axios.post('/user/reset-password', {
// phone: forgotPasswordForm.value.phone,
// captcha: forgotPasswordForm.value.captcha,
// newPassword: forgotPasswordForm.value.newPassword
// })
// 3.
message.success("密码重置成功");
// 4.
backToLogin();
} catch (error) {
console.error("密码重置失败", error);
message.error("密码重置失败,请重试");
} finally {
loading.value = false;
}
};
onMounted(() => {
getCookie();
getCode();
});
</script>
<style scoped lang="scss">
.login-container {
margin: 0px auto;
position: relative;
width: 100%;
height: 100%;
min-width: 1500px;
background-color: rgb(255, 255, 255);
h1 {
position: relative;
padding: 66px 0px 2px 80px;
margin-bottom: 0px;
height: 155px;
div {
position: relative;
line-height: 71px;
}
.logo-img {
position: absolute;
left: 0px;
top: 10px;
width: 55px;
}
.system-title {
margin-left: 71px;
font-weight: 700;
font-size: 35px;
color: rgb(47, 107, 152);
}
}
:deep(.ant-tabs-tab) {
padding: 8px 0;
}
:deep(.ant-input-prefix) {
display: flex;
width: 26px;
svg {
width: 18px;
height: 18px;
margin-right: 4px;
}
}
.login-wrapper {
position: relative;
width: 100%;
height: 100%;
min-height: 600px;
background: url("@/assets/images/bg_sjtb.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
//
.left-section {
.slogan {
position: absolute;
top: 20%;
left: 18%;
width: 490px;
height: 112px;
color: #040504;
font-size: 40px;
}
}
//
.right-section {
position: absolute;
left: 74%;
top: 30%;
width: 20%;
max-height: 402px;
max-width: 400px;
min-height: 362px;
border-radius: 3px;
padding: 15px 24px 24px;
background-color: rgb(255, 255, 255);
.ant-tabs-nav {
height: 38px;
}
}
/* 验证码布局 */
.captcha-row {
display: flex;
.ant-row {
display: flex;
align-items: start;
}
/* gap: 12px;
align-items: center; */
.captcha-img {
width: 100%;
height: 40px;
line-height: 40px;
background-repeat: no-repeat;
background-size: contain;
background-position: center center;
}
a {
line-height: 50px;
float: right;
color: #1890ff;
text-decoration: none;
background-color: transparent;
outline: none;
cursor: pointer;
transition: color 0.3s;
white-space: nowrap;
}
}
//
.forgot-link {
float: right;
color: #1677ff;
font-size: 14px;
}
//
.sms-login-tip {
padding: 40px 20px;
text-align: center;
color: #999;
}
//
.forgot-password-container {
.forgot-password-header {
display: flex;
align-items: center;
margin-bottom: 24px;
.back-button {
padding: 0;
margin-right: 12px;
color: #1677ff;
font-size: 14px;
&:hover {
color: #4096ff;
}
}
.forgot-password-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
}
}
}
</style>

View File

@ -240,6 +240,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { setPath } from '@/utils/auth';
import { onMounted, reactive, ref, toRefs, watch, nextTick } from "vue"; import { onMounted, reactive, ref, toRefs, watch, nextTick } from "vue";
import loginImg from "@/assets/images/logo.png"; import loginImg from "@/assets/images/logo.png";
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue"; import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
@ -362,6 +363,7 @@ function onFinish() {
.then(() => { .then(() => {
Cookies.set("username", user.username); Cookies.set("username", user.username);
router.push({ path: "/" }); router.push({ path: "/" });
setPath('/login')
state.loading = false; state.loading = false;
}) })
.catch(() => { .catch(() => {

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "WholeProcessPlatform",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}