index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <template>
  2. <view class="container" :style="{paddingTop: navbarHeight + 'rpx'}">
  3. <!-- 顶部渐变背景 -->
  4. <view class="top-gradient"></view>
  5. <!-- 自定义导航栏 -->
  6. <view class="custom-navbar" :style="{paddingTop: statusBarHeight + 'px'}">
  7. <view class="navbar-content">
  8. <view class="navbar-back" @click="handleBack">
  9. <view class="iconfont icon-right back-icon"></view>
  10. </view>
  11. <text class="navbar-title">账号信息</text>
  12. <view class="navbar-placeholder"></view>
  13. </view>
  14. </view>
  15. <!-- 第一个卡片:基本信息 -->
  16. <view class="info-card">
  17. <view class="info-item" @click="handleAvatar">
  18. <view class="item-label">头像</view>
  19. <view class="item-value">
  20. <image v-if="user.headImg" :src="user.headImg" class="avatar-img" mode="aspectFill"></image>
  21. <view v-else class="avatar-placeholder">
  22. <view class="iconfont icon-people"></view>
  23. </view>
  24. <view class="iconfont icon-right arrow-icon"></view>
  25. </view>
  26. </view>
  27. <view class="info-divider"></view>
  28. <view class="info-item" @click="handleEdit('name')">
  29. <view class="item-label">姓名</view>
  30. <view class="item-value">
  31. <text class="value-text">{{ user.name || '未设置' }}</text>
  32. <view class="iconfont icon-right arrow-icon"></view>
  33. </view>
  34. </view>
  35. <view class="info-divider"></view>
  36. <view class="info-item" @click="handleEdit('accountBlurb')">
  37. <view class="item-label">简介</view>
  38. <view class="item-value">
  39. <text class="value-text placeholder">{{ formatIntro(user.accountBlurb) }}</text>
  40. <view class="iconfont icon-right arrow-icon"></view>
  41. </view>
  42. </view>
  43. <view class="info-divider"></view>
  44. <view class="info-item" @click="handleEdit('phone')">
  45. <view class="item-label">手机号码</view>
  46. <view class="item-value">
  47. <text class="value-text">{{ user.phone || '未设置' }}</text>
  48. <view class="iconfont icon-right arrow-icon"></view>
  49. </view>
  50. </view>
  51. <view class="info-divider"></view>
  52. <view class="info-item">
  53. <view class="item-label">接单状态</view>
  54. <view class="item-value">
  55. <text class="status-text" :class="{ active: orderStatus }">{{ orderStatus ? '接单中' : '暂停接单' }}</text>
  56. <switch :checked="orderStatus" @change="handleOrderStatusChange" color="#007AFF" />
  57. </view>
  58. </view>
  59. </view>
  60. <!-- 第二个卡片:专业和财务信息 -->
  61. <view class="info-card">
  62. <view class="info-item" @click="handleEdit('practiceStartDate')">
  63. <view class="item-label">从业时间</view>
  64. <view class="item-value">
  65. <text class="value-text">{{ user.practiceStartDate || '未设置' }}</text>
  66. <view class="iconfont icon-right arrow-icon"></view>
  67. </view>
  68. </view>
  69. <view class="info-divider"></view>
  70. <view class="info-item" @click="handleEdit('professionalField')">
  71. <view class="item-label">专业领域</view>
  72. <view class="item-value">
  73. <text class="value-text">{{ user.professionalField || '未设置' }}</text>
  74. <view class="iconfont icon-right arrow-icon"></view>
  75. </view>
  76. </view>
  77. <view class="info-divider"></view>
  78. <view class="info-item" @click="handleEdit('legalScenarios')">
  79. <view class="item-label">法律场景</view>
  80. <view class="item-value">
  81. <text class="value-text">{{ user.legalScenarios || '未设置' }}</text>
  82. <view class="iconfont icon-right arrow-icon"></view>
  83. </view>
  84. </view>
  85. <view class="info-divider"></view>
  86. <view class="info-item" @click="handleEdit('lawFirm')">
  87. <view class="item-label">所属律所</view>
  88. <view class="item-value">
  89. <text class="value-text">{{ user.lawFirm || '未设置' }}</text>
  90. <view class="iconfont icon-right arrow-icon"></view>
  91. </view>
  92. </view>
  93. <view class="info-divider"></view>
  94. <view class="info-item" @click="handleEdit('receivingAccount')">
  95. <view class="item-label">收款账户</view>
  96. <view class="item-value">
  97. <text class="value-text">{{ user.receivingAccount || '未设置' }}</text>
  98. <view class="iconfont icon-right arrow-icon"></view>
  99. </view>
  100. </view>
  101. <view class="info-divider"></view>
  102. <view class="info-item" @click="handleEdit('address')">
  103. <view class="item-label">联系地址</view>
  104. <view class="item-value">
  105. <text class="value-text">{{ user.address || '未设置' }}</text>
  106. <view class="iconfont icon-right arrow-icon"></view>
  107. </view>
  108. </view>
  109. </view>
  110. <!-- 第三个卡片:账号管理 -->
  111. <view class="info-card">
  112. <view class="info-item" @click="handleDeregister">
  113. <view class="item-label">注销账号</view>
  114. <view class="item-value">
  115. <view class="iconfont icon-right arrow-icon"></view>
  116. </view>
  117. </view>
  118. </view>
  119. <!-- 从业时间选择器 -->
  120. <practice-time-picker
  121. :visible="showPracticeTimePicker"
  122. :value="user.practiceStartDate"
  123. @confirm="handlePracticeTimeConfirm"
  124. @close="handlePracticeTimeClose"
  125. />
  126. </view>
  127. </template>
  128. <script>
  129. import { getLawyerInfo, editLawyerUser } from "@/api/api"
  130. import upload from '@/utils/upload'
  131. import config from '@/config'
  132. import PracticeTimePicker from './components/practice-time-picker.vue'
  133. export default {
  134. components: {
  135. PracticeTimePicker
  136. },
  137. data() {
  138. return {
  139. user: {},
  140. orderStatus: true,
  141. statusBarHeight: 0,
  142. showPracticeTimePicker: false
  143. }
  144. },
  145. onLoad() {
  146. // 获取状态栏高度
  147. const systemInfo = uni.getSystemInfoSync()
  148. this.statusBarHeight = systemInfo.statusBarHeight || 0
  149. },
  150. onShow() {
  151. // 从其他页面返回时刷新数据
  152. this.getLawyerInfoData()
  153. },
  154. computed: {
  155. navbarHeight() {
  156. // 状态栏高度(px转rpx) + 导航栏高度(88rpx) + 间距(20rpx)
  157. return (this.statusBarHeight * 2) + 88 + 20
  158. }
  159. },
  160. methods: {
  161. getLawyerInfoData() {
  162. getLawyerInfo({ lawyerId: 1 }).then(response => {
  163. // 处理返回的律师信息
  164. if (response.data) {
  165. // 合并律师信息到 user 对象
  166. this.user = { ...this.user, ...response.data }
  167. // 如果有接单状态字段,使用它(orderReceivingStatus: 0-不接单, 1-接单中)
  168. if (response.data.orderReceivingStatus !== undefined) {
  169. this.orderStatus = response.data.orderReceivingStatus === 1
  170. }
  171. }
  172. }).catch(error => {
  173. console.error('获取律师信息失败:', error)
  174. })
  175. },
  176. handleAvatar() {
  177. const that = this
  178. uni.chooseImage({
  179. count: 1,
  180. sizeType: ['compressed'],
  181. sourceType: ['album'],
  182. success: function(res) {
  183. const tempFilePath = res.tempFilePaths[0]
  184. // 显示上传中提示
  185. uni.showLoading({
  186. title: '上传中...',
  187. mask: true
  188. })
  189. // 上传图片,使用 baseUrl1
  190. upload({
  191. url: '/file/upload',
  192. filePath: tempFilePath,
  193. name: 'file',
  194. baseUrl: config.baseUrl1
  195. }).then(response => {
  196. console.log('上传响应:', response)
  197. // 上传成功,更新用户头像
  198. // 根据实际返回的数据结构处理
  199. let headImg = null
  200. if (response.data) {
  201. // 如果 response.data 是字符串,直接使用
  202. if (typeof response.data === 'string') {
  203. headImg = response.data
  204. }
  205. // 如果 response.data 是对象,尝试获取 url 或 imgUrl
  206. else if (response.data.url) {
  207. headImg = response.data.url
  208. } else if (response.data.imgUrl) {
  209. headImg = response.data.imgUrl
  210. }
  211. } else if (response.url) {
  212. headImg = response.url
  213. } else if (response.imgUrl) {
  214. headImg = response.imgUrl
  215. }
  216. if (!headImg) {
  217. throw new Error('上传失败,未返回图片地址')
  218. }
  219. // 如果返回的是相对路径,需要拼接 baseUrl1
  220. if (headImg.startsWith('/')) {
  221. headImg = config.baseUrl1 + headImg
  222. }
  223. // 使用 Vue.set 确保响应式更新
  224. that.$set(that.user, 'headImg', headImg)
  225. // 调用API更新用户信息到服务器
  226. let params={
  227. id:that.user.id,
  228. headImg: headImg
  229. }
  230. editLawyerUser(params).then(response => {
  231. console.log('更新响应:', response)
  232. }).catch(error => {
  233. console.error('更新失败:', error)
  234. })
  235. }).then(() => {
  236. // 更新成功提示
  237. uni.showToast({
  238. title: '头像更新成功'
  239. })
  240. }).catch(error => {
  241. uni.hideLoading()
  242. console.error('上传头像失败:', error)
  243. uni.showToast({
  244. title: error.message || '上传失败,请重试',
  245. icon: 'none'
  246. })
  247. })
  248. },
  249. fail: function(error) {
  250. console.error('选择图片失败:', error)
  251. uni.showToast({
  252. title: '选择图片失败',
  253. icon: 'none'
  254. })
  255. }
  256. })
  257. },
  258. handleEdit(field) {
  259. // 根据字段类型跳转到不同的编辑页面
  260. if (field === 'name') {
  261. // 跳转到姓名编辑页面
  262. const currentName = this.user.name
  263. const userId = this.user.id || 1
  264. this.$tab.navigateTo(`/pages/mine/info/name?name=${encodeURIComponent(currentName)}&userId=${userId}`)
  265. } else if (field === 'accountBlurb') {
  266. // 跳转到简介编辑页面
  267. const currentIntro = this.user.accountBlurb || ''
  268. const userId = this.user.id || 1
  269. this.$tab.navigateTo(`/pages/mine/info/intro?accountBlurb=${encodeURIComponent(currentIntro)}&userId=${userId}`)
  270. } else if (field === 'phone') {
  271. // 跳转到手机号码编辑页面
  272. const currentPhone = this.user.phone || ''
  273. const userId = this.user.id || 1
  274. this.$tab.navigateTo(`/pages/mine/info/phone?phone=${encodeURIComponent(currentPhone)}&userId=${userId}`)
  275. } else if (field === 'practiceStartDate') {
  276. // 显示从业时间选择器
  277. this.showPracticeTimePicker = true
  278. } else {
  279. // 其他字段跳转到通用编辑页面
  280. this.$tab.navigateTo('/pages/mine/info/edit')
  281. }
  282. },
  283. handlePracticeTimeConfirm(dateStr) {
  284. // 显示加载提示
  285. uni.showLoading({
  286. title: '保存中...',
  287. mask: true
  288. })
  289. // 调用API更新从业时间
  290. const params = {
  291. id: this.user.id || 1,
  292. practiceStartDate: dateStr
  293. }
  294. editLawyerUser(params).then(response => {
  295. uni.hideLoading()
  296. // 更新本地数据
  297. this.$set(this.user, 'practiceStartDate', dateStr)
  298. uni.showToast({
  299. title: '保存成功',
  300. icon: 'success'
  301. })
  302. }).catch(error => {
  303. uni.hideLoading()
  304. console.error('更新从业时间失败:', error)
  305. uni.showToast({
  306. title: error.message || '保存失败,请重试',
  307. icon: 'none'
  308. })
  309. })
  310. },
  311. handlePracticeTimeClose() {
  312. this.showPracticeTimePicker = false
  313. },
  314. handleOrderStatusChange(e) {
  315. const newStatus = e.detail.value
  316. // 将布尔值转换为数字:true -> 1 (接单中), false -> 0 (不接单)
  317. const orderReceivingStatus = newStatus ? 1 : 0
  318. // 显示加载提示
  319. uni.showLoading({
  320. title: '更新中...',
  321. mask: true
  322. })
  323. // 调用API更新接单状态
  324. const params = {
  325. id: this.user.id || 1,
  326. orderReceivingStatus: orderReceivingStatus
  327. }
  328. editLawyerUser(params).then(response => {
  329. uni.hideLoading()
  330. // 更新本地状态
  331. this.orderStatus = newStatus
  332. uni.showToast({
  333. title: newStatus ? '已开启接单' : '已暂停接单',
  334. icon: 'success'
  335. })
  336. }).catch(error => {
  337. uni.hideLoading()
  338. console.error('更新接单状态失败:', error)
  339. // 恢复原状态
  340. this.orderStatus = !newStatus
  341. uni.showToast({
  342. title: error.message || '更新失败,请重试',
  343. icon: 'none'
  344. })
  345. })
  346. },
  347. handleDeregister() {
  348. this.$modal.confirm('确定要注销账号吗?注销后将无法恢复。').then(() => {
  349. // 调用注销账号API
  350. this.$modal.showToast('注销账号功能开发中')
  351. })
  352. },
  353. formatIntro(intro) {
  354. if (!intro) {
  355. return '添加一条简介'
  356. }
  357. if (intro.length > 10) {
  358. return intro.substring(0, 10) + '...'
  359. }
  360. return intro
  361. },
  362. handleBack() {
  363. uni.navigateBack()
  364. }
  365. }
  366. }
  367. </script>
  368. <style lang="scss" scoped>
  369. page {
  370. background-color: #f5f5f5;
  371. }
  372. .container {
  373. min-height: 100vh;
  374. padding: 0 30rpx 20rpx 30rpx;
  375. background-color: #f5f5f5;
  376. position: relative;
  377. // 顶部渐变背景
  378. .top-gradient {
  379. position: fixed;
  380. top: 0;
  381. left: 0;
  382. right: 0;
  383. height: 800rpx;
  384. background: linear-gradient(180deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 197, 253, 0.05) 50%, transparent 100%);
  385. z-index: 0;
  386. pointer-events: none;
  387. }
  388. // 自定义导航栏
  389. .custom-navbar {
  390. position: fixed;
  391. top: 0;
  392. left: 0;
  393. right: 0;
  394. z-index: 999;
  395. background-color: transparent;
  396. pointer-events: none;
  397. .navbar-content {
  398. display: flex;
  399. align-items: center;
  400. justify-content: center;
  401. height: 88rpx;
  402. pointer-events: auto;
  403. position: relative;
  404. .navbar-back {
  405. position: absolute;
  406. left: 30rpx;
  407. width: 60rpx;
  408. height: 60rpx;
  409. display: flex;
  410. align-items: center;
  411. justify-content: center;
  412. .back-icon {
  413. font-size: 36rpx;
  414. color: #000000;
  415. transform: rotate(180deg);
  416. }
  417. }
  418. .navbar-title {
  419. font-size: 36rpx;
  420. font-weight: 500;
  421. color: #000000;
  422. }
  423. .navbar-placeholder {
  424. position: absolute;
  425. right: 30rpx;
  426. width: 60rpx;
  427. height: 60rpx;
  428. }
  429. }
  430. }
  431. }
  432. .info-card {
  433. background-color: #ffffff;
  434. border-radius: 16rpx;
  435. margin-bottom: 20rpx;
  436. overflow: hidden;
  437. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
  438. .info-item {
  439. display: flex;
  440. align-items: center;
  441. justify-content: space-between;
  442. padding: 32rpx 30rpx;
  443. min-height: 100rpx;
  444. .item-label {
  445. font-size: 32rpx;
  446. color: #000000;
  447. font-weight: 400;
  448. }
  449. .item-value {
  450. display: flex;
  451. align-items: center;
  452. flex: 1;
  453. justify-content: flex-end;
  454. .avatar-img {
  455. width: 80rpx;
  456. height: 80rpx;
  457. border-radius: 50%;
  458. margin-right: 20rpx;
  459. }
  460. .avatar-placeholder {
  461. width: 80rpx;
  462. height: 80rpx;
  463. border-radius: 50%;
  464. background-color: #e0e0e0;
  465. display: flex;
  466. align-items: center;
  467. justify-content: center;
  468. margin-right: 20rpx;
  469. .iconfont {
  470. font-size: 40rpx;
  471. color: #999999;
  472. }
  473. }
  474. .value-text {
  475. font-size: 32rpx;
  476. color: #000000;
  477. margin-right: 20rpx;
  478. &.placeholder {
  479. color: #999999;
  480. }
  481. }
  482. .status-text {
  483. font-size: 32rpx;
  484. color: #999999;
  485. margin-right: 20rpx;
  486. &.active {
  487. color: #007AFF;
  488. }
  489. }
  490. .arrow-icon {
  491. font-size: 28rpx;
  492. color: #cccccc;
  493. }
  494. }
  495. }
  496. .info-divider {
  497. height: 1rpx;
  498. background-color: #f0f0f0;
  499. margin: 0 30rpx;
  500. }
  501. }
  502. </style>