zhaoyang 4 settimane fa
parent
commit
470e2beb76

+ 12 - 0
api/login.js

@@ -57,3 +57,15 @@ export function getCodeImg() {
     timeout: 20000
   })
 }
+
+// 发送短信
+export function sendSmsCode(phone) {
+  return request({
+    url: '/ali/sendSms',
+    headers: {
+      isToken: false
+    },
+    method: 'get',
+    data: { phone }
+  })
+}

+ 25 - 0
api/setUserInfo.js

@@ -0,0 +1,25 @@
+import request from '@/utils/request'
+
+// 获取律师事务所列表
+export function getFirmList() {
+  return request({
+    url: '/lawyer/firm/getList',
+    method: 'get'
+  })
+}
+
+// 获取法律问题场景列表
+export function getLegalProblemScenarioList() {
+  return request({
+    url: '/lawyer/legalProblemScenar/getList',
+    method: 'get'
+  })
+}
+
+// 获取擅长领域列表
+export function getExpertiseAreaList() {
+  return request({
+    url: '/lawyer/expertiseArea/getList',
+    method: 'get'
+  })
+}

+ 2 - 1
config.js

@@ -1,7 +1,8 @@
 // 应用全局配置
 module.exports = {
-  baseUrl: 'https://vue.ruoyi.vip/prod-api',
+  // baseUrl: 'https://vue.ruoyi.vip/prod-api',
   // baseUrl: 'http://localhost:8080',
+  baseUrl: 'http://192.168.2.101:8899',
   // 应用信息
   appInfo: {
     // 应用名称

+ 7 - 1
pages.json

@@ -7,6 +7,12 @@
       "navigationBarBackgroundColor": "transparent"
     }
   }, {
+    "path": "pages/setUserInfo",
+    "style": {
+      "navigationBarTitleText": "设置用户信息",
+      "navigationStyle": "custom"
+    }
+  },{
     "path": "pages/register",
     "style": {
       "navigationBarTitleText": "注册",
@@ -113,4 +119,4 @@
     "navigationBarBackgroundColor": "#ffffff",
     "navigationStyle": "custom"
   }
-}
+}

+ 447 - 171
pages/login.vue

@@ -1,210 +1,486 @@
 <template>
-  <view class="normal-login-container">
-    <view class="logo-content align-center justify-center flex">
-      <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
-      </image>
-      <text class="title">若依移动端登录</text>
+  <view class="login-container">
+
+    <!-- 背景装饰 -->
+    <view class="background-decoration"> </view>
+
+    <!-- 返回按钮 -->
+    <view class="back-btn" @click="showInterruptLoginModal = true" v-if="step === 2">
+      <image src="@/static/images/login/back.png" mode="widthFix" style="width: 50rpx;height: 50rpx;"></image>
     </view>
-    <view class="login-form-content">
-      <view class="input-item flex align-center">
-        <view class="iconfont icon-user icon"></view>
-        <input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
-      </view>
-      <view class="input-item flex align-center">
-        <view class="iconfont icon-password icon"></view>
-        <input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
+
+    <!-- 主内容区 -->
+    <view class="login-content">
+      <!-- 标题 -->
+      <view class="title">登录律师账号</view>
+      <view class="subtitle">请使用手机号接收验证码进行登录</view>
+
+      <!-- 第一步:输入手机号 -->
+      <view class="form-section" v-if="step === 1">
+        <view class="input-label">手机号码</view>
+        <view class="input-wrapper">
+          <input v-model="phone" type="number" class="phone-input" placeholder="请输入手机号码" maxlength="11" />
+        </view>
+        <view class="resend-code-btn" @click="handleGetCode"> 获取验证码</view>
       </view>
-      <view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
-        <view class="iconfont icon-code icon"></view>
-        <input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
-        <view class="login-code"> 
-          <image :src="codeUrl" @click="getCode" class="login-code-img"></image>
+
+      <!-- 第二步:输入验证码 -->
+      <view class="form-section" v-if="step === 2">
+        <view class="input-label">验证码</view>
+        <view class="input-wrapper">
+          <input v-model="code" type="number" class="code-input" placeholder="请输入验证码" maxlength="6"
+            @input="handleInputCode" />
         </view>
+
+        <button class="resend-code-btn" @click="handleResendCode">
+          {{ countdown > 0 ? `重新获取 (${countdown}s)` : '重新获取' }}
+        </button>
+        <!-- <button 
+          class="login-btn" 
+          :disabled="!canLogin"
+          @click="handleLogin"
+        >
+          登录
+        </button> -->
       </view>
-      <view class="action-btn">
-        <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
+
+      <!-- 用户协议 -->
+      <view class="agreement-section">
+        <view class="checkbox-wrapper" @click="toggleAgreement">
+          <view class="checkbox" :class="{ 'checked': agreed }">
+            <text class="check-icon" v-if="agreed">✓</text>
+          </view>
+          <text class="agreement-text">
+            我已阅读并同意
+            <text class="link-text" @click.stop="handleUserAgrement">《用户协议》</text>
+            和
+            <text class="link-text" @click.stop="handlePrivacy">《隐私政策》</text>
+          </text>
+        </view>
       </view>
