sunshibo пре 4 недеља
родитељ
комит
fccf14b3bc
5 измењених фајлова са 1013 додато и 13 уклоњено
  1. 12 0
      pages.json
  2. 385 0
      pages/mine/info/components/practice-time-picker.vue
  3. 112 13
      pages/mine/info/index.vue
  4. 242 0
      pages/mine/info/intro.vue
  5. 262 0
      pages/mine/info/phone.vue

+ 12 - 0
pages.json

@@ -57,6 +57,18 @@
       "navigationStyle": "custom"
     }
   }, {
+    "path": "pages/mine/info/intro",
+    "style": {
+      "navigationBarTitleText": "简介",
+      "navigationStyle": "custom"
+    }
+  }, {
+    "path": "pages/mine/info/phone",
+    "style": {
+      "navigationBarTitleText": "手机号码",
+      "navigationStyle": "custom"
+    }
+  }, {
     "path": "pages/mine/pwd/index",
     "style": {
       "navigationBarTitleText": "修改密码",

+ 385 - 0
pages/mine/info/components/practice-time-picker.vue

@@ -0,0 +1,385 @@
+<template>
+  <view v-if="visible" class="picker-mask" @click="handleMaskClick">
+    <view class="picker-container" @click.stop>
+      <!-- 头部 -->
+      <view class="picker-header">
+        <text class="picker-title">从业时间选择</text>
+        <text class="picker-cancel" @click="handleCancel">取消</text>
+      </view>
+
+      <!-- 日期选择列 -->
+      <view class="picker-content">
+        <picker-view 
+          class="picker-view" 
+          :value="pickerValue" 
+          @change="handlePickerChange"
+          :indicator-style="indicatorStyle"
+          :mask-style="maskStyle"
+        >
+          <!-- 年列 -->
+          <picker-view-column>
+            <view 
+              v-for="(year, index) in years" 
+              :key="index" 
+              class="picker-item"
+              :class="{ 'picker-item-selected': pickerValue[0] === index }"
+            >
+              {{ year }}年
+            </view>
+          </picker-view-column>
+
+          <!-- 月列 -->
+          <picker-view-column>
+            <view 
+              v-for="(month, index) in months" 
+              :key="index" 
+              class="picker-item"
+              :class="{ 'picker-item-selected': pickerValue[1] === index }"
+            >
+              {{ month }}月
+            </view>
+          </picker-view-column>
+
+          <!-- 日列 -->
+          <picker-view-column>
+            <view 
+              v-for="(day, index) in days" 
+              :key="index" 
+              class="picker-item"
+              :class="{ 'picker-item-selected': pickerValue[2] === index }"
+            >
+              {{ day }}日
+            </view>
+          </picker-view-column>
+        </picker-view>
+      </view>
+
+      <!-- 底部按钮 -->
+      <view class="picker-footer">
+        <view class="picker-btn reset-btn" @click="handleReset">重置</view>
+        <view class="picker-btn confirm-btn" @click="handleConfirm">确定</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'PracticeTimePicker',
+    props: {
+      visible: {
+        type: Boolean,
+        default: false
+      },
+      value: {
+        type: String,
+        default: ''
+      }
+    },
+    data() {
+      const now = new Date()
+      return {
+        pickerValue: [0, 0, 0],
+        selectedYear: now.getFullYear(),
+        selectedMonth: now.getMonth() + 1,
+        selectedDay: now.getDate(),
+        originalYear: now.getFullYear(),
+        originalMonth: now.getMonth() + 1,
+        originalDay: now.getDate(),
+        indicatorStyle: 'height: 50px; background-color: rgba(0, 122, 255, 0.15);',
+        maskStyle: 'background-color: rgba(255, 255, 255, 0.8);',
+        startYear: 1950,
+        endYear: now.getFullYear()
+      }
+    },
+    computed: {
+      years() {
+        const years = []
+        for (let i = this.startYear; i <= this.endYear; i++) {
+          years.push(i)
+        }
+        return years
+      },
+      months() {
+        return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+      },
+      days() {
+        const daysInMonth = new Date(this.selectedYear, this.selectedMonth, 0).getDate()
+        const days = []
+        for (let i = 1; i <= daysInMonth; i++) {
+          days.push(i)
+        }
+        return days
+      }
+    },
+    watch: {
+      visible(newVal) {
+        if (newVal) {
+          // 延迟初始化,确保 DOM 已渲染
+          this.$nextTick(() => {
+            this.initPicker()
+          })
+        }
+      },
+      value(newVal) {
+        if (newVal && this.visible) {
+          this.parseValue(newVal)
+          this.$nextTick(() => {
+            this.updatePickerValue()
+          })
+        }
+      },
+      selectedMonth() {
+        // 月份变化时,需要重新计算天数
+        this.$nextTick(() => {
+          this.updateDayIndex()
+        })
+      },
+      selectedYear() {
+        // 年份变化时,需要重新计算天数(考虑闰年)
+        this.$nextTick(() => {
+          this.updateDayIndex()
+        })
+      }
+    },
+    methods: {
+      initPicker() {
+        if (this.value) {
+          this.parseValue(this.value)
+        } else {
+          const now = new Date()
+          this.selectedYear = now.getFullYear()
+          this.selectedMonth = now.getMonth() + 1
+          this.selectedDay = now.getDate()
+        }
+        this.originalYear = this.selectedYear
+        this.originalMonth = this.selectedMonth
+        this.originalDay = this.selectedDay
+        // 等待计算属性更新后再设置 pickerValue
+        this.$nextTick(() => {
+          this.updatePickerValue()
+        })
+      },
+      parseValue(value) {
+        // 解析日期字符串,支持格式:YYYY-MM-DD 或 YYYY年MM月DD日
+        let year, month, day
+        if (value.includes('-')) {
+          const parts = value.split('-')
+          year = parseInt(parts[0])
+          month = parseInt(parts[1])
+          day = parseInt(parts[2])
+        } else if (value.includes('年')) {
+          const yearMatch = value.match(/(\d+)年/)
+          const monthMatch = value.match(/(\d+)月/)
+          const dayMatch = value.match(/(\d+)日/)
+          year = yearMatch ? parseInt(yearMatch[1]) : new Date().getFullYear()
+          month = monthMatch ? parseInt(monthMatch[1]) : new Date().getMonth() + 1
+          day = dayMatch ? parseInt(dayMatch[1]) : new Date().getDate()
+        } else {
+          const now = new Date()
+          year = now.getFullYear()
+          month = now.getMonth() + 1
+          day = now.getDate()
+        }
+        this.selectedYear = year
+        this.selectedMonth = month
+        this.selectedDay = day
+      },
+      updatePickerValue() {
+        try {
+          const yearIndex = this.years.indexOf(this.selectedYear)
+          const monthIndex = this.months.indexOf(this.selectedMonth)
+          const dayIndex = this.days.indexOf(this.selectedDay)
+          
+          this.pickerValue = [
+            yearIndex >= 0 ? yearIndex : 0,
+            monthIndex >= 0 ? monthIndex : 0,
+            dayIndex >= 0 ? dayIndex : 0
+          ]
+        } catch (error) {
+          console.error('updatePickerValue error:', error)
+          // 如果出错,使用默认值
+          this.pickerValue = [0, 0, 0]
+        }
+      },
+      updateDayIndex() {
+        // 如果当前选中的日期超过了新月份的天数,调整为该月最后一天
+        this.$nextTick(() => {
+          const daysInMonth = new Date(this.selectedYear, this.selectedMonth, 0).getDate()
+          if (this.selectedDay > daysInMonth) {
+            this.selectedDay = daysInMonth
+          }
+          this.updatePickerValue()
+        })
+      },
+      handlePickerChange(e) {
+        const values = e.detail.value
+        if (!values || values.length < 3) {
+          return
+        }
+        
+        const newYear = this.years[values[0]]
+        const newMonth = this.months[values[1]]
+        
+        // 如果年份或月份变化,需要重新计算天数
+        if (newYear !== this.selectedYear || newMonth !== this.selectedMonth) {
+          this.selectedYear = newYear
+          this.selectedMonth = newMonth
+          
+          // 重新计算天数,确保日期不超过当月最大天数
+          const daysInMonth = new Date(this.selectedYear, this.selectedMonth, 0).getDate()
+          if (this.selectedDay > daysInMonth) {
+            this.selectedDay = daysInMonth
+          }
+          
+          // 更新日期索引
+          this.$nextTick(() => {
+            const dayIndex = this.days.indexOf(this.selectedDay)
+            this.pickerValue = [values[0], values[1], dayIndex >= 0 ? dayIndex : 0]
+          })
+        } else {
+          // 只有日期变化,需要确保索引有效
+          const dayIndex = Math.min(values[2], this.days.length - 1)
+          this.selectedDay = this.days[dayIndex] || 1
+          this.pickerValue = [values[0], values[1], dayIndex]
+        }
+      },
+      handleMaskClick() {
+        this.handleCancel()
+      },
+      handleCancel() {
+        // 恢复原始值
+        this.selectedYear = this.originalYear
+        this.selectedMonth = this.originalMonth
+        this.selectedDay = this.originalDay
+        this.updatePickerValue()
+        this.$emit('close')
+      },
+      handleReset() {
+        const now = new Date()
+        this.selectedYear = now.getFullYear()
+        this.selectedMonth = now.getMonth() + 1
+        this.selectedDay = now.getDate()
+        this.updatePickerValue()
+      },
+      handleConfirm() {
+        // 格式化日期字符串,确保月份和日期是两位数
+        const monthStr = this.selectedMonth < 10 ? `0${this.selectedMonth}` : `${this.selectedMonth}`
+        const dayStr = this.selectedDay < 10 ? `0${this.selectedDay}` : `${this.selectedDay}`
+        const dateStr = `${this.selectedYear}-${monthStr}-${dayStr}`
+        this.$emit('confirm', dateStr)
+        this.$emit('close')
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .picker-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.5);
+    // z-index: 1000;
+    display: flex;
+    align-items: flex-end;
+  }
+
+  .picker-container {
+    width: 100%;
+    background-color: #ffffff;
+    border-radius: 32rpx 32rpx 0 0;
+    overflow: hidden;
+    animation: slideUp 0.3s ease-out;
+  }
+
+  @keyframes slideUp {
+    from {
+      transform: translateY(100%);
+    }
+    to {
+      transform: translateY(0);
+    }
+  }
+
+  .picker-header {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 88rpx;
+    padding: 0 60rpx;
+    position: relative;
+    border-bottom: 1rpx solid #f0f0f0;
+  }
+
+  .picker-title {
+    font-size: 36rpx;
+    font-weight: 500;
+    color: #000000;
+  }
+
+  .picker-cancel {
+    position: absolute;
+    right: 60rpx;
+    font-size: 32rpx;
+    color: #999999;
+  }
+
+  .picker-content {
+    height: 400rpx;
+    padding: 20rpx 0;
+  }
+
+  .picker-view {
+    height: 100%;
+    width: 100%;
+  }
+
+  .picker-item {
+    height: 50px;
+    line-height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 32rpx;
+    color: #999999;
+    text-align: center;
+  }
+
+  .picker-item-selected {
+    color: #2652C2 !important;
+    font-weight: 500;
+  }
+
+  .picker-footer {
+    display: flex;
+    padding: 30rpx;
+    gap: 20rpx;
+    border-top: 1rpx solid #f0f0f0;
+  }
+
+  .picker-btn {
+    flex: 1;
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 16rpx;
+    font-size: 32rpx;
+    font-weight: 400;
+  }
+
+  .reset-btn {
+    background-color: #ffffff;
+    border: 2rpx solid #007AFF;
+    color: #000000;
+  }
+
+  .confirm-btn {
+    background-color: #007AFF;
+    color: #ffffff;
+  }
+</style>
+

+ 112 - 13
pages/mine/info/index.vue

@@ -30,15 +30,15 @@
       <view class="info-item" @click="handleEdit('name')">
         <view class="item-label">姓名</view>
         <view class="item-value">
-          <text class="value-text">{{ user.nickName || user.userName || '未设置' }}</text>
+          <text class="value-text">{{ user.name ||  '未设置' }}</text>
           <view class="iconfont icon-right arrow-icon"></view>
         </view>
       </view>
       <view class="info-divider"></view>
-      <view class="info-item" @click="handleEdit('intro')">
+      <view class="info-item" @click="handleEdit('accountBlurb')">
         <view class="item-label">简介</view>
         <view class="item-value">
-          <text class="value-text placeholder">{{ user.intro || '添加一条简介' }}</text>
+          <text class="value-text placeholder">{{ formatIntro(user.accountBlurb) }}</text>
           <view class="iconfont icon-right arrow-icon"></view>
         </view>
       </view>
@@ -46,7 +46,7 @@
       <view class="info-item" @click="handleEdit('phone')">
         <view class="item-label">手机号码</view>
         <view class="item-value">
-          <text class="value-text">{{ user.phonenumber || '未设置' }}</text>
+          <text class="value-text">{{ user.phone || '未设置' }}</text>
           <view class="iconfont icon-right arrow-icon"></view>
         </view>
       </view>
@@ -62,10 +62,10 @@
 
     <!-- 第二个卡片:专业和财务信息 -->
     <view class="info-card">
-      <view class="info-item" @click="handleEdit('practiceTime')">
+      <view class="info-item" @click="handleEdit('practiceStartDate')">
         <view class="item-label">从业时间</view>
         <view class="item-value">
-          <text class="value-text">{{ user.practiceTime || '未设置' }}</text>
+          <text class="value-text">{{ user.practiceStartDate || '未设置' }}</text>
           <view class="iconfont icon-right arrow-icon"></view>
         </view>
       </view>
@@ -120,6 +120,14 @@
         </view>
       </view>
     </view>
+
+    <!-- 从业时间选择器 -->
+    <practice-time-picker 
+      :visible="showPracticeTimePicker" 
+      :value="user.practiceStartDate"
+      @confirm="handlePracticeTimeConfirm"
+      @close="handlePracticeTimeClose"
+    />
   </view>
 </template>
 
@@ -127,13 +135,18 @@
   import { getLawyerInfo, editLawyerUser } from "@/api/api"
   import upload from '@/utils/upload'
   import config from '@/config'
+  import PracticeTimePicker from './components/practice-time-picker.vue'
 
   export default {
+    components: {
+      PracticeTimePicker
+    },
     data() {
       return {
         user: {},
         orderStatus: true,
-        statusBarHeight: 0
+        statusBarHeight: 0,
+        showPracticeTimePicker: false
       }
     },
     onLoad() {
@@ -158,9 +171,9 @@
           if (response.data) {
             // 合并律师信息到 user 对象
             this.user = { ...this.user, ...response.data }
-            // 如果有接单状态字段,使用它
-            if (response.data.orderStatus !== undefined) {
-              this.orderStatus = response.data.orderStatus
+            // 如果有接单状态字段,使用它(orderReceivingStatus: 0-不接单, 1-接单中)
+            if (response.data.orderReceivingStatus !== undefined) {
+              this.orderStatus = response.data.orderReceivingStatus === 1
             }
           }
         }).catch(error => {
@@ -262,15 +275,92 @@
           const currentName = this.user.nickName || this.user.userName || ''
           const userId = this.user.id || 1
           this.$tab.navigateTo(`/pages/mine/info/name?name=${encodeURIComponent(currentName)}&userId=${userId}`)
+        } else if (field === 'accountBlurb') {
+          // 跳转到简介编辑页面
+          const currentIntro = this.user.accountBlurb || ''
+          const userId = this.user.id || 1
+          this.$tab.navigateTo(`/pages/mine/info/intro?accountBlurb=${encodeURIComponent(currentIntro)}&userId=${userId}`)
+        } else if (field === 'phone') {
+          // 跳转到手机号码编辑页面
+          const currentPhone = this.user.phone || ''
+          const userId = this.user.id || 1
+          this.$tab.navigateTo(`/pages/mine/info/phone?phone=${encodeURIComponent(currentPhone)}&userId=${userId}`)
+        } else if (field === 'practiceStartDate') {
+          // 显示从业时间选择器
+          this.showPracticeTimePicker = true
         } else {
           // 其他字段跳转到通用编辑页面
           this.$tab.navigateTo('/pages/mine/info/edit')
         }
       },
+      handlePracticeTimeConfirm(dateStr) {
+        // 显示加载提示
+        uni.showLoading({
+          title: '保存中...',
+          mask: true
+        })
+
+        // 调用API更新从业时间
+        const params = {
+          id: this.user.id || 1,
+          practiceStartDate: dateStr
+        }
+
+        editLawyerUser(params).then(response => {
+          uni.hideLoading()
+          // 更新本地数据
+          this.$set(this.user, 'practiceStartDate', dateStr)
+          uni.showToast({
+            title: '保存成功',
+            icon: 'success'
+          })
+        }).catch(error => {
+          uni.hideLoading()
+          console.error('更新从业时间失败:', error)
+          uni.showToast({
+            title: error.message || '保存失败,请重试',
+            icon: 'none'
+          })
+        })
+      },
+      handlePracticeTimeClose() {
+        this.showPracticeTimePicker = false
+      },
       handleOrderStatusChange(e) {
-        this.orderStatus = e.detail.value
-        // 这里可以调用API更新接单状态
-        // updateOrderStatus(this.orderStatus)
+        const newStatus = e.detail.value
+        // 将布尔值转换为数字:true -> 1 (接单中), false -> 0 (不接单)
+        const orderReceivingStatus = newStatus ? 1 : 0
+        
+        // 显示加载提示
+        uni.showLoading({
+          title: '更新中...',
+          mask: true
+        })
+
+        // 调用API更新接单状态
+        const params = {
+          id: this.user.id || 1,
+          orderReceivingStatus: orderReceivingStatus
+        }
+
+        editLawyerUser(params).then(response => {
+          uni.hideLoading()
+          // 更新本地状态
+          this.orderStatus = newStatus
+          uni.showToast({
+            title: newStatus ? '已开启接单' : '已暂停接单',
+            icon: 'success'
+          })
+        }).catch(error => {
+          uni.hideLoading()
+          console.error('更新接单状态失败:', error)
+          // 恢复原状态
+          this.orderStatus = !newStatus
+          uni.showToast({
+            title: error.message || '更新失败,请重试',
+            icon: 'none'
+          })
+        })
       },
       handleDeregister() {
         this.$modal.confirm('确定要注销账号吗?注销后将无法恢复。').then(() => {
@@ -278,6 +368,15 @@
           this.$modal.showToast('注销账号功能开发中')
         })
       },
+      formatIntro(intro) {
+        if (!intro) {
+          return '添加一条简介'
+        }
+        if (intro.length > 10) {
+          return intro.substring(0, 10) + '...'
+        }
+        return intro
+      },
       handleBack() {
         uni.navigateBack()
       }

+ 242 - 0
pages/mine/info/intro.vue

@@ -0,0 +1,242 @@
+<template>
+  <view class="container" :style="{paddingTop: navbarHeight + 'rpx'}">
+    <!-- 顶部渐变背景 -->
+    <view class="top-gradient"></view>
+
+    <!-- 自定义导航栏 -->
+    <view class="custom-navbar" :style="{paddingTop: statusBarHeight + 'px'}">
+      <view class="navbar-content">
+        <view class="navbar-back" @click="handleBack">
+          <view class="iconfont icon-right back-icon"></view>
+        </view>
+        <text class="navbar-title">简介</text>
+        <view class="navbar-right" @click="handleConfirm">
+          <text class="confirm-text" :class="{ disabled: !canConfirm }">确定</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 输入区域 -->
+    <view class="input-container">
+      <view class="input-wrapper">
+        <textarea 
+          class="intro-textarea" 
+          v-model="introValue" 
+          placeholder="请输入账号简介" 
+          maxlength="300"
+          @input="handleInput"
+          :auto-height="true"
+        />
+        <view class="char-count">{{ introValue.length }}/300</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  import { editLawyerUser } from "@/api/api"
+
+  export default {
+    data() {
+      return {
+        introValue: '',
+        originalIntro: '',
+        statusBarHeight: 0,
+        userId: null
+      }
+    },
+    onLoad(options) {
+      // 获取状态栏高度
+      const systemInfo = uni.getSystemInfoSync()
+      this.statusBarHeight = systemInfo.statusBarHeight || 0
+      
+      // 获取传入的简介和用户ID
+      if (options.accountBlurb) {
+        this.introValue = decodeURIComponent(options.accountBlurb)
+        this.originalIntro = this.introValue
+      }
+      if (options.userId) {
+        this.userId = options.userId
+      }
+    },
+    computed: {
+      navbarHeight() {
+        // 状态栏高度(px转rpx) + 导航栏高度(88rpx) + 间距(20rpx)
+        return (this.statusBarHeight * 2) + 88 + 20
+      },
+      canConfirm() {
+        // 与原始值不同时可以确认
+        return this.introValue !== this.originalIntro
+      }
+    },
+    methods: {
+      handleInput(e) {
+        this.introValue = e.detail.value
+      },
+      handleBack() {
+        uni.navigateBack()
+      },
+      handleConfirm() {
+        if (!this.canConfirm) {
+          return
+        }
+
+        // 显示加载提示
+        uni.showLoading({
+          title: '保存中...',
+          mask: true
+        })
+
+        // 调用API更新简介
+        const params = {
+          id: this.userId || 1, // 如果没有传入userId,使用默认值1
+          accountBlurb: this.introValue.trim()
+        }
+
+        editLawyerUser(params).then(response => {
+          uni.hideLoading()
+          uni.showToast({
+            title: '保存成功',
+            icon: 'success'
+          })
+          // 延迟返回,让用户看到成功提示
+          setTimeout(() => {
+            uni.navigateBack()
+          }, 1500)
+        }).catch(error => {
+          uni.hideLoading()
+          console.error('更新简介失败:', error)
+          uni.showToast({
+            title: error.message || '保存失败,请重试',
+            icon: 'none'
+          })
+        })
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  page {
+    background-color: #f5f5f5;
+  }
+
+  .container {
+    min-height: 100vh;
+    padding: 0 30rpx 20rpx 30rpx;
+    background-color: #f5f5f5;
+    position: relative;
+
+    // 顶部渐变背景
+    .top-gradient {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 800rpx;
+      background: linear-gradient(180deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 197, 253, 0.05) 50%, transparent 100%);
+      z-index: 0;
+      pointer-events: none;
+    }
+
+    // 自定义导航栏
+    .custom-navbar {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      z-index: 999;
+      background-color: transparent;
+      pointer-events: none;
+
+      .navbar-content {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 88rpx;
+        pointer-events: auto;
+        position: relative;
+
+        .navbar-back {
+          position: absolute;
+          left: 30rpx;
+          width: 60rpx;
+          height: 60rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          .back-icon {
+            font-size: 36rpx;
+            color: #000000;
+            transform: rotate(180deg);
+          }
+        }
+
+        .navbar-title {
+          font-size: 36rpx;
+          font-weight: 500;
+          color: #000000;
+        }
+
+        .navbar-right {
+          position: absolute;
+          right: 30rpx;
+          height: 60rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          .confirm-text {
+            font-size: 32rpx;
+            color: #007AFF;
+            font-weight: 400;
+
+            &.disabled {
+              color: #cccccc;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 输入区域
+  .input-container {
+    padding: 40rpx 0;
+    margin-top: 20rpx;
+  }
+
+  .input-wrapper {
+    position: relative;
+    background-color: #ffffff;
+    border-radius: 16rpx;
+    padding: 32rpx 30rpx;
+    min-height: 500rpx;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .intro-textarea {
+    flex: 1;
+    font-size: 32rpx;
+    color: #000000;
+    background-color: transparent;
+    border: none;
+    outline: none;
+    padding: 0;
+    margin: 0;
+    width: 100%;
+    min-height: 400rpx;
+    line-height: 1.6;
+  }
+
+  .char-count {
+    position: absolute;
+    bottom: 32rpx;
+    right: 30rpx;
+    font-size: 28rpx;
+    color: #999999;
+  }
+</style>
+

+ 262 - 0
pages/mine/info/phone.vue

@@ -0,0 +1,262 @@
+<template>
+  <view class="container" :style="{paddingTop: navbarHeight + 'rpx'}">
+    <!-- 顶部渐变背景 -->
+    <view class="top-gradient"></view>
+
+    <!-- 自定义导航栏 -->
+    <view class="custom-navbar" :style="{paddingTop: statusBarHeight + 'px'}">
+      <view class="navbar-content">
+        <view class="navbar-back" @click="handleBack">
+          <view class="iconfont icon-right back-icon"></view>
+        </view>
+        <text class="navbar-title">手机号码</text>
+        <view class="navbar-right" @click="handleConfirm">
+          <text class="confirm-text" :class="{ disabled: !canConfirm }">确定</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 输入区域 -->
+    <view class="input-container">
+      <view class="input-wrapper">
+        <input 
+          class="phone-input" 
+          v-model="phoneValue" 
+          placeholder="请输入手机号码" 
+          maxlength="11"
+          type="number"
+          @input="handleInput"
+        />
+        <view class="char-count">{{ phoneValue.length }}/11</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  import { editLawyerUser } from "@/api/api"
+
+  export default {
+    data() {
+      return {
+        phoneValue: '',
+        originalPhone: '',
+        statusBarHeight: 0,
+        userId: null
+      }
+    },
+    onLoad(options) {
+      // 获取状态栏高度
+      const systemInfo = uni.getSystemInfoSync()
+      this.statusBarHeight = systemInfo.statusBarHeight || 0
+      
+      // 获取传入的手机号码和用户ID
+      if (options.phone) {
+        this.phoneValue = decodeURIComponent(options.phone)
+        this.originalPhone = this.phoneValue
+      }
+      if (options.userId) {
+        this.userId = options.userId
+      }
+    },
+    computed: {
+      navbarHeight() {
+        // 状态栏高度(px转rpx) + 导航栏高度(88rpx) + 间距(20rpx)
+        return (this.statusBarHeight * 2) + 88 + 20
+      },
+      canConfirm() {
+        // 有输入且与原始值不同时可以确认
+        return this.phoneValue.trim().length > 0 && this.phoneValue !== this.originalPhone
+      }
+    },
+    methods: {
+      handleInput(e) {
+        this.phoneValue = e.detail.value
+      },
+      validatePhone(phone) {
+        // 验证手机号码格式:11位数字,以1开头
+        const phoneRegex = /^1[3-9]\d{9}$/
+        return phoneRegex.test(phone)
+      },
+      handleBack() {
+        uni.navigateBack()
+      },
+      handleConfirm() {
+        if (!this.canConfirm) {
+          return
+        }
+
+        const trimmedPhone = this.phoneValue.trim()
+        if (trimmedPhone.length === 0) {
+          uni.showToast({
+            title: '请输入手机号码',
+            icon: 'none'
+          })
+          return
+        }
+
+        // 验证手机号码格式
+        if (!this.validatePhone(trimmedPhone)) {
+          uni.showToast({
+            title: '请输入正确的手机号码',
+            icon: 'none'
+          })
+          return
+        }
+
+        // 显示加载提示
+        uni.showLoading({
+          title: '保存中...',
+          mask: true
+        })
+
+        // 调用API更新手机号码
+        const params = {
+          id: this.userId || 1, // 如果没有传入userId,使用默认值1
+          phone: trimmedPhone
+        }
+
+        editLawyerUser(params).then(response => {
+          uni.hideLoading()
+          uni.showToast({
+            title: '保存成功',
+            icon: 'success'
+          })
+          // 延迟返回,让用户看到成功提示
+          setTimeout(() => {
+            uni.navigateBack()
+          }, 1500)
+        }).catch(error => {
+          uni.hideLoading()
+          console.error('更新手机号码失败:', error)
+          uni.showToast({
+            title: error.message || '保存失败,请重试',
+            icon: 'none'
+          })
+        })
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  page {
+    background-color: #f5f5f5;
+  }
+
+  .container {
+    min-height: 100vh;
+    padding: 0 30rpx 20rpx 30rpx;
+    background-color: #f5f5f5;
+    position: relative;
+
+    // 顶部渐变背景
+    .top-gradient {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 800rpx;
+      background: linear-gradient(180deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 197, 253, 0.05) 50%, transparent 100%);
+      z-index: 0;
+      pointer-events: none;
+    }
+
+    // 自定义导航栏
+    .custom-navbar {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      z-index: 999;
+      background-color: transparent;
+      pointer-events: none;
+
+      .navbar-content {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 88rpx;
+        pointer-events: auto;
+        position: relative;
+
+        .navbar-back {
+          position: absolute;
+          left: 30rpx;
+          width: 60rpx;
+          height: 60rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          .back-icon {
+            font-size: 36rpx;
+            color: #000000;
+            transform: rotate(180deg);
+          }
+        }
+
+        .navbar-title {
+          font-size: 36rpx;
+          font-weight: 500;
+          color: #000000;
+        }
+
+        .navbar-right {
+          position: absolute;
+          right: 30rpx;
+          height: 60rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          .confirm-text {
+            font-size: 32rpx;
+            color: #007AFF;
+            font-weight: 400;
+
+            &.disabled {
+              color: #cccccc;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 输入区域
+  .input-container {
+    padding: 40rpx 0;
+    margin-top: 20rpx;
+  }
+
+  .input-wrapper {
+    position: relative;
+    background-color: #f5f5f5;
+    border-radius: 16rpx;
+    padding: 32rpx 30rpx;
+    min-height: 100rpx;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .phone-input {
+    flex: 1;
+    font-size: 32rpx;
+    color: #000000;
+    background-color: transparent;
+    border: none;
+    outline: none;
+    padding: 0;
+    margin: 0;
+  }
+
+  .char-count {
+    font-size: 28rpx;
+    color: #999999;
+    margin-left: 20rpx;
+    white-space: nowrap;
+  }
+</style>
+