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

424 lines
11 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. <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. </view>
  87. <!-- 温馨提示 -->
  88. <view class="tips-card">
  89. <view class="tips-title">温馨提示</view>
  90. <view class="tips-text">
  91. 1. 请填写真实身份信息用于办理读者证<br>
  92. 2. 密码请牢记用于登录和借阅查询<br>
  93. <!-- 3. 提交后工作人员将尽快审核 -->
  94. </view>
  95. </view>
  96. </view>
  97. </template>
  98. <script>
  99. import config from '@/utils/config';
  100. import { getOpenId } from '@/utils/storage';
  101. import { FetchSessionKey, FetchDecryptPhone } from '@/api/user.js';
  102. export default {
  103. data() {
  104. return {
  105. sessionKey: '',
  106. openid: '',
  107. formData: {
  108. name: '',
  109. idCard: '',
  110. phone: '',
  111. password: '',
  112. confirmPassword: ''
  113. },
  114. showPwd: false,
  115. showConfirmPwd: false,
  116. loading: false
  117. };
  118. },
  119. onLoad() {
  120. this.getSessionKey();
  121. },
  122. methods: {
  123. async getSessionKey() {
  124. try {
  125. uni.login({
  126. success: async (loginRes) => {
  127. console.log('loginRes',loginRes)
  128. if (loginRes.code) {
  129. const res = await FetchSessionKey({
  130. libcode: config.LIB_CODE,
  131. code: loginRes.code
  132. });
  133. if (res.code === 200) {
  134. this.sessionKey = res.data.session_key;
  135. this.openid = res.data.openid;
  136. }
  137. }
  138. }
  139. });
  140. } catch (error) {
  141. console.error('获取sessionKey失败:', error);
  142. }
  143. },
  144. getPhoneNumber(e) {
  145. if (e.detail.errMsg !== 'getPhoneNumber:ok') {
  146. uni.showToast({ title: '取消授权', icon: 'none' });
  147. return;
  148. }
  149. if (!this.sessionKey) {
  150. uni.showToast({ title: '请稍后再试', icon: 'none' });
  151. return;
  152. }
  153. FetchDecryptPhone({
  154. sessionKey: this.sessionKey,
  155. encryptedData: e.detail.encryptedData,
  156. iv: e.detail.iv
  157. }).then(res => {
  158. if (res.code === 200) {
  159. this.formData.phone = res.data.phoneNumber || '';
  160. uni.showToast({ title: '获取成功', icon: 'success' });
  161. } else {
  162. uni.showToast({ title: res.message || '获取失败', icon: 'none' });
  163. }
  164. }).catch(() => {
  165. uni.showToast({ title: '获取手机号失败', icon: 'none' });
  166. });
  167. },
  168. togglePwd() {
  169. this.showPwd = !this.showPwd;
  170. },
  171. toggleConfirmPwd() {
  172. this.showConfirmPwd = !this.showConfirmPwd;
  173. },
  174. // 身份证验证(支持15位和18位,自动去除空格和连字符)
  175. validateIdCard(idCard) {
  176. if (!idCard) return false;
  177. // 去除空格和连字符
  178. idCard = idCard.replace(/[\s-]/g, '');
  179. // 15位身份证格式验证
  180. const idCard15Reg = /^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/;
  181. // 18位身份证格式验证
  182. 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]$/;
  183. // 15位身份证直接验证格式
  184. if (idCard.length === 15) {
  185. return idCard15Reg.test(idCard);
  186. }
  187. // 18位身份证需要验证格式、日期和校验码
  188. if (idCard.length !== 18 || !idCard18Reg.test(idCard)) {
  189. return false;
  190. }
  191. // 验证日期有效性(宽松模式:跳过校验码验证)
  192. const year = +idCard.substring(6, 10);
  193. const month = +idCard.substring(10, 12);
  194. const day = +idCard.substring(12, 14);
  195. const date = new Date(year, month - 1, day);
  196. return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
  197. },
  198. // 手机号验证
  199. validatePhone(phone) {
  200. return /^1[3-9]\d{9}$/.test(phone);
  201. },
  202. // ✅ 【核心修复】表单验证(更严谨)
  203. validateForm() {
  204. const { name, idCard, phone, password, confirmPassword } = this.formData;
  205. // 1. 姓名:必须非空且trim后有内容
  206. if (!name || name.trim() === '') {
  207. uni.showToast({ title: '请输入姓名', icon: 'none' });
  208. return false;
  209. }
  210. // 2. 身份证:非空 + 格式正确
  211. if (!idCard || idCard.trim() === '') {
  212. uni.showToast({ title: '请输入身份证号', icon: 'none' });
  213. return false;
  214. }
  215. if (!this.validateIdCard(idCard.trim())) {
  216. uni.showToast({ title: '身份证号格式不正确', icon: 'none' });
  217. return false;
  218. }
  219. // 3. 手机号:非空 + 格式正确
  220. if (!phone || phone.trim() === '') {
  221. uni.showToast({ title: '请输入手机号', icon: 'none' });
  222. return false;
  223. }
  224. if (!this.validatePhone(phone.trim())) {
  225. uni.showToast({ title: '手机号格式不正确', icon: 'none' });
  226. return false;
  227. }
  228. // 4. 密码:至少6位
  229. if (!password || password.length < 6) {
  230. uni.showToast({ title: '密码至少6位', icon: 'none' });
  231. return false;
  232. }
  233. // 5. 确认密码:必须一致
  234. if (password !== confirmPassword) {
  235. uni.showToast({ title: '两次密码不一致', icon: 'none' });
  236. return false;
  237. }
  238. // 所有校验通过
  239. return true;
  240. },
  241. async submit() {
  242. // 防止重复点击
  243. if (this.loading) return;
  244. // 小程序关键:等 input 数据同步到 data
  245. await this.$nextTick();
  246. // 校验不通过直接 return
  247. if (!this.validateForm()) {
  248. return;
  249. }
  250. this.loading = true;
  251. try {
  252. const openId = await getOpenId();
  253. if (!openId) {
  254. uni.showToast({ title: '获取用户信息失败', icon: 'none' });
  255. return;
  256. }
  257. uni.showToast({ title: '后续流程正在研发中,请稍后试用~', icon: 'none' });
  258. setTimeout(() => uni.navigateBack(), 1500);
  259. // 提交(trim 后再发)
  260. // const res = await uni.request({
  261. // url: config.baseUrl + '/api/reader/register',
  262. // method: 'POST',
  263. // data: {
  264. // name: this.formData.name.trim(),
  265. // idCard: this.formData.idCard.trim(),
  266. // phone: this.formData.phone.trim(),
  267. // password: this.formData.password,
  268. // openId,
  269. // libcode: config.LIB_CODE
  270. // }
  271. // });
  272. // if (res.data?.code === 200) {
  273. // uni.showToast({ title: '办理成功', icon: 'success' });
  274. // setTimeout(() => uni.navigateBack(), 1500);
  275. // } else {
  276. // uni.showToast({ title: res.data?.message || '提交失败', icon: 'none' });
  277. // }
  278. } catch (err) {
  279. console.error('提交异常', err);
  280. uni.showToast({ title: '网络异常,请重试', icon: 'none' });
  281. } finally {
  282. this.loading = false;
  283. }
  284. }
  285. }
  286. };
  287. </script>
  288. <style lang="scss" scoped>
  289. page {
  290. background-color: #f5f7fa;
  291. }
  292. .page-container {
  293. min-height: 100vh;
  294. background-color: #f5f7fa;
  295. padding-bottom: 20px;
  296. }
  297. /* 顶部 */
  298. .top-bar {
  299. height: 210px;
  300. .library-info {
  301. position: relative;
  302. z-index: 2;
  303. color: #fff;
  304. }
  305. }
  306. /* 表单卡片 */
  307. .form-card {
  308. margin: -30px 12px 12px;
  309. background: #fff;
  310. border-radius: 12px;
  311. padding: 20px 16px;
  312. box-shadow: 0 4px 15px rgba(0,0,0,0.08);
  313. position: relative;
  314. z-index: 3;
  315. }
  316. .form-item {
  317. display: flex;
  318. align-items: center;
  319. height: 44px;
  320. border-bottom: 1rpx solid #f2f3f5;
  321. margin-bottom: 4px;
  322. .form-icon {
  323. font-size: 14px;
  324. color: #01a4fe;
  325. margin-right: 10px;
  326. }
  327. .input {
  328. flex: 1;
  329. font-size: 14px;
  330. color: #333;
  331. }
  332. .pwd-icon {
  333. color: #999;
  334. padding: 5px;
  335. }
  336. .get-phone-btn {
  337. font-size: 12px;
  338. color: #fff;
  339. background-color: #01a4fe;
  340. border-radius: 20px;
  341. padding: 6px 10px;
  342. border: none;
  343. line-height: 1.2;
  344. &::after {
  345. border: none;
  346. }
  347. }
  348. }
  349. /* 提交按钮 */
  350. .submit-btn {
  351. width: 100%;
  352. height: 44px;
  353. background-color: #01a4fe;
  354. color: #fff;
  355. border-radius: 22px;
  356. font-size: 15px;
  357. margin-top: 20px;
  358. border: none;
  359. &::after {
  360. border: none;
  361. }
  362. }
  363. /* 温馨提示 */
  364. .tips-card {
  365. margin: 0 12px;
  366. background: #fff;
  367. border-radius: 10px;
  368. padding: 15px;
  369. box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  370. .tips-title {
  371. font-size: 13px;
  372. color: #333;
  373. font-weight: 500;
  374. margin-bottom: 8px;
  375. }
  376. .tips-text {
  377. font-size: 12px;
  378. color: #666;
  379. line-height: 1.6;
  380. }
  381. }
  382. </style>