login.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <template>
  2. <view class="login-container">
  3. <!-- 顶部高级渐变背景 -->
  4. <view class="gradient-bg"></view>
  5. <view class="content-wrapper">
  6. <!-- Logo 区域 -->
  7. <view class="logo-section">
  8. <view class="logo-outer">
  9. <image class="logo-img" :src="assets.logo" mode="aspectFill"></image>
  10. </view>
  11. <text class="app-title">ERP 智能下单系统</text>
  12. <text class="app-subtitle">简洁 · 高效 · 数字化管理</text>
  13. </view>
  14. <!-- 按钮区域 -->
  15. <view class="action-section">
  16. <button class="main-btn" @click="startLoginFlow">
  17. <image class="btn-icon" :src="assets.wechat" mode="aspectFit"></image>
  18. <text>授权手机号码登录</text>
  19. </button>
  20. <view class="agreement-box">
  21. <label class="checkbox-label" @click="toggleAgreed">
  22. <checkbox :checked="isAgreed" color="#C1001C" style="transform:scale(0.7)" />
  23. <text class="agreement-text">我已阅读并同意
  24. <text class="link" @click.stop="showProtocol('user')">《用户协议》</text> 与
  25. <text class="link" @click.stop="showProtocol('privacy')">《隐私政策》</text>
  26. </text>
  27. </label>
  28. </view>
  29. </view>
  30. <!-- 页脚 -->
  31. <view class="footer-section">
  32. <text>© 2026 ERP Order System. All Rights Reserved.</text>
  33. </view>
  34. </view>
  35. <!-- 全局遮罩 -->
  36. <view class="global-mask" v-if="activeModal" @click="closeAllModals"></view>
  37. <!-- 1. 协议拦截确认弹窗 -->
  38. <view class="confirm-modal center-card" v-if="activeModal === 'confirm'">
  39. <view class="card-title">服务协议提示</view>
  40. <view class="card-body">请您阅读并同意我们的协议内容,以便为您提供更安全的服务体验。</view>
  41. <view class="card-footer-btns">
  42. <view class="btn-item cancel" @click="activeModal = ''">拒绝</view>
  43. <view class="btn-item agree" @click="agreeAndClose">同意并继续</view>
  44. </view>
  45. </view>
  46. <!-- 2. 头像昵称授权弹窗 (原生能力适配) -->
  47. <view class="simulated-profile-pop bottom-pop" v-if="activeModal === 'profile'">
  48. <view class="pop-header-bar">
  49. <text class="pop-cancel" @click="activeModal = ''">取消</text>
  50. <text class="pop-main-title">获取头像昵称</text>
  51. <text class="pop-done" @click="activeModal = 'phone'">保存</text>
  52. </view>
  53. <view class="profile-edit-content">
  54. <view class="avatar-edit-box">
  55. <!-- 使用微信原生头像选择能力 -->
  56. <button class="avatar-wrapper-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
  57. <image class="current-avatar" :src="userAvatar || 'https://img.icons8.com/color/144/user.png'"></image>
  58. <view class="camera-icon">
  59. <image src="https://img.icons8.com/ios-glyphs/30/999999/camera.png" mode="aspectFit"></image>
  60. </view>
  61. </button>
  62. <text class="edit-hint">点击修改头像</text>
  63. </view>
  64. <view class="nickname-edit-box">
  65. <text class="label">昵称</text>
  66. <!-- 使用微信原生昵称填写能力 -->
  67. <input class="nickname-input" type="nickname" :value="userName" placeholder="请输入昵称" @blur="onNicknameBlur" @input="onNicknameChange" />
  68. </view>
  69. <view class="auth-notice-box">
  70. <text class="notice-text">授权后,开发者将获得您的头像和昵称,用于展示您的个人资料。</text>
  71. </view>
  72. </view>
  73. <view class="bottom-action">
  74. <button class="confirm-btn-fixed" @click="activeModal = 'phone'">确定</button>
  75. </view>
  76. </view>
  77. <!-- 3. 模拟手机号授权 -->
  78. <view class="phone-auth-pop bottom-pop" v-if="activeModal === 'phone'">
  79. <view class="p-header">
  80. <image class="p-mini-logo" :src="assets.logo" mode="aspectFill"></image>
  81. <text class="p-app-name">ERP 智能下单系统 申请</text>
  82. </view>
  83. <view class="p-body">
  84. <text class="p-title">获取您的手机号</text>
  85. <view class="p-number-card">
  86. <view class="p-card-left">
  87. <text class="p-real-num">138****8888</text>
  88. <text class="p-num-hint">微信绑定号码</text>
  89. </view>
  90. <icon type="success" size="18" color="#C1001C" />
  91. </view>
  92. <text class="p-other-link">使用其他手机号码</text>
  93. </view>
  94. <view class="p-footer-btns">
  95. <button class="p-btn-fixed p-deny" @click="activeModal = ''">拒绝</button>
  96. <button class="p-btn-fixed p-allow" @click="completeLogin">允许</button>
  97. </view>
  98. </view>
  99. <!-- 4. 协议富文本弹窗 (样式专项修复) -->
  100. <view class="protocol-modal center-card" v-if="activeModal === 'protocol'">
  101. <view class="p-pop-header">
  102. <text class="p-pop-title">{{ currentProtocol.title }}</text>
  103. <text class="p-pop-close" @click="activeModal = ''">×</text>
  104. </view>
  105. <scroll-view scroll-y class="p-pop-scroll">
  106. <view class="rich-text-wrapper">
  107. <rich-text :nodes="currentProtocol.content"></rich-text>
  108. </view>
  109. </scroll-view>
  110. <view class="p-pop-footer">
  111. <button class="p-pop-btn" @click="activeModal = ''">我已了解</button>
  112. </view>
  113. </view>
  114. </view>
  115. </template>
  116. <script>
  117. import assets from '@/utils/assets.js';
  118. export default {
  119. data() {
  120. return {
  121. assets, isAgreed: false, activeModal: '',
  122. userAvatar: '', userName: '微信用户',
  123. redirectUrl: '',
  124. currentProtocol: { title: '', content: '' },
  125. protocols: {
  126. user: { title: '用户服务协议', content: '<div style="line-height:1.8;"><h4 style="margin-bottom:10px;">服务内容</h4><p>欢迎使用ERP系统,我们将为您提供高效的下单管理服务。本协议旨在明确双方权利与义务...</p></div>' },
  127. privacy: { title: '隐私政策', content: '<div style="line-height:1.8;"><h4 style="margin-bottom:10px;">隐私保护</h4><p>我们非常重视您的个人信息保护。我们会按照法律法规要求,采取相应的安全保护措施...</p></div>' }
  128. }
  129. }
  130. },
  131. onLoad(options) {
  132. if (options.redirect) {
  133. this.redirectUrl = decodeURIComponent(options.redirect);
  134. }
  135. },
  136. methods: {
  137. toggleAgreed() { this.isAgreed = !this.isAgreed; },
  138. startLoginFlow() {
  139. if (!this.isAgreed) this.activeModal = 'confirm';
  140. else this.activeModal = 'profile';
  141. },
  142. agreeAndClose() {
  143. this.isAgreed = true;
  144. this.activeModal = '';
  145. },
  146. completeLogin() {
  147. uni.showLoading({ title: '登录中...' });
  148. uni.setStorageSync('isLogin', true);
  149. setTimeout(() => {
  150. uni.hideLoading();
  151. this.activeModal = '';
  152. const targetUrl = this.redirectUrl || '/pages/index/index';
  153. uni.reLaunch({ url: targetUrl });
  154. }, 1000);
  155. },
  156. onChooseAvatar(e) {
  157. this.userAvatar = e.detail.avatarUrl;
  158. // 这里通常需要将临时路径上传到服务器
  159. },
  160. onNicknameBlur(e) {
  161. this.userName = e.detail.value;
  162. },
  163. onNicknameChange(e) {
  164. this.userName = e.detail.value;
  165. },
  166. showProtocol(type) { this.currentProtocol = this.protocols[type]; this.activeModal = 'protocol'; },
  167. closeAllModals() { this.activeModal = ''; }
  168. }
  169. }
  170. </script>
  171. <style scoped>
  172. /* 基础容器 */
  173. .login-container { width: 100%; min-height: 100vh; background: #fff; position: relative; display: flex; flex-direction: column; }
  174. .gradient-bg { position: absolute; top: 0; left: 0; right: 0; height: 600rpx; background: linear-gradient(180deg, rgba(193, 0, 28, 0.12) 0%, rgba(255, 255, 255, 0) 100%); z-index: 1; }
  175. .content-wrapper { position: relative; z-index: 2; flex: 1; display: flex; flex-direction: column; padding: 0 80rpx; box-sizing: border-box; }
  176. .logo-section { display: flex; flex-direction: column; align-items: center; margin-top: 360rpx; margin-bottom: 120rpx; }
  177. .logo-outer { width: 200rpx; height: 200rpx; background: #fff; border-radius: 48rpx; box-shadow: 0 40rpx 80rpx rgba(193, 0, 28, 0.35), 0 10rpx 30rpx rgba(0, 0, 0, 0.1), inset 0 4rpx 10rpx rgba(255,255,255,0.8); display: flex; align-items: center; justify-content: center; overflow: hidden; margin-bottom: 40rpx; }
  178. .logo-img { width: 100%; height: 100%; }
  179. .app-title { font-size: 48rpx; font-weight: bold; color: #1a1a1a; letter-spacing: 2rpx; }
  180. .app-subtitle { font-size: 26rpx; color: #999; margin-top: 10rpx; letter-spacing: 6rpx; }
  181. .main-btn { width: 100%; height: 100rpx; background: linear-gradient(135deg, #C1001C 0%, #FF4D4F 100%); border-radius: 50rpx; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: bold; box-shadow: 0 12rpx 30rpx rgba(193, 0, 28, 0.2); border: none; margin-bottom: 40rpx; }
  182. .btn-icon { width: 48rpx; height: 48rpx; margin-right: 16rpx; }
  183. .agreement-text { font-size: 24rpx; color: #999; }
  184. .link { color: #C1001C; margin: 0 4rpx; font-weight: 500; }
  185. .footer-section { margin-top: auto; padding-bottom: 60rpx; text-align: center; font-size: 20rpx; color: #dcdcdc; }
  186. /* 弹窗通用基础 */
  187. .global-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 998; }
  188. .center-card { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 620rpx; background: #fff; border-radius: 32rpx; z-index: 1000; box-shadow: 0 30rpx 80rpx rgba(0,0,0,0.15); padding: 50rpx 40rpx; display: flex; flex-direction: column; }
  189. .bottom-pop { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 40rpx 40rpx 0 0; z-index: 1001; padding: 40rpx; padding-bottom: calc(50rpx + env(safe-area-inset-bottom)); box-shadow: 0 -10rpx 40rpx rgba(0,0,0,0.05); }
  190. /* 按钮对齐辅助 */
  191. button { display: flex !important; align-items: center !important; justify-content: center !important; padding: 0 !important; line-height: normal !important; }
  192. button::after { border: none; }
  193. /* 协议拦截弹窗 */
  194. .card-title { font-size: 38rpx; font-weight: bold; text-align: center; margin-bottom: 30rpx; }
  195. .card-body { font-size: 28rpx; color: #666; line-height: 1.6; text-align: center; margin-bottom: 50rpx; }
  196. .card-footer-btns { display: flex; gap: 24rpx; }
  197. .btn-item { flex: 1; height: 90rpx; border-radius: 45rpx; font-size: 30rpx; display: flex !important; align-items: center !important; justify-content: center !important; text-align: center; line-height: 90rpx; }
  198. .btn-item.cancel { background: #f8f8f8; color: #999; }
  199. .btn-item.agree { background: #C1001C; color: #fff; font-weight: bold; }
  200. /* 协议内容弹窗专项修复 */
  201. .p-pop-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30rpx; }
  202. .p-pop-title { font-size: 36rpx; font-weight: bold; color: #1a1a1a; }
  203. .p-pop-close { font-size: 48rpx; color: #ccc; padding: 10rpx; }
  204. .p-pop-scroll { max-height: 55vh; margin-bottom: 30rpx; }
  205. .rich-text-wrapper { padding: 10rpx 0; color: #444; font-size: 28rpx; }
  206. .p-pop-footer { padding-top: 20rpx; }
  207. .p-pop-btn { width: 100%; height: 90rpx; background: #C1001C; color: #fff; border-radius: 45rpx; font-size: 30rpx; font-weight: bold; }
  208. /* 头像授权弹窗 */
  209. .pop-header-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 60rpx; }
  210. .pop-cancel { font-size: 30rpx; color: #999; }
  211. .pop-main-title { font-size: 32rpx; font-weight: bold; }
  212. .pop-done { font-size: 30rpx; color: #C1001C; font-weight: bold; }
  213. .profile-edit-content { display: flex; flex-direction: column; align-items: center; }
  214. .avatar-wrapper-btn {
  215. width: 170rpx; height: 170rpx; border-radius: 85rpx; background: #f8f8f8;
  216. position: relative; margin-bottom: 24rpx; padding: 0 !important; overflow: visible;
  217. display: flex !important; align-items: center; justify-content: center;
  218. border: none;
  219. }
  220. .current-avatar { width: 100%; height: 100%; border-radius: 85rpx; border: 4rpx solid #fff; box-shadow: 0 4rpx 15rpx rgba(0,0,0,0.05); }
  221. .camera-icon {
  222. position: absolute; bottom: 0; right: 0; background: #fff; width: 56rpx; height: 56rpx;
  223. border-radius: 28rpx; display: flex; align-items: center; justify-content: center;
  224. box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.12); z-index: 5;
  225. }
  226. .camera-icon image { width: 30rpx; height: 30rpx; }
  227. .edit-hint { font-size: 24rpx; color: #999; margin-bottom: 70rpx; width: 100%; text-align: center; display: block; }
  228. .nickname-edit-box { width: 100%; display: flex; align-items: center; padding: 36rpx 0; border-top: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0; margin-bottom: 40rpx; }
  229. .nickname-edit-box .label { width: 130rpx; font-size: 32rpx; }
  230. .nickname-input { flex: 1; font-size: 32rpx; }
  231. .notice-text { font-size: 24rpx; color: #bfbfbf; text-align: center; display: block; margin-bottom: 60rpx; }
  232. .confirm-btn-fixed { width: 100%; height: 96rpx; background: #C1001C; color: #fff; border-radius: 16rpx; font-size: 32rpx; font-weight: bold; }
  233. /* 手机号授权弹窗 */
  234. .p-header { display: flex; align-items: center; margin-bottom: 50rpx; }
  235. .p-mini-logo { width: 44rpx; height: 44rpx; border-radius: 8rpx; margin-right: 16rpx; }
  236. .p-app-name { font-size: 28rpx; color: #7f7f7f; }
  237. .p-title { font-size: 40rpx; font-weight: bold; color: #000; margin-bottom: 44rpx; display: block; }
  238. .p-number-card { background: #fbfbfb; padding: 36rpx; border-radius: 20rpx; display: flex; justify-content: space-between; align-items: center; margin-bottom: 30rpx; border: 1rpx solid #f0f0f0; }
  239. .p-real-num { font-size: 36rpx; font-weight: bold; color: #1a1a1a; display: block; }
  240. .p-num-hint { font-size: 24rpx; color: #999; }
  241. .p-other-link { font-size: 28rpx; color: #576b95; display: block; margin-bottom: 60rpx; }
  242. .p-footer-btns { display: flex; gap: 30rpx; }
  243. .p-btn-fixed { flex: 1; height: 96rpx; border-radius: 16rpx; font-size: 32rpx; border: none; }
  244. .p-deny { background: #f2f2f2; color: #C1001C; }
  245. .p-allow { background: #C1001C; color: #fff; font-weight: bold; }
  246. </style>