310 lines
14 KiB
Plaintext
310 lines
14 KiB
Plaintext
|
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
|||
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>读者门户 · 简洁版 (合并读者类型)</title>
|
|||
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|||
|
|
<style>
|
|||
|
|
/* 样式保持不变,省略(实际使用时应保留原有样式) */
|
|||
|
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|||
|
|
body { background:#eef2f6; font-family:'Inter',sans-serif; display:flex; align-items:center; justify-content:center; min-height:100vh; padding:1rem; }
|
|||
|
|
#app { width:100%; max-width:520px; }
|
|||
|
|
.card { background:white; border-radius:28px; box-shadow:0 25px 45px -12px rgba(0,0,0,0.15); padding:2.2rem 2rem; width:100%; }
|
|||
|
|
h2 { font-size:1.9rem; font-weight:600; color:#1a2b3c; margin-bottom:1.5rem; border-bottom:2px solid #f0f4f9; padding-bottom:1rem; }
|
|||
|
|
h3 { font-size:1.2rem; font-weight:500; color:#2c3e50; margin-bottom:1.2rem; }
|
|||
|
|
.field { margin-bottom:1.3rem; }
|
|||
|
|
label { display:block; font-size:0.8rem; font-weight:600; text-transform:uppercase; color:#4a5e74; margin-bottom:0.3rem; }
|
|||
|
|
input, select { width:100%; padding:0.8rem 1rem; font-size:1rem; border:1.5px solid #e2e9f2; border-radius:16px; background:#fff; transition:border 0.15s; outline:none; }
|
|||
|
|
input:focus, select:focus { border-color:#7c8ea0; background:#fafcff; }
|
|||
|
|
button { background:#2c3e50; color:white; border:none; padding:0.9rem 1.5rem; font-size:1rem; font-weight:500; border-radius:40px; cursor:pointer; transition:background 0.15s; width:100%; margin-top:0.3rem; }
|
|||
|
|
button:hover { background:#1d2c3a; }
|
|||
|
|
.secondary-btn { background:white; color:#2c3e50; border:1.5px solid #d0ddee; margin-top:1rem; }
|
|||
|
|
.secondary-btn:hover { background:#f2f6fc; border-color:#a0b8cf; }
|
|||
|
|
.nav-links { display:flex; justify-content:space-between; margin-top:1.5rem; gap:0.5rem; flex-wrap:wrap; }
|
|||
|
|
.nav-link { background:#f0f5fb; color:#2e405b; padding:0.6rem 1.2rem; border-radius:40px; font-size:0.9rem; font-weight:500; cursor:pointer; border:1px solid transparent; transition:background 0.15s; text-align:center; flex:1 1 auto; }
|
|||
|
|
.nav-link:hover { background:#dae2ed; color:#0e1e2f; }
|
|||
|
|
.active-view { margin-bottom:1rem; }
|
|||
|
|
.note { font-size:0.75rem; color:#8f9fb1; margin-top:0.5rem; text-align:center; }
|
|||
|
|
.compact-form { background:#f9fcff; border-radius:20px; padding:1.5rem 1.2rem; margin:0.5rem 0 1rem; }
|
|||
|
|
.nav-link.active-nav { background:#2c3e50; color:white; border-color:#2c3e50; }
|
|||
|
|
.nav-link.active-nav:hover { background:#1d2c3a; }
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div id="app">
|
|||
|
|
<div class="card">
|
|||
|
|
<h2>📖 读者门户</h2>
|
|||
|
|
|
|||
|
|
<!-- 动态视图:读者登录 / 读者注册 / 员工登录 -->
|
|||
|
|
<div class="active-view">
|
|||
|
|
<!-- 读者登录视图 -->
|
|||
|
|
<div v-if="currentView === 'reader-login'">
|
|||
|
|
<h3>👤 读者登录</h3>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>证号</label>
|
|||
|
|
<input v-model="readerLogin.cardNumber" type="text" placeholder="例如:R2024001" />
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>姓名</label>
|
|||
|
|
<input v-model="readerLogin.name" type="text" placeholder="你的名字" />
|
|||
|
|
</div>
|
|||
|
|
<button @click="handleReaderLogin">登录</button>
|
|||
|
|
<div class="note">跳转与验证由后端处理</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 读者注册视图 -->
|
|||
|
|
<div v-else-if="currentView === 'reader-register'">
|
|||
|
|
<h3>📝 注册新读者</h3>
|
|||
|
|
<div class="compact-form">
|
|||
|
|
<div class="field">
|
|||
|
|
<label>证号</label>
|
|||
|
|
<input v-model="registerForm.cardNumber" type="text" placeholder="唯一证号" />
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>姓名</label>
|
|||
|
|
<input v-model="registerForm.name" type="text" placeholder="真实姓名" />
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>年龄(选填)</label>
|
|||
|
|
<input v-model="registerForm.age" type="number" placeholder="可选" min="0" max="120" />
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>读者类型</label>
|
|||
|
|
<select v-model="registerForm.type">
|
|||
|
|
<option>老年</option>
|
|||
|
|
<option>成人</option>
|
|||
|
|
<option>儿童</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>联系方式</label>
|
|||
|
|
<input v-model="registerForm.contact" type="text" placeholder="手机 / 邮箱" />
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>注册日期</label>
|
|||
|
|
<input v-model="registerForm.regDate" type="date" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<button @click="handleRegister">提交注册</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 员工登录视图 -->
|
|||
|
|
<div v-else-if="currentView === 'employee-login'">
|
|||
|
|
<h3>🔐 员工登录</h3>
|
|||
|
|
<div class="compact-form">
|
|||
|
|
<div class="field">
|
|||
|
|
<label>员工工号</label>
|
|||
|
|
<input v-model="employeeLogin.employeeCode" type="text" placeholder="员工工号" />
|
|||
|
|
</div>
|
|||
|
|
<div class="field">
|
|||
|
|
<label>姓名</label>
|
|||
|
|
<input v-model="employeeLogin.name" type="text" placeholder="姓名" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<button @click="handleEmployeeLogin">登录</button>
|
|||
|
|
<div class="note">经理页面另有添加用户功能</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 导航链接 -->
|
|||
|
|
<div class="nav-links">
|
|||
|
|
<span class="nav-link" @click="switchView('reader-login')" :class="{ 'active-nav': currentView === 'reader-login' }">🔑 读者登录</span>
|
|||
|
|
<span class="nav-link" @click="switchView('reader-register')" :class="{ 'active-nav': currentView === 'reader-register' }">➕ 注册</span>
|
|||
|
|
<span class="nav-link" @click="switchView('employee-login')" :class="{ 'active-nav': currentView === 'employee-login' }">👔 员工登录</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div style="margin-top: 1.2rem; text-align: center; font-size:0.8rem; color:#abb9c9;">
|
|||
|
|
点击上方标签切换功能
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
(function() {
|
|||
|
|
const { createApp, ref } = Vue;
|
|||
|
|
|
|||
|
|
// 工具函数:获取今日日期字符串 yyyy-MM-dd
|
|||
|
|
const getTodayString = () => {
|
|||
|
|
const d = new Date();
|
|||
|
|
const year = d.getFullYear();
|
|||
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|||
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|||
|
|
return `${year}-${month}-${day}`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 工具函数:验证日期字符串是否符合 yyyy-MM-dd 格式
|
|||
|
|
const isValidDate = (dateStr) => {
|
|||
|
|
if (!dateStr) return false;
|
|||
|
|
const regex = /^\d{4}-\d{2}-\d{2}$/;
|
|||
|
|
if (!regex.test(dateStr)) return false;
|
|||
|
|
const date = new Date(dateStr);
|
|||
|
|
return !isNaN(date.getTime());
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const app = createApp({
|
|||
|
|
setup() {
|
|||
|
|
const currentView = ref('reader-login');
|
|||
|
|
|
|||
|
|
// 读者登录数据
|
|||
|
|
const readerLogin = ref({
|
|||
|
|
cardNumber: '',
|
|||
|
|
name: ''
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 员工登录数据
|
|||
|
|
const employeeLogin = ref({
|
|||
|
|
employeeCode: '',
|
|||
|
|
name: ''
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 注册表单默认值
|
|||
|
|
const getDefaultRegisterForm = () => ({
|
|||
|
|
cardNumber: '',
|
|||
|
|
name: '',
|
|||
|
|
age: '',
|
|||
|
|
type: '成人',
|
|||
|
|
contact: '',
|
|||
|
|
regDate: getTodayString()
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const registerForm = ref(getDefaultRegisterForm());
|
|||
|
|
|
|||
|
|
// 切换视图时重置数据
|
|||
|
|
const switchView = (view) => {
|
|||
|
|
currentView.value = view;
|
|||
|
|
if (view === 'reader-register') {
|
|||
|
|
registerForm.value = getDefaultRegisterForm();
|
|||
|
|
} else if (view === 'reader-login') {
|
|||
|
|
readerLogin.value = { cardNumber: '', name: '' };
|
|||
|
|
} else if (view === 'employee-login') {
|
|||
|
|
employeeLogin.value = { employeeCode: '', name: '' };
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 读者登录
|
|||
|
|
const handleReaderLogin = async () => {
|
|||
|
|
if (!readerLogin.value.cardNumber || !readerLogin.value.name) {
|
|||
|
|
alert('请填写证号和姓名');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const params = new URLSearchParams({
|
|||
|
|
cardNumber: readerLogin.value.cardNumber,
|
|||
|
|
name: readerLogin.value.name
|
|||
|
|
});
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('${pageContext.request.contextPath}/api/login/reader', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|||
|
|
body: params
|
|||
|
|
});
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.success) {
|
|||
|
|
window.location.href = '${pageContext.request.contextPath}/view/Reader.jsp';
|
|||
|
|
} else {
|
|||
|
|
alert(result.message || '登录失败');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e);
|
|||
|
|
alert('网络错误,请检查后端服务是否正常');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 读者注册
|
|||
|
|
const handleRegister = async () => {
|
|||
|
|
// 前端基础校验
|
|||
|
|
if (!registerForm.value.cardNumber) {
|
|||
|
|
alert('证号不能为空');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!registerForm.value.name) {
|
|||
|
|
alert('姓名不能为空');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// 年龄:如果是空字符串或非数字,设为空(后端会处理为null)
|
|||
|
|
let ageVal = registerForm.value.age;
|
|||
|
|
if (ageVal !== '' && isNaN(Number(ageVal))) {
|
|||
|
|
alert('年龄请输入数字');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// 日期处理:如果无效或为空,使用今天日期
|
|||
|
|
let regDateVal = registerForm.value.regDate;
|
|||
|
|
if (!isValidDate(regDateVal)) {
|
|||
|
|
regDateVal = getTodayString();
|
|||
|
|
registerForm.value.regDate = regDateVal; // 同步更新显示
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const params = new URLSearchParams({
|
|||
|
|
cardNumber: registerForm.value.cardNumber,
|
|||
|
|
name: registerForm.value.name,
|
|||
|
|
age: ageVal,
|
|||
|
|
type: registerForm.value.type,
|
|||
|
|
contact: registerForm.value.contact || '',
|
|||
|
|
regDate: regDateVal
|
|||
|
|
});
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('${pageContext.request.contextPath}/api/register', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|||
|
|
body: params
|
|||
|
|
});
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.success) {
|
|||
|
|
alert('注册成功,请登录');
|
|||
|
|
switchView('reader-login');
|
|||
|
|
} else {
|
|||
|
|
alert(result.message || '注册失败');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e);
|
|||
|
|
alert('网络错误,请检查后端服务是否正常');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 员工登录
|
|||
|
|
const handleEmployeeLogin = async () => {
|
|||
|
|
if (!employeeLogin.value.employeeCode || !employeeLogin.value.name) {
|
|||
|
|
alert('请填写工号和姓名');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const params = new URLSearchParams({
|
|||
|
|
employeeCode: employeeLogin.value.employeeCode,
|
|||
|
|
name: employeeLogin.value.name
|
|||
|
|
});
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('${pageContext.request.contextPath}/api/login/employee', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|||
|
|
body: params
|
|||
|
|
});
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.success) {
|
|||
|
|
window.location.href = '${pageContext.request.contextPath}/view/Work.jsp';
|
|||
|
|
} else {
|
|||
|
|
alert(result.message || '登录失败');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e);
|
|||
|
|
alert('网络错误,请检查后端服务是否正常');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
currentView,
|
|||
|
|
readerLogin,
|
|||
|
|
employeeLogin,
|
|||
|
|
registerForm,
|
|||
|
|
switchView,
|
|||
|
|
handleReaderLogin,
|
|||
|
|
handleRegister,
|
|||
|
|
handleEmployeeLogin
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
app.mount('#app');
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|