图书馆小程序
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.

524 lines
14 KiB

4 weeks ago
3 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks 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="libraryLogo" mode="aspectFill"></image>
  8. <view class="library-name">{{ fondsName }}</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 {FetchInitScreenSetting, FetchSessionKey, FetchDecryptPhone } from '@/api/user.js';
  112. export default {
  113. data() {
  114. return {
  115. fondsName: getApp().globalData.fondsName || '',
  116. sessionKey: '',
  117. openid: '',
  118. formData: {
  119. name: '',
  120. idCard: '',
  121. phone: '',
  122. password: '',
  123. confirmPassword: ''
  124. },
  125. showPwd: false,
  126. showConfirmPwd: false,
  127. loading: false,
  128. agreed: false
  129. };
  130. },
  131. onLoad() {
  132. this.getScreenSetting();
  133. this.getSessionKey();
  134. },
  135. methods: {
  136. async getSessionKey() {
  137. try {
  138. uni.login({
  139. success: async (loginRes) => {
  140. console.log('loginRes',loginRes)
  141. if (loginRes.code) {
  142. const res = await FetchSessionKey({
  143. libcode: getApp().globalData.libcode,
  144. code: loginRes.code
  145. });
  146. if (res.code === 200) {
  147. this.sessionKey = res.data.session_key;
  148. this.openid = res.data.openid;
  149. }
  150. }
  151. }
  152. });
  153. } catch (error) {
  154. console.error('获取sessionKey失败:', error);
  155. }
  156. },
  157. async getScreenSetting() {
  158. const res = await FetchInitScreenSetting({ libcode: getApp().globalData.libcode });
  159. if(res.data.library_logo && res.data.library_logo.context){
  160. this.libraryLogo = config.baseUrl + '/files/' + res.data.library_logo.context;
  161. }else{
  162. this.libraryLogo = null;
  163. }
  164. },
  165. getPhoneNumber(e) {
  166. if (e.detail.errMsg !== 'getPhoneNumber:ok') {
  167. uni.showToast({ title: '取消授权', icon: 'none' });
  168. return;
  169. }
  170. if (!this.sessionKey) {
  171. uni.showToast({ title: '请稍后再试', icon: 'none' });
  172. return;
  173. }
  174. FetchDecryptPhone({
  175. sessionKey: this.sessionKey,
  176. encryptedData: e.detail.encryptedData,
  177. iv: e.detail.iv
  178. }).then(res => {
  179. if (res.code === 200) {
  180. this.formData.phone = res.data.phoneNumber || '';
  181. uni.showToast({ title: '获取成功', icon: 'success' });
  182. } else {
  183. uni.showToast({ title: res.message || '获取失败', icon: 'none' });
  184. }
  185. }).catch(() => {
  186. uni.showToast({ title: '获取手机号失败', icon: 'none' });
  187. });
  188. },
  189. togglePwd() {
  190. this.showPwd = !this.showPwd;
  191. },
  192. toggleConfirmPwd() {
  193. this.showConfirmPwd = !this.showConfirmPwd;
  194. },
  195. // 身份证验证(支持15位和18位,自动去除空格和连字符)
  196. validateIdCard(idCard) {
  197. if (!idCard) return false;
  198. // 去除空格和连字符
  199. idCard = idCard.replace(/[\s-]/g, '');
  200. // 15位身份证格式验证
  201. const idCard15Reg = /^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/;
  202. // 18位身份证格式验证
  203. 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]$/;
  204. // 15位身份证直接验证格式
  205. if (idCard.length === 15) {
  206. return idCard15Reg.test(idCard);
  207. }
  208. // 18位身份证需要验证格式、日期和校验码
  209. if (idCard.length !== 18 || !idCard18Reg.test(idCard)) {
  210. return false;
  211. }
  212. // 验证日期有效性(宽松模式:跳过校验码验证)
  213. const year = +idCard.substring(6, 10);
  214. const month = +idCard.substring(10, 12);
  215. const day = +idCard.substring(12, 14);
  216. const date = new Date(year, month - 1, day);
  217. return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
  218. },
  219. // 手机号验证
  220. validatePhone(phone) {
  221. return /^1[3-9]\d{9}$/.test(phone);
  222. },
  223. // 表单验证(更严谨)
  224. validateForm() {
  225. const { name, idCard, phone, password, confirmPassword } = this.formData;
  226. // 1. 姓名:必须非空且trim后有内容
  227. if (!name || name.trim() === '') {
  228. uni.showToast({ title: '请输入姓名', icon: 'none' });
  229. return false;
  230. }
  231. // 2. 身份证:非空 + 格式正确
  232. if (!idCard || idCard.trim() === '') {
  233. uni.showToast({ title: '请输入身份证号', icon: 'none' });
  234. return false;
  235. }
  236. if (!this.validateIdCard(idCard.trim())) {
  237. uni.showToast({ title: '身份证号格式不正确', icon: 'none' });
  238. return false;
  239. }
  240. // 3. 手机号:非空 + 格式正确
  241. if (!phone || phone.trim() === '') {
  242. uni.showToast({ title: '请输入手机号', icon: 'none' });
  243. return false;
  244. }
  245. if (!this.validatePhone(phone.trim())) {
  246. uni.showToast({ title: '手机号格式不正确', icon: 'none' });
  247. return false;
  248. }
  249. // 4. 密码:至少6位
  250. if (!password || password.length < 6) {
  251. uni.showToast({ title: '密码至少6位', icon: 'none' });
  252. return false;
  253. }
  254. // 5. 确认密码:必须一致
  255. if (password !== confirmPassword) {
  256. uni.showToast({ title: '两次密码不一致', icon: 'none' });
  257. return false;
  258. }
  259. // 所有校验通过
  260. return true;
  261. },
  262. // 显示协议弹窗
  263. showAgreementModal() {
  264. uni.showModal({
  265. title: '提示',
  266. content: '请先阅读并同意《爱图智服服务协议》',
  267. confirmText: '同意',
  268. cancelText: '不同意',
  269. success: (res) => {
  270. if (res.confirm) {
  271. // 用户同意,选中radio并继续提交
  272. this.agreed = true;
  273. this.submitForm();
  274. }
  275. }
  276. });
  277. },
  278. // 跳转到协议详情页面
  279. openAgreement() {
  280. uni.navigateTo({
  281. url: '/subpkg/pages/agreement/agreement'
  282. });
  283. },
  284. async submit() {
  285. // 防止重复点击
  286. if (this.loading) return;
  287. // 小程序关键:等 input 数据同步到 data
  288. await this.$nextTick();
  289. // 校验不通过直接 return
  290. if (!this.validateForm()) {
  291. return;
  292. }
  293. // 如果未同意协议,显示协议弹窗
  294. if (!this.agreed) {
  295. this.showAgreementModal();
  296. return;
  297. }
  298. // 已同意协议,直接提交
  299. await this.submitForm();
  300. },
  301. // 实际提交表单
  302. async submitForm() {
  303. this.loading = true;
  304. try {
  305. const openId = await getOpenId();
  306. if (!openId) {
  307. uni.showToast({ title: '获取用户信息失败', icon: 'none' });
  308. return;
  309. }
  310. uni.showToast({ title: '后续流程正在研发中,请稍后试用~', icon: 'none' });
  311. setTimeout(() => uni.navigateBack(), 1500);
  312. // 提交(trim 后再发)
  313. // const res = await uni.request({
  314. // url: config.baseUrl + '/api/reader/register',
  315. // method: 'POST',
  316. // data: {
  317. // name: this.formData.name.trim(),
  318. // idCard: this.formData.idCard.trim(),
  319. // phone: this.formData.phone.trim(),
  320. // password: this.formData.password,
  321. // openId,
  322. // libcode: getApp().globalData.libcode
  323. // }
  324. // });
  325. // if (res.data?.code === 200) {
  326. // uni.showToast({ title: '办理成功', icon: 'success' });
  327. // setTimeout(() => uni.navigateBack(), 1500);
  328. // } else {
  329. // uni.showToast({ title: res.data?.message || '提交失败', icon: 'none' });
  330. // }
  331. } catch (err) {
  332. console.error('提交异常', err);
  333. uni.showToast({ title: '网络异常,请重试', icon: 'none' });
  334. } finally {
  335. this.loading = false;
  336. }
  337. }
  338. }
  339. };
  340. </script>
  341. <style lang="scss" scoped>
  342. page {
  343. background-color: #f5f7fa;
  344. }
  345. .page-container {
  346. min-height: 100vh;
  347. background-color: #f5f7fa;
  348. padding-bottom: 20px;
  349. }
  350. /* 顶部 */
  351. .top-bar {
  352. height: 210px;
  353. .library-info {
  354. position: relative;
  355. z-index: 2;
  356. color: #fff;
  357. }
  358. }
  359. /* 表单卡片 */
  360. .form-card {
  361. margin: -30px 12px 12px;
  362. background: #fff;
  363. border-radius: 12px;
  364. padding: 20px 16px;
  365. box-shadow: 0 4px 15px rgba(0,0,0,0.08);
  366. position: relative;
  367. z-index: 3;
  368. }
  369. .form-item {
  370. display: flex;
  371. align-items: center;
  372. height: 44px;
  373. border-bottom: 1rpx solid #f2f3f5;
  374. margin-bottom: 4px;
  375. .form-icon {
  376. font-size: 14px;
  377. color: #01a4fe;
  378. margin-right: 10px;
  379. }
  380. .input {
  381. flex: 1;
  382. font-size: 14px;
  383. color: #333;
  384. }
  385. .pwd-icon {
  386. color: #999;
  387. padding: 5px;
  388. }
  389. .get-phone-btn {
  390. font-size: 12px;
  391. color: #fff;
  392. background-color: #01a4fe;
  393. border-radius: 20px;
  394. padding: 6px 10px;
  395. border: none;
  396. line-height: 1.2;
  397. &::after {
  398. border: none;
  399. }
  400. }
  401. }
  402. /* 提交按钮 */
  403. .submit-btn {
  404. width: 100%;
  405. height: 44px;
  406. background-color: #01a4fe;
  407. color: #fff;
  408. border-radius: 22px;
  409. font-size: 15px;
  410. margin-top: 20px;
  411. border: none;
  412. &::after {
  413. border: none;
  414. }
  415. }
  416. /* 同意协议 */
  417. .agreement-item {
  418. display: flex;
  419. align-items: center;
  420. margin-top: 16px;
  421. .radio-box {
  422. width: 22rpx;
  423. height: 22rpx;
  424. border: 2rpx solid #ccc;
  425. border-radius: 50%;
  426. margin-right: 10rpx;
  427. display: flex;
  428. align-items: center;
  429. justify-content: center;
  430. transition: all 0.2s;
  431. &.checked {
  432. border-color: #01a4fe;
  433. background-color: #01a4fe;
  434. }
  435. .radio-inner {
  436. width: 12rpx;
  437. height: 12rpx;
  438. background-color: #fff;
  439. border-radius: 50%;
  440. }
  441. }
  442. .agreement-text {
  443. font-size: 12px;
  444. color: #666;
  445. flex: 1;
  446. }
  447. .agreement-link {
  448. color: #01a4fe;
  449. }
  450. }
  451. /* 温馨提示 */
  452. .tips-card {
  453. margin: 0 12px;
  454. background: #fff;
  455. border-radius: 10px;
  456. padding: 15px;
  457. box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  458. .tips-title {
  459. font-size: 13px;
  460. color: #333;
  461. font-weight: 500;
  462. margin-bottom: 8px;
  463. }
  464. .tips-text {
  465. font-size: 12px;
  466. color: #666;
  467. line-height: 1.6;
  468. }
  469. }
  470. </style>