图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

514 lines
13 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="page-container">
  3. <!-- 顶部背景 + 标题 -->
  4. <view class="top-bar">
  5. <image class="top-bar-bg" src="@/static/images/mingqi-beij@2x.png" mode="aspectFill"></image>
  6. <view class="library-info">
  7. <image class="avatar" src="@/static/images/logo.jpg" mode="aspectFill"></image>
  8. <view class="library-name">葛店经济技术开发区图书馆</view>
  9. <view class="sub-title">读者证办理</view>
  10. </view>
  11. </view>
  12. <!-- 表单卡片 -->
  13. <view class="form-card">
  14. <!-- 姓名 -->
  15. <view class="form-item">
  16. <uni-icons class="form-icon" custom-prefix="iconfont" type="icon-xingming" size="22"></uni-icons>
  17. <input
  18. class="input"
  19. placeholder="请输入真实姓名"
  20. v-model="formData.name"
  21. maxlength="20"
  22. />
  23. </view>
  24. <!-- 身份证号 -->
  25. <view class="form-item">
  26. <uni-icons class="form-icon" custom-prefix="iconfont" type="icon-duzhezheng" size="24"></uni-icons>
  27. <input
  28. class="input"
  29. placeholder="请输入18位身份证号"
  30. v-model="formData.idCard"
  31. maxlength="18"
  32. />
  33. </view>
  34. <!-- 手机号 -->
  35. <view class="form-item">
  36. <uni-icons class="form-icon" type="phone" size="24"></uni-icons>
  37. <input
  38. class="input"
  39. placeholder="请输入手机号"
  40. v-model="formData.phone"
  41. type="number"
  42. maxlength="11"
  43. />
  44. <button class="get-phone-btn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
  45. 一键获取
  46. </button>
  47. </view>
  48. <!-- 密码 -->
  49. <view class="form-item">
  50. <uni-icons class="form-icon" type="locked" size="24"></uni-icons>
  51. <input
  52. class="input"
  53. placeholder="请设置6-20位密码"
  54. :password="!showPwd"
  55. v-model="formData.password"
  56. maxlength="20"
  57. />
  58. <uni-icons
  59. class="pwd-icon"
  60. :type="showPwd ? 'eye' : 'eye-slash'"
  61. size="20"
  62. @click="togglePwd"
  63. ></uni-icons>
  64. </view>
  65. <!-- 确认密码 -->
  66. <view class="form-item">
  67. <uni-icons class="form-icon" type="locked" size="24"></uni-icons>
  68. <input
  69. class="input"
  70. placeholder="请再次确认密码"
  71. :password="!showConfirmPwd"
  72. v-model="formData.confirmPassword"
  73. maxlength="20"
  74. />
  75. <uni-icons
  76. class="pwd-icon"
  77. :type="showConfirmPwd ? 'eye' : 'eye-slash'"
  78. size="20"
  79. @click="toggleConfirmPwd"
  80. ></uni-icons>
  81. </view>
  82. <!-- 提交按钮 -->
  83. <button class="submit-btn" type="primary" @click="submit" :loading="loading">
  84. {{ loading ? '提交中...' : '确认办理' }}
  85. </button>
  86. <!-- 同意协议 -->
  87. <view class="agreement-item">
  88. <view class="radio-box" :class="{ checked: agreed }" @click="agreed = !agreed">
  89. <view class="radio-inner" v-if="agreed"></view>
  90. </view>
  91. <text class="agreement-text">
  92. 我已阅读并同意遵守
  93. <text class="agreement-link" @click="openAgreement">爱图智服服务协议</text>
  94. </text>
  95. </view>
  96. </view>
  97. <!-- 温馨提示 -->
  98. <view class="tips-card">
  99. <view class="tips-title">温馨提示</view>
  100. <view class="tips-text">
  101. 1. 请填写真实身份信息用于办理读者证<br>
  102. 2. 密码请牢记用于登录和借阅查询<br>
  103. <!-- 3. 提交后工作人员将尽快审核 -->
  104. </view>
  105. </view>
  106. </view>
  107. </template>
  108. <script>
  109. import config from '@/utils/config';
  110. import { getOpenId } from '@/utils/storage';
  111. import { FetchSessionKey, FetchDecryptPhone } from '@/api/user.js';
  112. export default {
  113. data() {
  114. return {
  115. sessionKey: '',
  116. openid: '',
  117. formData: {
  118. name: '',
  119. idCard: '',
  120. phone: '',
  121. password: '',
  122. confirmPassword: ''
  123. },
  124. showPwd: false,
  125. showConfirmPwd: false,
  126. loading: false,
  127. agreed: false
  128. };
  129. },
  130. onLoad() {
  131. this.getSessionKey();
  132. },
  133. methods: {
  134. async getSessionKey() {
  135. try {
  136. uni.login({
  137. success: async (loginRes) => {
  138. console.log('loginRes',loginRes)
  139. if (loginRes.code) {
  140. const res = await FetchSessionKey({
  141. libcode: config.LIB_CODE,
  142. code: loginRes.code
  143. });
  144. if (res.code === 200) {
  145. this.sessionKey = res.data.session_key;
  146. this.openid = res.data.openid;
  147. }
  148. }
  149. }
  150. });
  151. } catch (error) {
  152. console.error('获取sessionKey失败:', error);
  153. }
  154. },
  155. getPhoneNumber(e) {
  156. if (e.detail.errMsg !== 'getPhoneNumber:ok') {
  157. uni.showToast({ title: '取消授权', icon: 'none' });
  158. return;
  159. }
  160. if (!this.sessionKey) {
  161. uni.showToast({ title: '请稍后再试', icon: 'none' });
  162. return;
  163. }
  164. FetchDecryptPhone({
  165. sessionKey: this.sessionKey,
  166. encryptedData: e.detail.encryptedData,
  167. iv: e.detail.iv
  168. }).then(res => {
  169. if (res.code === 200) {
  170. this.formData.phone = res.data.phoneNumber || '';
  171. uni.showToast({ title: '获取成功', icon: 'success' });
  172. } else {
  173. uni.showToast({ title: res.message || '获取失败', icon: 'none' });
  174. }
  175. }).catch(() => {
  176. uni.showToast({ title: '获取手机号失败', icon: 'none' });
  177. });
  178. },
  179. togglePwd() {
  180. this.showPwd = !this.showPwd;
  181. },
  182. toggleConfirmPwd() {
  183. this.showConfirmPwd = !this.showConfirmPwd;
  184. },
  185. // 身份证验证(支持15位和18位,自动去除空格和连字符)
  186. validateIdCard(idCard) {
  187. if (!idCard) return false;
  188. // 去除空格和连字符
  189. idCard = idCard.replace(/[\s-]/g, '');
  190. // 15位身份证格式验证
  191. const idCard15Reg = /^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/;
  192. // 18位身份证格式验证
  193. const idCard18Reg = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
  194. // 15位身份证直接验证格式
  195. if (idCard.length === 15) {
  196. return idCard15Reg.test(idCard);
  197. }
  198. // 18位身份证需要验证格式、日期和校验码
  199. if (idCard.length !== 18 || !idCard18Reg.test(idCard)) {
  200. return false;
  201. }
  202. // 验证日期有效性(宽松模式:跳过校验码验证)
  203. const year = +idCard.substring(6, 10);
  204. const month = +idCard.substring(10, 12);
  205. const day = +idCard.substring(12, 14);
  206. const date = new Date(year, month - 1, day);
  207. return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
  208. },
  209. // 手机号验证
  210. validatePhone(phone) {
  211. return /^1[3-9]\d{9}$/.test(phone);
  212. },
  213. // ✅ 【核心修复】表单验证(更严谨)
  214. validateForm() {
  215. const { name, idCard, phone, password, confirmPassword } = this.formData;
  216. // 1. 姓名:必须非空且trim后有内容
  217. if (!name || name.trim() === '') {
  218. uni.showToast({ title: '请输入姓名', icon: 'none' });
  219. return false;
  220. }
  221. // 2. 身份证:非空 + 格式正确
  222. if (!idCard || idCard.trim() === '') {
  223. uni.showToast({ title: '请输入身份证号', icon: 'none' });
  224. return false;
  225. }
  226. if (!this.validateIdCard(idCard.trim())) {
  227. uni.showToast({ title: '身份证号格式不正确', icon: 'none' });
  228. return false;
  229. }
  230. // 3. 手机号:非空 + 格式正确
  231. if (!phone || phone.trim() === '') {
  232. uni.showToast({ title: '请输入手机号', icon: 'none' });
  233. return false;
  234. }
  235. if (!this.validatePhone(phone.trim())) {
  236. uni.showToast({ title: '手机号格式不正确', icon: 'none' });
  237. return false;
  238. }
  239. // 4. 密码:至少6位
  240. if (!password || password.length < 6) {
  241. uni.showToast({ title: '密码至少6位', icon: 'none' });
  242. return false;
  243. }
  244. // 5. 确认密码:必须一致
  245. if (password !== confirmPassword) {
  246. uni.showToast({ title: '两次密码不一致', icon: 'none' });
  247. return false;
  248. }
  249. // 所有校验通过
  250. return true;
  251. },
  252. // 显示协议弹窗
  253. showAgreementModal() {
  254. uni.showModal({
  255. title: '提示',
  256. content: '请先阅读并同意《爱图智服服务协议》',
  257. confirmText: '同意',
  258. cancelText: '不同意',
  259. success: (res) => {
  260. if (res.confirm) {
  261. // 用户同意,选中radio并继续提交
  262. this.agreed = true;
  263. this.submitForm();
  264. }
  265. }
  266. });
  267. },
  268. // 跳转到协议详情页面
  269. openAgreement() {
  270. uni.navigateTo({
  271. url: '/subpkg/pages/agreement/agreement'
  272. });
  273. },
  274. async submit() {
  275. // 防止重复点击
  276. if (this.loading) return;
  277. // 小程序关键:等 input 数据同步到 data
  278. await this.$nextTick();
  279. // 校验不通过直接 return
  280. if (!this.validateForm()) {
  281. return;
  282. }
  283. // 如果未同意协议,显示协议弹窗
  284. if (!this.agreed) {
  285. this.showAgreementModal();
  286. return;
  287. }
  288. // 已同意协议,直接提交
  289. await this.submitForm();
  290. },
  291. // 实际提交表单
  292. async submitForm() {
  293. this.loading = true;
  294. try {
  295. const openId = await getOpenId();
  296. if (!openId) {
  297. uni.showToast({ title: '获取用户信息失败', icon: 'none' });
  298. return;
  299. }
  300. uni.showToast({ title: '后续流程正在研发中,请稍后试用~', icon: 'none' });
  301. setTimeout(() => uni.navigateBack(), 1500);
  302. // 提交(trim 后再发)
  303. // const res = await uni.request({
  304. // url: config.baseUrl + '/api/reader/register',
  305. // method: 'POST',
  306. // data: {
  307. // name: this.formData.name.trim(),
  308. // idCard: this.formData.idCard.trim(),
  309. // phone: this.formData.phone.trim(),
  310. // password: this.formData.password,
  311. // openId,
  312. // libcode: config.LIB_CODE
  313. // }
  314. // });
  315. // if (res.data?.code === 200) {
  316. // uni.showToast({ title: '办理成功', icon: 'success' });
  317. // setTimeout(() => uni.navigateBack(), 1500);
  318. // } else {
  319. // uni.showToast({ title: res.data?.message || '提交失败', icon: 'none' });
  320. // }
  321. } catch (err) {
  322. console.error('提交异常', err);
  323. uni.showToast({ title: '网络异常,请重试', icon: 'none' });
  324. } finally {
  325. this.loading = false;
  326. }
  327. }
  328. }
  329. };
  330. </script>
  331. <style lang="scss" scoped>
  332. page {
  333. background-color: #f5f7fa;
  334. }
  335. .page-container {
  336. min-height: 100vh;
  337. background-color: #f5f7fa;
  338. padding-bottom: 20px;
  339. }
  340. /* 顶部 */
  341. .top-bar {
  342. height: 210px;
  343. .library-info {
  344. position: relative;
  345. z-index: 2;
  346. color: #fff;
  347. }
  348. }
  349. /* 表单卡片 */
  350. .form-card {
  351. margin: -30px 12px 12px;
  352. background: #fff;
  353. border-radius: 12px;
  354. padding: 20px 16px;
  355. box-shadow: 0 4px 15px rgba(0,0,0,0.08);
  356. position: relative;
  357. z-index: 3;
  358. }
  359. .form-item {
  360. display: flex;
  361. align-items: center;
  362. height: 44px;
  363. border-bottom: 1rpx solid #f2f3f5;
  364. margin-bottom: 4px;
  365. .form-icon {
  366. font-size: 14px;
  367. color: #01a4fe;
  368. margin-right: 10px;
  369. }
  370. .input {
  371. flex: 1;
  372. font-size: 14px;
  373. color: #333;
  374. }
  375. .pwd-icon {
  376. color: #999;
  377. padding: 5px;
  378. }
  379. .get-phone-btn {
  380. font-size: 12px;
  381. color: #fff;
  382. background-color: #01a4fe;
  383. border-radius: 20px;
  384. padding: 6px 10px;
  385. border: none;
  386. line-height: 1.2;
  387. &::after {
  388. border: none;
  389. }
  390. }
  391. }
  392. /* 提交按钮 */
  393. .submit-btn {
  394. width: 100%;
  395. height: 44px;
  396. background-color: #01a4fe;
  397. color: #fff;
  398. border-radius: 22px;
  399. font-size: 15px;
  400. margin-top: 20px;
  401. border: none;
  402. &::after {
  403. border: none;
  404. }
  405. }
  406. /* 同意协议 */
  407. .agreement-item {
  408. display: flex;
  409. align-items: center;
  410. margin-top: 16px;
  411. .radio-box {
  412. width: 22rpx;
  413. height: 22rpx;
  414. border: 2rpx solid #ccc;
  415. border-radius: 50%;
  416. margin-right: 10rpx;
  417. display: flex;
  418. align-items: center;
  419. justify-content: center;
  420. transition: all 0.2s;
  421. &.checked {
  422. border-color: #01a4fe;
  423. background-color: #01a4fe;
  424. }
  425. .radio-inner {
  426. width: 12rpx;
  427. height: 12rpx;
  428. background-color: #fff;
  429. border-radius: 50%;
  430. }
  431. }
  432. .agreement-text {
  433. font-size: 12px;
  434. color: #666;
  435. flex: 1;
  436. }
  437. .agreement-link {
  438. color: #01a4fe;
  439. }
  440. }
  441. /* 温馨提示 */
  442. .tips-card {
  443. margin: 0 12px;
  444. background: #fff;
  445. border-radius: 10px;
  446. padding: 15px;
  447. box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  448. .tips-title {
  449. font-size: 13px;
  450. color: #333;
  451. font-weight: 500;
  452. margin-bottom: 8px;
  453. }
  454. .tips-text {
  455. font-size: 12px;
  456. color: #666;
  457. line-height: 1.6;
  458. }
  459. }
  460. </style>