-      <view class="reg text-center" v-if="register">
-        <text class="text-grey1">没有账号?</text>
-        <text @click="handleUserRegister" class="text-blue">立即注册</text>
+    </view>
+
+    <!-- 用户协议弹窗 -->
+    <view class="modal-overlay" v-if="showAgreementModal" @click="showAgreementModal = false">
+      <view class="modal-content" @click.stop>
+        <view class="modal-title">用户协议及隐私政策</view>
+        <view class="modal-text">
+          为了更好的保障您的合法权益,请您阅读并同意以下协议
+          <text class="link-text" @click="handleUserAgrement">《用户协议》</text>
+          和
+          <text class="link-text" @click="handlePrivacy">《隐私政策》</text>
+        </view>
+        <view class="modal-buttons">
+          <button class="modal-btn reject-btn" @click="handleReject">拒绝</button>
+          <button class="modal-btn agree-btn" @click="handleAgree">同意</button>
+        </view>
       </view>
-      <view class="xieyi text-center">
-        <text class="text-grey1">登录即代表同意</text>
-        <text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
-        <text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
+    </view>
+
+    <!-- 中断登录弹窗 -->
+    <view class="modal-overlay" v-if="showInterruptLoginModal">
+      <view class="modal-content" @click.stop>
+        <view class="modal-title" style="margin-bottom: 60rpx;">
+          返回将中断登录,确定返回?
+        </view>
+        <view class="modal-buttons">
+          <button class="modal-btn reject-btn" @click="showInterruptLoginModal = false">取消</button>
+          <button class="modal-btn agree-btn" @click="interruptLogin">确定</button>
+        </view>
       </view>
     </view>
-     
   </view>
 </template>
 
 <script>
