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

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