login.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. <template>
  2. <view class="login-container">
  3. <!-- 顶部导航栏 -->
  4. <view class="nav-bar">
  5. <text class="nav-title">登录</text>
  6. </view>
  7. <!-- Logo 区域 -->
  8. <view class="logo-section">
  9. <view class="logo-icon">
  10. <!-- 蓝色圆角正方形 logo -->
  11. </view>
  12. <text class="app-name">布尔销销乐</text>
  13. </view>
  14. <!-- 表单区域 -->
  15. <view class="form-section">
  16. <!-- 手机号输入 -->
  17. <view class="input-item">
  18. <uni-easyinput
  19. v-model="phoneNumber"
  20. placeholder="请输入手机号"
  21. type="number"
  22. maxlength="11"
  23. :clearable="false"
  24. class="phone-input"
  25. />
  26. </view>
  27. <!-- 验证码输入 -->
  28. <view class="input-item verify-code-row">
  29. <uni-easyinput
  30. v-model="verifyCode"
  31. placeholder="验证码"
  32. type="number"
  33. maxlength="6"
  34. :clearable="false"
  35. class="code-input"
  36. />
  37. <text
  38. class="get-code-btn"
  39. :class="{ disabled: countdown > 0 }"
  40. @click="getVerifyCode"
  41. >
  42. {{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
  43. </text>
  44. </view>
  45. <!-- 登录按钮 -->
  46. <view class="login-btn-wrapper">
  47. <button class="login-btn" @click="handleLogin" :loading="isLoading">登录</button>
  48. </view>
  49. <!-- 协议说明 -->
  50. <view class="agreement-text">
  51. <text class="gray-text">登录即代表您已同意</text>
  52. <text class="link-text" @click="showUserAgreement">《用户协议》</text>
  53. <text class="gray-text">与</text>
  54. <text class="link-text" @click="showPrivacyPolicy">《隐私政策》</text>
  55. </view>
  56. </view>
  57. <!-- 其他登录方式 -->
  58. <view class="other-method-section">
  59. <view class="divider">
  60. <view class="divider-line"></view>
  61. <text class="divider-text">其他方式</text>
  62. <view class="divider-line"></view>
  63. </view>
  64. <!-- 微信一键登录 -->
  65. <view class="wechat-login-wrapper">
  66. <button class="wechat-login-btn" @click="wechatLogin">
  67. <text class="wechat-icon"></text>
  68. <text class="wechat-text">微信一键登录</text>
  69. </button>
  70. </view>
  71. </view>
  72. <!-- 用户协议弹窗 -->
  73. <view class="agreement-popup" v-if="showUserAgreementPopup" @click="closeUserAgreement">
  74. <view class="popup-mask"></view>
  75. <view class="popup-content" @click.stop>
  76. <view class="popup-header">
  77. <text class="popup-title">用户协议</text>
  78. <text class="popup-close" @click="closeUserAgreement">✕</text>
  79. </view>
  80. <scroll-view scroll-y class="popup-body">
  81. <view class="agreement-content">
  82. <text class="agreement-title">用户协议</text>
  83. <text class="update-time">最后更新时间:2026 年 4 月 24 日</text>
  84. <view class="section">
  85. <text class="section-title">一、协议的接受与修改</text>
  86. <text class="section-content">
  87. 1.1 欢迎您使用布尔销销乐服务。请您仔细阅读本用户协议,以确保您了解我们的产品或服务,以及本协议规定的双方的权利和义务。\n
  88. 1.2 当您使用本服务时,即表示您已阅读、理解并同意接受本协议的所有条款。如果您不同意本协议的任何条款,请立即停止使用本服务。\n
  89. 1.3 我们有权根据需要不时修改本协议条款,修改后的协议一旦公布即有效代替原来的协议。您可随时查阅最新协议。
  90. </text>
  91. </view>
  92. <view class="section">
  93. <text class="section-title">二、服务内容</text>
  94. <text class="section-content">
  95. 2.1 布尔销销乐是一款面向企业的客户关系管理工具,提供客户管理、销售跟进、数据分析等功能。\n
  96. 2.2 我们有权对服务内容进行变更、中断或终止,变更、中断或终止服务前将尽可能提前通知用户。
  97. </text>
  98. </view>
  99. <view class="section">
  100. <text class="section-title">三、用户账号</text>
  101. <text class="section-content">
  102. 3.1 您需要通过手机号验证码登录或微信授权登录方式使用本服务。\n
  103. 3.2 您应保证提供的信息真实、准确、完整。\n
  104. 3.3 您应妥善保管您的账号信息,因您保管不善导致账号被盗、密码失密或遭受其他损失的,您应自行承担相应责任。
  105. </text>
  106. </view>
  107. <view class="section">
  108. <text class="section-title">四、用户行为规范</text>
  109. <text class="section-content">
  110. 4.1 您在使用本服务时,应遵守国家法律法规,不得利用本服务从事任何违法违规活动。\n
  111. 4.2 您不得利用本服务制作、复制、发布、传播含有下列内容的信息:\n
  112. (1) 反对宪法所确定的基本原则的;\n
  113. (2) 危害国家安全、泄露国家秘密的;\n
  114. (3) 损害国家荣誉和利益的;\n
  115. (4) 煽动民族仇恨、民族歧视的;\n
  116. (5) 破坏国家宗教政策,宣扬邪教和封建迷信的;\n
  117. (6) 散布谣言、淫秽色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的。
  118. </text>
  119. </view>
  120. <view class="section">
  121. <text class="section-title">五、知识产权</text>
  122. <text class="section-content">
  123. 5.1 本服务包含的所有内容(包括但不限于文字、图片、音频、视频、软件、程序、代码、界面设计等)的知识产权均归我们所有。\n
  124. 5.2 未经我们书面许可,您不得以任何形式复制、传播、展示、修改本服务的内容。
  125. </text>
  126. </view>
  127. <view class="section">
  128. <text class="section-title">六、免责声明</text>
  129. <text class="section-content">
  130. 6.1 因不可抗力(如自然灾害、政府行为、社会异常事件等)导致服务中断或数据丢失,我们不承担责任。\n
  131. 6.2 因网络状况、通讯线路、黑客攻击、计算机病毒、政府管制等任何非我们原因导致的服务中断或数据丢失,我们不承担责任。
  132. </text>
  133. </view>
  134. <view class="section">
  135. <text class="section-title">七、协议终止</text>
  136. <text class="section-content">
  137. 7.1 您有权随时停止使用本服务并注销账号。\n
  138. 7.2 如您违反本协议,我们有权终止向您提供服务,并保留追究法律责任的权利。
  139. </text>
  140. </view>
  141. <view class="section">
  142. <text class="section-title">八、法律适用与争议解决</text>
  143. <text class="section-content">
  144. 8.1 本协议的成立、生效、履行、解释及争议解决均适用中华人民共和国法律。\n
  145. 8.2 如双方就本协议内容或其执行发生任何争议,应友好协商解决;协商不成的,任何一方均可向我们所在地有管辖权的人民法院提起诉讼。
  146. </text>
  147. </view>
  148. <view class="section">
  149. <text class="section-title">九、联系我们</text>
  150. <text class="section-content">
  151. 如您对本协议有任何疑问,请通过以下方式联系我们:\n
  152. 客服电话:400-XXX-XXXX\n
  153. 客服邮箱:support@example.com
  154. </text>
  155. </view>
  156. </view>
  157. </scroll-view>
  158. <view class="popup-footer">
  159. <button class="agree-btn" @click="closeUserAgreement">我已知晓</button>
  160. </view>
  161. </view>
  162. </view>
  163. <!-- 隐私政策弹窗 -->
  164. <view class="agreement-popup" v-if="showPrivacyPolicyPopup" @click="closePrivacyPolicy">
  165. <view class="popup-mask"></view>
  166. <view class="popup-content" @click.stop>
  167. <view class="popup-header">
  168. <text class="popup-title">隐私政策</text>
  169. <text class="popup-close" @click="closePrivacyPolicy">✕</text>
  170. </view>
  171. <scroll-view scroll-y class="popup-body">
  172. <view class="agreement-content">
  173. <text class="agreement-title">隐私政策</text>
  174. <text class="update-time">最后更新时间:2026 年 4 月 24 日</text>
  175. <view class="section">
  176. <text class="section-title">一、我们如何收集和使用您的个人信息</text>
  177. <text class="section-content">
  178. 1.1 为了向您提供服务,我们可能会收集以下信息:\n
  179. (1) 手机号码:用于账号注册和登录验证;\n
  180. (2) 微信昵称、头像:用于完善您的个人资料(仅在微信授权登录时收集);\n
  181. (3) 设备信息:用于保障服务安全和优化用户体验。\n
  182. 1.2 我们收集的信息仅用于提供服务、改进产品质量和保障账号安全,不会用于其他用途。
  183. </text>
  184. </view>
  185. <view class="section">
  186. <text class="section-title">二、我们如何共享、转让、公开披露您的个人信息</text>
  187. <text class="section-content">
  188. 2.1 我们不会与任何公司、组织和个人共享您的个人信息,但以下情况除外:\n
  189. (1) 事先获得您的明确同意;\n
  190. (2) 根据法律法规或政府主管部门的强制性要求。\n
  191. 2.2 我们不会转让您的个人信息给任何第三方。\n
  192. 2.3 我们不会公开披露您的个人信息,除非获得您的明确同意或基于法律规定。
  193. </text>
  194. </view>
  195. <view class="section">
  196. <text class="section-title">三、我们如何保护您的个人信息</text>
  197. <text class="section-content">
  198. 3.1 我们已使用符合业界标准的安全防护措施保护您的个人信息。\n
  199. 3.2 我们采取数据加密、访问控制等技术措施防止数据遭到未经授权的访问、使用、修改或泄露。\n
  200. 3.3 我们仅允许有必要知晓这些信息的员工访问您的个人信息,并为此设置了严格的访问权限控制。
  201. </text>
  202. </view>
  203. <view class="section">
  204. <text class="section-title">四、您的权利</text>
  205. <text class="section-content">
  206. 4.1 您有权访问、更正、删除您的个人信息。\n
  207. 4.2 您有权改变您授权同意的范围或撤回您的授权。\n
  208. 4.3 您有权注销您的账号。
  209. </text>
  210. </view>
  211. <view class="section">
  212. <text class="section-title">五、我们如何处理未成年人的个人信息</text>
  213. <text class="section-content">
  214. 5.1 我们非常重视对未成年人个人信息的保护。\n
  215. 5.2 若您是 18 周岁以下的未成年人,在使用本服务前,应事先取得您家长或法定监护人的书面同意。
  216. </text>
  217. </view>
  218. <view class="section">
  219. <text class="section-title">六、隐私政策的更新</text>
  220. <text class="section-content">
  221. 6.1 我们可能会适时对本隐私政策进行修订。\n
  222. 6.2 当本政策的条款发生变更时,我们会在版本更新时以适当的方式向您提示变更后的内容。
  223. </text>
  224. </view>
  225. <view class="section">
  226. <text class="section-title">七、联系我们</text>
  227. <text class="section-content">
  228. 如您对本隐私政策有任何疑问,请通过以下方式联系我们:\n
  229. 客服电话:400-XXX-XXXX\n
  230. 客服邮箱:support@example.com
  231. </text>
  232. </view>
  233. </view>
  234. </scroll-view>
  235. <view class="popup-footer">
  236. <button class="agree-btn" @click="closePrivacyPolicy">我已知晓</button>
  237. </view>
  238. </view>
  239. </view>
  240. </view>
  241. </template>
  242. <script setup>
  243. import { ref } from 'vue'
  244. import { sendVerifyCode, loginByPhone } from '@/api/user.js'
  245. import { saveUserInfo } from '@/utils/userCache.js'
  246. // 表单数据
  247. const phoneNumber = ref('')
  248. const verifyCode = ref('')
  249. const countdown = ref(0)
  250. const isLoading = ref(false)
  251. let timer = null
  252. // 弹窗控制
  253. const showUserAgreementPopup = ref(false)
  254. const showPrivacyPolicyPopup = ref(false)
  255. // 获取验证码
  256. const getVerifyCode = async () => {
  257. if (countdown.value > 0) return
  258. if (!phoneNumber.value || phoneNumber.value.length !== 11) {
  259. uni.showToast({
  260. title: '请输入正确的手机号',
  261. icon: 'none'
  262. })
  263. return
  264. }
  265. try {
  266. const res = await sendVerifyCode(phoneNumber.value)
  267. console.log('验证码发送成功:', res)
  268. // 开始倒计时
  269. countdown.value = 60
  270. timer = setInterval(() => {
  271. countdown.value--
  272. if (countdown.value <= 0) {
  273. clearInterval(timer)
  274. }
  275. }, 1000)
  276. uni.showToast({
  277. title: '验证码已发送',
  278. icon: 'success'
  279. })
  280. } catch (error) {
  281. console.error('发送验证码失败:', error)
  282. }
  283. }
  284. // 处理登录
  285. const handleLogin = async () => {
  286. if (isLoading.value) return
  287. if (!phoneNumber.value || phoneNumber.value.length !== 11) {
  288. uni.showToast({
  289. title: '请输入正确的手机号',
  290. icon: 'none'
  291. })
  292. return
  293. }
  294. if (!verifyCode.value || verifyCode.value.length !== 6) {
  295. uni.showToast({
  296. title: '请输入验证码',
  297. icon: 'none'
  298. })
  299. return
  300. }
  301. isLoading.value = true
  302. try {
  303. const res = await loginByPhone(phoneNumber.value, verifyCode.value)
  304. console.log('登录成功:', res)
  305. // 保存用户信息和 token
  306. const { userInfo, token } = res.data || res
  307. const saveSuccess = saveUserInfo(userInfo, token)
  308. if (saveSuccess) {
  309. uni.showToast({
  310. title: '登录成功',
  311. icon: 'success'
  312. })
  313. // 延迟跳转
  314. setTimeout(() => {
  315. uni.reLaunch({
  316. url: '/pages/index/index'
  317. })
  318. }, 1500)
  319. } else {
  320. uni.showToast({
  321. title: '保存登录信息失败',
  322. icon: 'none'
  323. })
  324. }
  325. } catch (error) {
  326. console.error('登录失败:', error)
  327. } finally {
  328. isLoading.value = false
  329. }
  330. }
  331. // 微信登录
  332. const wechatLogin = () => {
  333. // #ifdef MP-WEIXIN
  334. uni.getUserProfile({
  335. desc: '用于完善用户资料',
  336. success: (res) => {
  337. console.log('微信授权成功:', res.userInfo)
  338. // TODO: 调用微信登录接口
  339. uni.showToast({
  340. title: '微信授权成功',
  341. icon: 'success'
  342. })
  343. },
  344. fail: (err) => {
  345. console.log('微信授权失败:', err)
  346. }
  347. })
  348. // #endif
  349. // #ifndef MP-WEIXIN
  350. uni.showToast({
  351. title: '请在微信小程序中使用',
  352. icon: 'none'
  353. })
  354. // #endif
  355. }
  356. // 显示用户协议
  357. const showUserAgreement = () => {
  358. showUserAgreementPopup.value = true
  359. }
  360. // 关闭用户协议
  361. const closeUserAgreement = () => {
  362. showUserAgreementPopup.value = false
  363. }
  364. // 显示隐私政策
  365. const showPrivacyPolicy = () => {
  366. showPrivacyPolicyPopup.value = true
  367. }
  368. // 关闭隐私政策
  369. const closePrivacyPolicy = () => {
  370. showPrivacyPolicyPopup.value = false
  371. }
  372. // 页面卸载时清除定时器
  373. import { onUnmounted } from 'vue'
  374. onUnmounted(() => {
  375. if (timer) {
  376. clearInterval(timer)
  377. }
  378. })
  379. </script>
  380. <style lang="scss" scoped>
  381. .login-container {
  382. min-height: 100vh;
  383. background-color: #ffffff;
  384. padding: 0 40rpx;
  385. position: relative;
  386. }
  387. // 导航栏
  388. .nav-bar {
  389. height: 88rpx;
  390. display: flex;
  391. align-items: center;
  392. justify-content: center;
  393. .nav-title {
  394. font-size: 34rpx;
  395. font-weight: 600;
  396. color: #000000;
  397. }
  398. }
  399. // Logo 区域
  400. .logo-section {
  401. display: flex;
  402. flex-direction: column;
  403. align-items: center;
  404. margin-top: 60rpx;
  405. margin-bottom: 80rpx;
  406. .logo-icon {
  407. width: 160rpx;
  408. height: 160rpx;
  409. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  410. border-radius: 32rpx;
  411. box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3);
  412. }
  413. .app-name {
  414. font-size: 40rpx;
  415. font-weight: 600;
  416. color: #000000;
  417. margin-top: 32rpx;
  418. }
  419. }
  420. // 表单区域
  421. .form-section {
  422. .input-item {
  423. margin-bottom: 24rpx;
  424. .phone-input, .code-input {
  425. background-color: #f5f7fa;
  426. border-radius: 16rpx;
  427. font-size: 28rpx;
  428. }
  429. }
  430. .verify-code-row {
  431. display: flex;
  432. align-items: center;
  433. .code-input {
  434. flex: 1;
  435. margin-right: 20rpx;
  436. }
  437. .get-code-btn {
  438. font-size: 26rpx;
  439. color: #1890ff;
  440. white-space: nowrap;
  441. &.disabled {
  442. color: #999999;
  443. }
  444. }
  445. }
  446. .login-btn-wrapper {
  447. margin-top: 48rpx;
  448. margin-bottom: 24rpx;
  449. .login-btn {
  450. width: 100%;
  451. height: 88rpx;
  452. background: linear-gradient(90deg, #1890ff 0%, #096dd9 100%);
  453. border-radius: 16rpx;
  454. border: none;
  455. font-size: 32rpx;
  456. font-weight: 500;
  457. color: #ffffff;
  458. display: flex;
  459. align-items: center;
  460. justify-content: center;
  461. &::after {
  462. border: none;
  463. }
  464. }
  465. }
  466. .agreement-text {
  467. display: flex;
  468. flex-wrap: wrap;
  469. justify-content: center;
  470. font-size: 22rpx;
  471. .gray-text {
  472. color: #999999;
  473. margin: 0 4rpx;
  474. }
  475. .link-text {
  476. color: #1890ff;
  477. margin: 0 4rpx;
  478. }
  479. }
  480. }
  481. // 其他登录方式
  482. .other-method-section {
  483. margin-top: 120rpx;
  484. .divider {
  485. display: flex;
  486. align-items: center;
  487. justify-content: center;
  488. margin-bottom: 40rpx;
  489. .divider-line {
  490. width: 120rpx;
  491. height: 1rpx;
  492. background-color: #e5e5e5;
  493. }
  494. .divider-text {
  495. font-size: 24rpx;
  496. color: #999999;
  497. margin: 0 24rpx;
  498. }
  499. }
  500. .wechat-login-wrapper {
  501. .wechat-login-btn {
  502. width: 100%;
  503. height: 88rpx;
  504. background: linear-gradient(90deg, #07c160 0%, #06ad56 100%);
  505. border-radius: 16rpx;
  506. border: none;
  507. font-size: 32rpx;
  508. font-weight: 500;
  509. color: #ffffff;
  510. display: flex;
  511. align-items: center;
  512. justify-content: center;
  513. &::after {
  514. border: none;
  515. }
  516. .wechat-icon {
  517. margin-right: 12rpx;
  518. font-size: 36rpx;
  519. }
  520. .wechat-text {
  521. color: #ffffff;
  522. }
  523. }
  524. }
  525. }
  526. // 协议弹窗
  527. .agreement-popup {
  528. position: fixed;
  529. top: 0;
  530. left: 0;
  531. right: 0;
  532. bottom: 0;
  533. z-index: 1000;
  534. .popup-mask {
  535. position: absolute;
  536. top: 0;
  537. left: 0;
  538. right: 0;
  539. bottom: 0;
  540. background-color: rgba(0, 0, 0, 0.5);
  541. }
  542. .popup-content {
  543. position: absolute;
  544. bottom: 0;
  545. left: 0;
  546. right: 0;
  547. background-color: #ffffff;
  548. border-radius: 32rpx 32rpx 0 0;
  549. max-height: 80vh;
  550. display: flex;
  551. flex-direction: column;
  552. animation: slideUp 0.3s ease-out;
  553. .popup-header {
  554. display: flex;
  555. align-items: center;
  556. justify-content: space-between;
  557. padding: 32rpx;
  558. border-bottom: 1rpx solid #f0f0f0;
  559. .popup-title {
  560. font-size: 32rpx;
  561. font-weight: 600;
  562. color: #000000;
  563. }
  564. .popup-close {
  565. font-size: 40rpx;
  566. color: #999999;
  567. width: 60rpx;
  568. height: 60rpx;
  569. display: flex;
  570. align-items: center;
  571. justify-content: center;
  572. }
  573. }
  574. .popup-body {
  575. flex: 1;
  576. overflow-y: auto;
  577. padding: 32rpx;
  578. .agreement-content {
  579. display: flex;
  580. flex-direction: column;
  581. .agreement-title {
  582. font-size: 36rpx;
  583. font-weight: 600;
  584. color: #000000;
  585. display: block;
  586. text-align: center;
  587. margin-bottom: 20rpx;
  588. }
  589. .update-time {
  590. font-size: 24rpx;
  591. color: #999999;
  592. display: block;
  593. text-align: center;
  594. margin-bottom: 40rpx;
  595. }
  596. .section {
  597. margin-bottom: 32rpx;
  598. .section-title {
  599. font-size: 28rpx;
  600. font-weight: 600;
  601. color: #000000;
  602. display: block;
  603. margin-bottom: 16rpx;
  604. }
  605. .section-content {
  606. font-size: 24rpx;
  607. color: #333333;
  608. line-height: 1.8;
  609. display: block;
  610. white-space: pre-wrap;
  611. }
  612. }
  613. }
  614. }
  615. .popup-footer {
  616. padding: 24rpx 32rpx 40rpx;
  617. border-top: 1rpx solid #f0f0f0;
  618. .agree-btn {
  619. width: 100%;
  620. height: 80rpx;
  621. background: linear-gradient(90deg, #1890ff 0%, #096dd9 100%);
  622. border-radius: 16rpx;
  623. border: none;
  624. font-size: 30rpx;
  625. font-weight: 500;
  626. color: #ffffff;
  627. display: flex;
  628. align-items: center;
  629. justify-content: center;
  630. &::after {
  631. border: none;
  632. }
  633. }
  634. }
  635. }
  636. }
  637. @keyframes slideUp {
  638. from {
  639. transform: translateY(100%);
  640. }
  641. to {
  642. transform: translateY(0);
  643. }
  644. }
  645. </style>