-  import { getCodeImg } from '@/api/login'
-  import { getToken } from '@/utils/auth'
-
-  export default {
-    data() {
-      return {
-        codeUrl: "",
-        captchaEnabled: true,
-        // 用户注册开关
-        register: false,
-        globalConfig: getApp().globalData.config,
-        loginForm: {
-          username: "admin",
-          password: "admin123",
-          code: "",
-          uuid: ""
+import { sendSmsCode } from '@/api/login'
+import { getToken } from '@/utils/auth'
+
+export default {
+  data() {
+    return {
+      step: 1, // 1: 输入手机号, 2: 输入验证码
+      phone: '',
+      code: '',
+      countdown: 0,
+      agreed: false,
+      showAgreementModal: false,
+      globalConfig: getApp().globalData.config,
+      timer: null,
+      showInterruptLoginModal: false
+    }
+  },
+  computed: {
+    canGetCode() {
+      return /^1[3-9]\d{9}$/.test(this.phone) && this.agreed
+    },
+    canLogin() {
+      return this.code.length >= 4 && this.agreed
+    }
+  },
+  onLoad() {
+    //#ifdef H5
+    if (getToken()) {
+      this.$tab.reLaunch('/pages/index')
+    }
+    //#endif
+    // 首次进入显示协议弹窗
+    this.showAgreementModal = true
+  },
+  onUnload() {
+    if (this.timer) {
+      clearInterval(this.timer)
+    }
+  },
+  methods: {
+    interruptLogin() {
+      this.showInterruptLoginModal = false
+      this.step = 1
+      this.code = ''
+    },
+    toggleAgreement() {
+      this.agreed = !this.agreed
+    },
+    handleGetCode() {
+      if (!this.canGetCode) {
+        if (!/^1[3-9]\d{9}$/.test(this.phone)) {
+          this.$modal.msgError('请输入正确的手机号码')
+        } else if (!this.agreed) {
+          this.$modal.msgError('请先同意用户协议和隐私政策')
         }
+        return
       }
+
+      this.$modal.loading('发送中...')
+      sendSmsCode(this.phone).then(() => {
+        this.$modal.closeLoading()
+        this.$modal.msgSuccess('验证码已发送')
+        this.step = 2
+        this.startCountdown()
+      }).catch(() => {
+        this.$modal.closeLoading()
+      })
     },
-    created() {
-      this.getCode()
+    handleResendCode() {
+      if (this.countdown > 0) return
+      this.handleGetCode()
     },
-    onLoad() {
-      //#ifdef H5
-      if (getToken()) {
-        this.$tab.reLaunch('/pages/indexOrder/index')
+    startCountdown() {
+      this.countdown = 60
+      if (this.timer) {
+        clearInterval(this.timer)
       }
-      //#endif
+      this.timer = setInterval(() => {
+        this.countdown--
+        if (this.countdown <= 0) {
+          clearInterval(this.timer)
+          this.timer = null
+        }
+      }, 1000)
     },
-    methods: {
-      // 用户注册
-      handleUserRegister() {
-        this.$tab.redirectTo(`/pages/register`)
-      },
-      // 隐私协议
-      handlePrivacy() {
-        let site = this.globalConfig.appInfo.agreements[0]
-        this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
-      },
-      // 用户协议
-      handleUserAgrement() {
-        let site = this.globalConfig.appInfo.agreements[1]
-        this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
-      },
-      // 获取图形验证码
-      getCode() {
-        getCodeImg().then(res => {
-          this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
-          if (this.captchaEnabled) {
-            this.codeUrl = 'data:image/gif;base64,' + res.img
-            this.loginForm.uuid = res.uuid
-          }
-        })
-      },
-      // 登录方法
-      async handleLogin() {
-        if (this.loginForm.username === "") {
-          this.$modal.msgError("请输入账号")
-        } else if (this.loginForm.password === "") {
-          this.$modal.msgError("请输入密码")
-        } else if (this.loginForm.code === "" && this.captchaEnabled) {
-          this.$modal.msgError("请输入验证码")
-        } else {
-          this.$modal.loading("登录中,请耐心等待...")
-          this.pwdLogin()
+    handleInputCode() {
+      if (this.code.length >= 6) {
+        this.handleLogin()
+      }
+    },
+    async handleLogin() {
+      if (!this.canLogin) {
+        if (this.code.length < 4) {
+          this.$modal.msgError('请输入验证码')
+        } else if (!this.agreed) {
+          this.$modal.msgError('请先同意用户协议和隐私政策')
         }
-      },
-      // 密码登录
-      async pwdLogin() {
-        this.$store.dispatch('Login', this.loginForm).then(() => {
-          this.$modal.closeLoading()
-          this.loginSuccess()
-        }).catch(() => {
-          if (this.captchaEnabled) {
-            this.getCode()
-          }
-        })
-      },
-      // 登录成功后,处理函数
-      loginSuccess(result) {
-        // 设置用户信息
-        this.$store.dispatch('GetInfo').then(res => {
-          this.$tab.reLaunch('/pages/indexOrder/index')
+        return
+      }
+
+      this.$modal.loading('登录中,请耐心等待...')
+
+      this.$modal.closeLoading()
+      this.loginSuccess()
+
+      try {
+        await this.$store.dispatch('LoginBySms', {
+          phone: this.phone,
+          code: this.code
         })
+        this.$modal.closeLoading()
+        this.loginSuccess()
+      } catch (error) {
+        this.$modal.closeLoading()
       }
+
+    },
+    loginSuccess() {
+      // this.$store.dispatch('GetInfo').then(res => {
+        this.$tab.reLaunch('/pages/setUserInfo')
+      // })
+    },
+    handleUserAgrement() {
+      let site = this.globalConfig.appInfo.agreements[1]
+      this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
+    },
+    handlePrivacy() {
+      let site = this.globalConfig.appInfo.agreements[0]
+      this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
+    },
+    handleReject() {
+      this.showAgreementModal = false
+      this.agreed = false
+    },
+    handleAgree() {
+      this.showAgreementModal = false
+      this.agreed = true
     }
   }
+}
 </script>
 
 <style lang="scss" scoped>
-  page {
-    background-color: #ffffff;
+page {
+  background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf0 100%);
+  min-height: 100vh;
+}
+
+.login-container {
+  position: relative;
+  width: 100%;
+  min-height: 100vh;
+  padding: 0 60rpx;
+  padding-top: 120rpx;
+  overflow: hidden;
+  background-image: url('@/static/images/login/bg1.png');
+  background-size: cover;
+  background-repeat: no-repeat;
+  background-position: center center;
+}
+
+.background-decoration {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 100%;
+  height: 80%;
+  background-image: url('@/static/images/login/bg2.png');
+  background-size: cover;
+}
+
+.back-btn {
+  position: absolute;
+  top: 100rpx;
+  left: 40rpx;
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+
+  .back-icon {
+    font-size: 40rpx;
+    color: #333;
+    font-weight: bold;
   }
+}
 
-  .normal-login-container {
-    width: 100%;
 
-    .logo-content {
-      width: 100%;
-      font-size: 21px;
-      text-align: center;
-      padding-top: 15%;
+.login-content {
+  position: relative;
+  z-index: 1;
+  top: 260rpx;
+}
 
-      image {
-        border-radius: 4px;
-      }
+.title {
+  font-size: 56rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+  text-align: left;
+}
 
-      .title {
-        margin-left: 10px;
-      }
-    }
+.subtitle {
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 80rpx;
+  text-align: left;
+}
 
-    .login-form-content {
-      text-align: center;
-      margin: 20px auto;
-      margin-top: 15%;
-      width: 80%;
-
-      .input-item {
-        margin: 20px auto;
-        background-color: #f5f6f7;
-        height: 45px;
-        border-radius: 20px;
-
-        .icon {
-          font-size: 38rpx;
-          margin-left: 10px;
-          color: #999;
-        }
+.form-section {
+  margin-bottom: 60rpx;
+}
 
-        .input {
-          width: 100%;
-          font-size: 14px;
-          line-height: 20px;
-          text-align: left;
-          padding-left: 15px;
-        }
+.input-label {
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 20rpx;
+  text-align: left;
+}
 
-      }
+.input-wrapper {
+  background-color: #f8f9fa;
+  border-radius: 16rpx;
+  padding: 24rpx 30rpx;
+  margin-bottom: 40rpx;
+}
 
-      .login-btn {
-        margin-top: 40px;
-        height: 45px;
-      }
-      
-      .reg {
-        margin-top: 15px;
-      }
-      
-      .xieyi {
-        color: #333;
-        margin-top: 20px;
-      }
-      
-      .login-code {
-        height: 38px;
-        float: right;
-      
-        .login-code-img {
-          height: 38px;
-          position: absolute;
-          margin-left: 10px;
-          width: 200rpx;
-        }
-      }
+.phone-input,
+.code-input {
+  width: 100%;
+  font-size: 32rpx;
+  color: #333;
+  background: transparent;
+  border: none;
+}
+
+.get-code-btn,
+.resend-code-btn {
+  width: 100%;
+  height: 88rpx;
+  background: linear-gradient(135deg, #4a90e2 0%, #5ba0f2 100%);
+  color: #fff;
+  font-size: 32rpx;
+  border-radius: 16rpx;
+  border: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 40rpx;
+
+  &[disabled] {
+    background: #d0d0d0;
+    color: #999;
+  }
+}
+
+.login-btn {
+  width: 100%;
+  height: 88rpx;
+  background: linear-gradient(135deg, #4a90e2 0%, #5ba0f2 100%);
+  color: #fff;
+  font-size: 32rpx;
+  border-radius: 16rpx;
+  border: none;
+  margin-top: 40rpx;
+
+  &[disabled] {
+    background: #d0d0d0;
+    color: #999;
+  }
+}
+
+.agreement-section {
+  position: fixed;
+  bottom: 60rpx;
+  left: 0;
+  right: 0;
+  padding: 0 60rpx;
+  z-index: 2;
+}
+
+.checkbox-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.checkbox {
+  width: 36rpx;
+  height: 36rpx;
+  border: 2rpx solid #ddd;
+  border-radius: 50%;
+  margin-right: 16rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+
+  &.checked {
+    background: #4a90e2;
+    border-color: #4a90e2;
+
+    .check-icon {
+      color: #fff;
+      font-size: 24rpx;
+      font-weight: bold;
     }
   }
+}
+
+.agreement-text {
+  font-size: 24rpx;
+  color: #666;
+  line-height: 1.6;
+}
+
+.link-text {
+  color: #4a90e2;
+}
+
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  width: 600rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 60rpx 40rpx 0 40rpx;
+  margin: 0 40rpx;
+}
+
+.modal-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 30rpx;
+  text-align: center;
+}
+
+.modal-text {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.8;
+  margin-bottom: 50rpx;
+  text-align: left;
+}
+
+.modal-buttons {
+  display: flex;
+  border-top: 1rpx solid #eee;
+  // padding-top: 30rpx;
+
+}
+
+.modal-btn {
+  flex: 1;
+  height: 100rpx;
+  font-size: 32rpx;
+  border: none;
+  background: transparent;
+  border-radius: 12rpx;
+  padding: 10rpx;
+
+  &.reject-btn {
+    color: #999;
+    margin-right: 20rpx;
+    border-right: 1rpx solid #eee;
+  }
+
+  &.agree-btn {
+    color: #4a90e2;
+    font-weight: bold;
+  }
+}
 
-</style>
+uni-button:after {
+  border: none !important;
+}
+</style>

+ 852 - 0
pages/setUserInfo.vue

@@ -0,0 +1,852 @@
+<template>
+    <view class="set-user-info-container">
+        <!-- 标题 -->
+        <view class="page-title">完善个人信息</view>
+
+        <scroll-view class="form-scroll" scroll-y>
+            <!-- 头像上传 -->
+            <view class="avatar-section">
+                <view class="avatar-wrapper" @click="chooseAvatar">
+                    <image v-if="formData.avatar" :src="formData.avatar" class="avatar-image" mode="aspectFill" />
+                    <view v-else class="avatar-placeholder">
+                         <image src="/static/images/login/user.png" class="avatar-image" mode="aspectFill" style="width: 80%; height: 80%;" />
+                    </view>
+                </view>
+                <view class="avatar-tip">
+                    <text class="tip-text">点击上传头像</text>
+                    <text class="required-mark">*</text>
+                </view>
+                <view v-if="errors.avatar" class="error-tip">{{ errors.avatar }}</view>
+            </view>
+
+            <!-- 表单字段 -->
+            <view class="form-section">
+                <!-- 真实姓名 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>真实姓名</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <input v-model="formData.realName" class="form-input" placeholder="请输入真实姓名"
+                        @blur="validateField('realName')" />
+                    <view v-if="errors.realName" class="error-tip">{{ errors.realName }}</view>
+                </view>
+
+                <!-- 从业时间 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>从业时间</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <picker mode="date" :value="formData.practiceYear" :start="startDate" :end="endDate"
+                        @change="onPracticeYearChange">
+                        <view class="picker-view">
+                            <text :class="formData.practiceYear ? 'picker-text' : 'picker-placeholder'">
+                                {{ formData.practiceYear ? formatDate(formData.practiceYear) : '请选择从业时间' }}
+                            </text>
+                            <text class="picker-arrow">›</text>
+                        </view>
+                    </picker>
+                    <view v-if="errors.practiceYear" class="error-tip">{{ errors.practiceYear }}</view>
+                </view>
+
+                <!-- 专业领域 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>专业领域</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <picker mode="selector" :range="professionalFields" :value="professionalFieldIndex"
+                        @change="onProfessionalFieldChange">
+                        <view class="picker-view">
+                            <text :class="formData.professionalField ? 'picker-text' : 'picker-placeholder'">
+                                {{ formData.professionalField || '请选择专业领域' }}
+                            </text>
+                            <text class="picker-arrow">›</text>
+                        </view>
+                    </picker>
+                    <view v-if="errors.professionalField" class="error-tip">{{ errors.professionalField }}</view>
+                </view>
+
+                <!-- 法律场景 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>法律场景</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <view class="picker-view" @click="showLegalScenarioModal = true">
+                        <view class="picker-text-ellipsis">
+                            <text :class="formData.legalScenarios && formData.legalScenarios.length > 0 ? 'picker-text' : 'picker-placeholder'">
+                                {{ getLegalScenarioText() || '请选择法律场景' }}
+                            </text>
+                        </view>
+                        <text class="picker-arrow">›</text>
+                    </view>
+                    <view v-if="errors.legalScenario" class="error-tip">{{ errors.legalScenario }}</view>
+                </view>
+
+                <!-- 联系地址 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>联系地址</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <input v-model="formData.address" class="form-input" placeholder="请输入常用联系地址"
+                        @blur="validateField('address')" />
+                    <view v-if="errors.address" class="error-tip">{{ errors.address }}</view>
+                </view>
+
+                <!-- 律师事务所名称 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>律师事务所名称</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <picker 
+                        mode="selector" 
+                        :range="firmList" 
+                        range-key="firmName"
+                        :value="lawFirmIndex"
+                        @change="onLawFirmChange"
+                    >
+                        <view class="picker-view">
+                            <text :class="formData.lawFirmName ? 'picker-text' : 'picker-placeholder'">
+                                {{ formData.lawFirmName || '请选择律师事务所名称' }}
+                            </text>
+                            <text class="picker-arrow">›</text>
+                        </view>
+                    </picker>
+                    <view v-if="errors.lawFirmName" class="error-tip">{{ errors.lawFirmName }}</view>
+                </view>
+
+                <!-- 律师事务所收款账号 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>律师事务所收款账号</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <picker 
+                        mode="selector" 
+                        :range="paymentAccountList" 
+                        :value="paymentAccountIndex"
+                        @change="onPaymentAccountChange"
+                    >
+                        <view class="picker-view">
+                            <text :class="formData.paymentAccount ? 'picker-text' : 'picker-placeholder'">
+                                {{ formData.paymentAccount || '请选择律师事务所收款账号' }}
+                            </text>
+                            <text class="picker-arrow">›</text>
+                        </view>
+                    </picker>
+                    <view v-if="errors.paymentAccount" class="error-tip">{{ errors.paymentAccount }}</view>
+                </view>
+
+                
+
+                <!-- 律师资格证 -->
+                <view class="form-item">
+                    <view class="form-label">
+                        <text>律师资格证</text>
+                        <text class="required-mark">*</text>
+                    </view>
+                    <view class="certificate-upload" @click="chooseCertificate">
+                        <image v-if="formData.certificate" :src="formData.certificate" class="certificate-image"
+                            mode="aspectFit" />
+                        <view v-else class="certificate-placeholder">
+                            <text class="certificate-icon">🏔️</text>
+                            <text class="certificate-text">点击上传图片</text>
+                        </view>
+                    </view>
+                    <view v-if="errors.certificate" class="error-tip">{{ errors.certificate }}</view>
+                </view>
+            </view>
+        </scroll-view>
+
+        <!-- 提交按钮 -->
+        <view class="submit-section">
+            <button class="submit-btn" @click="handleSubmit">提交信息</button>
+        </view>
+        
+        <!-- 法律场景多选弹窗 -->
+        <view class="modal-overlay" v-if="showLegalScenarioModal" @click="showLegalScenarioModal = false">
+            <view class="legal-scenario-modal" @click.stop>
+                <view class="modal-header">
+                    <view class="modal-title">选择您擅长的法律场景</view>
+                    <text class="cancel-btn" @click="showLegalScenarioModal = false">取消</text>
+                </view>
+                <view class="scenario-tags">
+                    <view 
+                        v-for="(scenario, index) in legalScenarios" 
+                        :key="index"
+                        class="scenario-tag"
+                        :class="{ 'selected': isLegalScenarioSelected(scenario) }"
+                        @click="toggleLegalScenario(scenario)"
+                    >
+                        {{ scenario }}
+                    </view>
+                </view>
+                <button class="confirm-btn" @click="confirmLegalScenarios">选好了</button>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import upload from '@/utils/upload'
+import { updateUserProfile, uploadAvatar } from '@/api/system/user'
+import { getFirmList, getLegalProblemScenarioList, getExpertiseAreaList } from '@/api/setUserInfo'
+import { getToken } from '@/utils/auth'
+import config from '@/config'
+
+export default {
+    data() {
+        return {
+            formData: {
+                avatar: '',
+                realName: '',
+                practiceYear: '',
+                professionalField: '',
+                legalScenarios: [], // 改为数组支持多选
+                address: '',
+                paymentAccount: '',
+                lawFirmName: '',
+                certificate: ''
+            },
+            errors: {},
+            startDate: '1950-01-01', // 最早可选日期
+            endDate: '', // 最晚可选日期(今天)
+            professionalFields: ['刑事法律', '民事法律', '商事法律', '行政法律', '知识产权', '劳动法律', '其他'],
+            professionalFieldIndex: -1,
+            legalScenarios: ['家庭生活', '职场工作', '邻里与社区', '网络与数字', '交通出行', '居住与物业', '消费与服务'],
+            showLegalScenarioModal: false,
+            firmList: [], // 律师事务所列表
+            lawFirmIndex: -1,
+            paymentAccountList: [], // 收款账号列表
+            paymentAccountIndex: -1,
+            baseUrl: config.baseUrl
+        }
+    },
+    onLoad() {
+        // 设置最晚可选日期为今天
+        const today = new Date()
+        const year = today.getFullYear()
+        const month = String(today.getMonth() + 1).padStart(2, '0')
+        const day = String(today.getDate()).padStart(2, '0')
+        this.endDate = `${year}-${month}-${day}`
+        // 加载律师事务所列表
+        // this.getPageInfo()
+    },
+    methods: {
+        getPageInfo() {
+            getFirmList().then(res => {
+                if (res.data && res.data.length > 0) {
+                    this.firmList = res.data
+                    // 提取收款账号列表(假设每个律师事务所都有收款账号字段)
+                    this.paymentAccountList = res.data.map(item => item.paymentAccount || item.account || '').filter(account => account)
+                }
+            }).catch(() => {
+                // 如果接口失败,使用默认数据
+                this.firmList = []
+                this.paymentAccountList = []
+            })
+        },
+        
+        // 律师事务所选择
+        onLawFirmChange(e) {
+            this.lawFirmIndex = e.detail.value
+            const selectedFirm = this.firmList[e.detail.value]
+            if (selectedFirm) {
+                this.formData.lawFirmName = selectedFirm.firmName || selectedFirm.name || ''
+                // 如果选择的律师事务所有收款账号,自动填充
+                if (selectedFirm.paymentAccount || selectedFirm.account) {
+                    this.formData.paymentAccount = selectedFirm.paymentAccount || selectedFirm.account
+                    // 找到对应的收款账号索引
+                    const accountIndex = this.paymentAccountList.indexOf(this.formData.paymentAccount)
+                    if (accountIndex > -1) {
+                        this.paymentAccountIndex = accountIndex
+                    }
+                }
+                this.errors.lawFirmName = ''
+            }
+        },
+        
+        // 收款账号选择
+        onPaymentAccountChange(e) {
+            this.paymentAccountIndex = e.detail.value
+            this.formData.paymentAccount = this.paymentAccountList[e.detail.value] || ''
+            this.errors.paymentAccount = ''
+        },
+        // 选择头像
+        chooseAvatar() {
+            uni.chooseImage({
+                count: 1,
+                sizeType: ['compressed'],
+                sourceType: ['album', 'camera'],
+                success: (res) => {
+                    const tempFilePath = res.tempFilePaths[0]
+                    this.uploadAvatarFile(tempFilePath)
+                }
+            })
+        },
+
+        // 上传头像
+        uploadAvatarFile(filePath) {
+            this.$modal.loading('上传中...')
+            uni.uploadFile({
+                url: 'http://192.168.2.54:8899/file/upload',
+                filePath: filePath,
+                name: 'avatarfile',
+                header: {
+                    'Authorization': 'Bearer ' + (getToken() || '')
+                },
+                success: (res) => {
+                    try {
+                        const result = JSON.parse(res.data)
+                        if (result.code === 200) {
+                            this.$modal.closeLoading()
+                            this.formData.avatar = this.baseUrl + result.imgUrl
+                            this.errors.avatar = ''
+                        } else {
+                            this.$modal.closeLoading()
+                            this.$modal.msgError(result.msg || '上传失败')
+                        }
+                    } catch (e) {
+                        this.$modal.closeLoading()
+                        this.$modal.msgError('上传失败')
+                    }
+                },
+                fail: (error) => {
+                    this.$modal.closeLoading()
+                    this.$modal.msgError('上传失败')
+                }
+            })
+        },
+
+        // 选择律师资格证
+        chooseCertificate() {
+            uni.chooseImage({
+                count: 1,
+                sizeType: ['compressed'],
+                sourceType: ['album', 'camera'],
+                success: (res) => {
+                    const tempFilePath = res.tempFilePaths[0]
+                    this.uploadCertificateFile(tempFilePath)
+                }
+            })
+        },
+
+        // 上传资格证
+        uploadCertificateFile(filePath) {
+            this.$modal.loading('上传中...')
+            upload({
+                url: '/system/user/profile/certificate',
+                name: 'certificate',
+                filePath: filePath
+            }).then(res => {
+                this.$modal.closeLoading()
+                this.formData.certificate = this.baseUrl + res.imgUrl
+                this.errors.certificate = ''
+            }).catch(() => {
+                this.$modal.closeLoading()
+            })
+        },
+
+        // 从业时间选择
+        onPracticeYearChange(e) {
+            this.formData.practiceYear = e.detail.value
+            this.errors.practiceYear = ''
+        },
+        
+        // 格式化日期显示
+        formatDate(dateStr) {
+            if (!dateStr) return ''
+            const date = new Date(dateStr)
+            const year = date.getFullYear()
+            const month = String(date.getMonth() + 1).padStart(2, '0')
+            const day = String(date.getDate()).padStart(2, '0')
+            return `${year}年${month}月${day}日`
+        },
+
+        // 专业领域选择
+        onProfessionalFieldChange(e) {
+            this.professionalFieldIndex = e.detail.value
+            this.formData.professionalField = this.professionalFields[e.detail.value]
+            this.errors.professionalField = ''
+        },
+
+        // 获取法律场景显示文本
+        getLegalScenarioText() {
+            if (!this.formData.legalScenarios || this.formData.legalScenarios.length === 0) {
+                return ''
+            }
+            return this.formData.legalScenarios.join('、')
+        },
+        
+        // 判断法律场景是否已选中
+        isLegalScenarioSelected(scenario) {
+            return this.formData.legalScenarios && this.formData.legalScenarios.includes(scenario)
+        },
+        
+        // 切换法律场景选择状态
+        toggleLegalScenario(scenario) {
+            if (!this.formData.legalScenarios) {
+                this.formData.legalScenarios = []
+            }
+            const index = this.formData.legalScenarios.indexOf(scenario)
+            if (index > -1) {
+                // 已选中,取消选择
+                this.formData.legalScenarios.splice(index, 1)
+            } else {
+                // 未选中,添加选择
+                this.formData.legalScenarios.push(scenario)
+            }
+        },
+        
+        // 确认法律场景选择
+        confirmLegalScenarios() {
+            if (this.formData.legalScenarios && this.formData.legalScenarios.length > 0) {
+                this.errors.legalScenario = ''
+            }
+            this.showLegalScenarioModal = false
+        },
+
+        // 验证单个字段
+        validateField(field) {
+            this.errors[field] = ''
+            const value = this.formData[field]
+
+            if (!value || value.trim() === '') {
+                switch (field) {
+                    case 'realName':
+                        this.errors.realName = '请填写真实姓名'
+                        break
+                    case 'address':
+                        this.errors.address = '请填写联系地址'
+                        break
+                    case 'paymentAccount':
+                        this.errors.paymentAccount = '请选择律师事务所收款账号'
+                        break
+                    case 'lawFirmName':
+                        this.errors.lawFirmName = '请选择律师事务所名称'
+                        break
+                }
+            }
+        },
+
+        // 验证所有字段
+        validateForm() {
+            this.errors = {}
+            let isValid = true
+
+            if (!this.formData.avatar) {
+                this.errors.avatar = '请上传头像'
+                isValid = false
+            }
+
+            if (!this.formData.realName || this.formData.realName.trim() === '') {
+                this.errors.realName = '请填写真实姓名'
+                isValid = false
+            }
+
+            if (!this.formData.practiceYear) {
+                this.errors.practiceYear = '请选择从业时间'
+                isValid = false
+            }
+
+            if (!this.formData.professionalField) {
+                this.errors.professionalField = '请选择专业领域'
+                isValid = false
+            }
+
+            if (!this.formData.legalScenarios || this.formData.legalScenarios.length === 0) {
+                this.errors.legalScenario = '请选择法律场景'
+                isValid = false
+            }
+
+            if (!this.formData.address || this.formData.address.trim() === '') {
+                this.errors.address = '请填写联系地址'
+                isValid = false
+            }
+
+            if (!this.formData.paymentAccount || this.formData.paymentAccount.trim() === '') {
+                this.errors.paymentAccount = '请选择律师事务所收款账号'
+                isValid = false
+            }
+
+            if (!this.formData.lawFirmName || this.formData.lawFirmName.trim() === '') {
+                this.errors.lawFirmName = '请选择律师事务所名称'
+                isValid = false
+            }
+
+            if (!this.formData.certificate) {
+                this.errors.certificate = '请上传律师资格证'
+                isValid = false
+            }
+
+            return isValid
+        },
+
+        // 提交表单
+        handleSubmit() {
+            if (!this.validateForm()) {
+                this.$modal.msgError('请完善所有必填信息')
+                return
+            }
+
+            this.$modal.loading('提交中...')
+            updateUserProfile(this.formData).then(() => {
+                this.$modal.closeLoading()
+                this.$modal.msgSuccess('提交成功')
+                setTimeout(() => {
+                    uni.navigateBack()
+                }, 1500)
+            }).catch(() => {
+                this.$modal.closeLoading()
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+page {
+    background-color: #F9FAFC;
+}
+
+input,.picker-view {
+    background-color: #fff !important;
+    width: 676rpx;
+    height: 103rpx;
+    background: #FFFFFF;
+    box-shadow: 0rpx 2rpx 11rpx 0rpx rgba(0, 0, 0, 0.06);
+    border-radius: 23rpx 23rpx 23rpx 23rpx;
+}
+
+.set-user-info-container {
+    min-height: 100vh;
+    padding-bottom: 120rpx;
+}
+
+.page-title {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #333;
+    text-align: center;
+    padding: 40rpx 0 30rpx;
+    // background-color: #fff;
+}
+
+.form-scroll {
+    height: calc(100vh - 200rpx);
+    padding: 0 30rpx;
+}
+
+.avatar-section {
+    // background-color: #F9FAFC;
+    padding: 40rpx 0;
+    margin-bottom: 20rpx;
+    border-radius: 16rpx;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.avatar-wrapper {
+    position: relative;
+    width: 160rpx;
+    height: 160rpx;
+    border-radius: 50%;
+    overflow: hidden;
+    // background-color: #f0f0f0;
+    margin-bottom: 20rpx;
+}
+
+.avatar-image {
+    width: 100%;
+    height: 100%;
+}
+
+.avatar-placeholder {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    // background-color: #f0f0f0;
+}
+
+.avatar-icon {
+    font-size: 80rpx;
+    color: #ccc;
+}
+
+.camera-icon {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    width: 48rpx;
+    height: 48rpx;
+    background-color: #4a90e2;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border: 3rpx solid #fff;
+}
+
+.camera-text {
+    font-size: 24rpx;
+}
+
+.avatar-tip {
+    display: flex;
+    align-items: center;
+    font-size: 28rpx;
+    color: #4a90e2;
+}
+
+.tip-text {
+    margin-right: 4rpx;
+}
+
+.required-mark {
+    color: #ff4d4f;
+    font-size: 28rpx;
+}
+
+.form-section {
+    // background-color: #fff;
+    border-radius: 16rpx;
+    padding: 30rpx;
+    margin-bottom: 20rpx;
+}
+
+.form-item {
+    margin-bottom: 40rpx;
+
+    &:last-child {
+        margin-bottom: 0;
+    }
+}
+
+.form-label {
+    font-size: 28rpx;
+    color: #333;
+    margin-bottom: 20rpx;
+    display: flex;
+    align-items: center;
+}
+
+.form-input {
+    width: 100%;
+    height: 80rpx;
+    background-color: #f8f9fa;
+    border-radius: 12rpx;
+    padding: 0 24rpx;
+    font-size: 28rpx;
+    color: #333;
+    box-sizing: border-box;
+}
+
+.picker-view {
+    width: 100%;
+    height: 80rpx;
+    background-color: #f8f9fa;
+    border-radius: 12rpx;
+    padding: 0 24rpx;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    box-sizing: border-box;
+}
+
+.picker-text {
+    font-size: 28rpx;
+    color: #333;
+}
+
+.picker-placeholder {
+    font-size: 28rpx;
+    color: #999;
+}
+
+.picker-text-ellipsis {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    margin-right: 20rpx;
+}
+
+.picker-arrow {
+    font-size: 40rpx;
+    color: #999;
+    font-weight: 300;
+}
+
+.certificate-upload {
+    width: 100%;
+    height: 300rpx;
+    border: 2rpx dashed #ddd;
+    border-radius: 12rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #fafafa;
+    box-sizing: border-box;
+}
+
+.certificate-image {
+    width: 100%;
+    height: 100%;
+    border-radius: 12rpx;
+}
+
+.certificate-placeholder {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+}
+
+.certificate-icon {
+    font-size: 80rpx;
+    margin-bottom: 20rpx;
+}
+
+.certificate-text {
+    font-size: 28rpx;
+    color: #999;
+}
+
+.error-tip {
+    font-size: 24rpx;
+    color: #ff4d4f;
+    margin-top: 10rpx;
+    padding: 8rpx 16rpx;
+    background-color: #fff2f0;
+    border-radius: 8rpx;
+    display: inline-block;
+}
+
+.submit-section {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 20rpx 30rpx;
+    padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+    background-color: #fff;
+    box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.submit-btn {
+    width: 100%;
+    height: 88rpx;
+    background: linear-gradient(135deg, #4a90e2 0%, #5ba0f2 100%);
+    color: #fff;
+    font-size: 32rpx;
+    border-radius: 16rpx;
+    border: none;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+ uni-button:after {
+     border: none !important;
+ }
+
+ .modal-overlay {
+     position: fixed;
+     top: 0;
+     left: 0;
+     right: 0;
+     bottom: 0;
+     background: rgba(0, 0, 0, 0.5);
+    //  display: flex;
+    //  align-items: center;
+    //  justify-content: center;
+     z-index: 1000;
+ }
+
+ .legal-scenario-modal {
+     width: 100%;
+     background: #fff;
+     border-radius: 24rpx 24rpx 0 0;
+     padding: 40rpx 30rpx 30rpx;
+     max-height: 80vh;
+     display: flex;
+     flex-direction: column;
+     position: fixed;
+     left: 0;
+     bottom: 0;
+ }
+
+ .modal-header {
+     position: relative;
+     display: flex;
+     justify-content: center;
+     align-items: center;
+     margin-bottom: 40rpx;
+     padding: 0 60rpx;
+ }
+
+ .modal-title {
+     font-size: 32rpx;
+     font-weight: bold;
+     color: #333;
+     text-align: center;
+ }
+
+ .cancel-btn {
+     font-size: 28rpx;
+     color: #999;
+     position: absolute;
+     right: 30rpx;
+     top: 50%;
+     transform: translateY(-50%);
+ }
+
+ .scenario-tags {
+     display: flex;
+     flex-wrap: wrap;
+     gap: 20rpx;
+     margin-bottom: 40rpx;
+     max-height: 50vh;
+     overflow-y: auto;
+ }
+
+ .scenario-tag {
+     min-width: 200rpx;
+     height: 70rpx;
+     background-color: #f5f5f5;
+     border-radius: 45rpx;
+     display: flex;
+     align-items: center;
+     justify-content: center;
+     font-size: 28rpx;
+     color: #666;
+     flex: 0 0 calc(33.333% - 14rpx);
+     box-sizing: border-box;
+     transition: all 0.3s;
+ }
+
+ .scenario-tag.selected {
+     background:#ECF1FE;
+     color: #2652C2;
+ }
+
+ .confirm-btn {
+     width: 100%;
+     height: 88rpx;
+     background: linear-gradient(135deg, #4a90e2 0%, #5ba0f2 100%);
+     color: #fff;
+     font-size: 32rpx;
+     border-radius: 16rpx;
+     border: none;
+     display: flex;
+     align-items: center;
+     justify-content: center;
+ }
+ </style>

BIN
static/images/login/back.png


BIN
static/images/login/bg1.png


BIN
static/images/login/bg2.png


BIN
static/images/login/user.png


+ 19 - 1
store/modules/user.js

@@ -2,7 +2,7 @@ import config from '@/config'
 import storage from '@/utils/storage'
 import constant from '@/utils/constant'
 import { isHttp, isEmpty } from "@/utils/validate"
-import { login, logout, getInfo } from '@/api/login'
+import { login, logout, getInfo  } from '@/api/login'
 import { getToken, setToken, removeToken } from '@/utils/auth'
 import defAva from '@/static/images/profile.jpg'
 
@@ -61,6 +61,24 @@ const user = {
         })
       })
     },
+    
+    // 手机号验证码登录
+    LoginBySms({ commit }, userInfo) {
+      const phone = userInfo.phone.trim()
+      const code = userInfo.code
+      return new Promise((resolve, reject) => {
+        setToken('22222')
+        commit('SET_TOKEN', '22222')
+        resolve()
+        // loginBySms(phone, code).then(res => {
+        //   setToken(res.token)
+        //   commit('SET_TOKEN', res.token)
+        //   resolve()
+        // }).catch(error => {
+        //   reject(error)
+        // })
+      })
+    },
 
     // 获取用户信息
     GetInfo({ commit, state }) {