customerDetail.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <template>
  2. <view class="app-container detail-page pb-24">
  3. <TopBar title="客户详情" show-back />
  4. <view v-if="loading" class="py-20 text-center">
  5. <text class="text-gray-400">加载中...</text>
  6. </view>
  7. <view v-else-if="!customer" class="px-4 py-6">
  8. <EmptyState text="客户不存在" />
  9. </view>
  10. <view v-else class="px-4 py-4 gap-y-4">
  11. <!-- 基本信息 -->
  12. <view class="card p-4">
  13. <view class="flex items-center justify-between mb-4">
  14. <view class="flex-1 min-w-0 mr-3">
  15. <text class="text-lg font-bold text-gray-800 truncate">{{ customer.customerName }}</text>
  16. <text v-if="customer.createTime" class="text-xs text-gray-400 block mt-1">
  17. 创建于 {{ formatDate(customer.createTime, 'YYYY-MM-DD') }}
  18. </text>
  19. </view>
  20. <view
  21. class="status-pill px-2.5 py-0.5 rounded-full text-xs flex-shrink-0"
  22. :style="{ backgroundColor: statusBg(customer.status), color: statusColor(customer.status) }"
  23. >
  24. {{ statusText(customer.status) }}
  25. </view>
  26. </view>
  27. <view class="flex items-center justify-between py-3 border-t border-gray-100">
  28. <view class="flex items-center">
  29. <text class="uni-icons uniui-person-filled text-primary mr-2"></text>
  30. <text class="text-sm text-gray-500">联系人</text>
  31. </view>
  32. <text class="text-sm text-gray-800">{{ customer.contactName || '-' }}</text>
  33. </view>
  34. <view class="flex items-center justify-between py-3 border-t border-gray-100">
  35. <view class="flex items-center">
  36. <text class="uni-icons uniui-phone-filled text-primary mr-2"></text>
  37. <text class="text-sm text-gray-500">联系电话</text>
  38. </view>
  39. <view class="flex items-center" @click="callPhone(customer.phone)">
  40. <text class="text-sm text-primary">{{ customer.phone || '-' }}</text>
  41. <text v-if="customer.phone" class="uni-icons uniui-arrowright text-gray-400 ml-1"></text>
  42. </view>
  43. </view>
  44. </view>
  45. <!-- 地址信息 -->
  46. <view class="card p-4">
  47. <view class="detail-section-title">
  48. <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
  49. <text>地址信息</text>
  50. </view>
  51. <view class="detail-row">
  52. <text class="detail-label">省市区</text>
  53. <text class="detail-value text-right flex-1">{{ region || '-' }}</text>
  54. </view>
  55. <view class="detail-row">
  56. <text class="detail-label">街道</text>
  57. <text class="detail-value text-right flex-1">{{ customer.street || '-' }}</text>
  58. </view>
  59. <view class="detail-row">
  60. <text class="detail-label">详细地址</text>
  61. <text class="detail-value text-right flex-1">{{ customer.detailAddress || '-' }}</text>
  62. </view>
  63. <view v-if="customer.latitude || customer.longitude" class="detail-row">
  64. <text class="detail-label">经纬度</text>
  65. <text class="detail-value text-right flex-1">{{ customer.latitude || '-' }}, {{ customer.longitude || '-' }}</text>
  66. </view>
  67. </view>
  68. <!-- 备注 -->
  69. <view v-if="customer.remark" class="card p-4">
  70. <view class="detail-section-title">
  71. <text class="uni-icons uniui-compose text-primary mr-2"></text>
  72. <text>备注</text>
  73. </view>
  74. <text class="text-sm text-gray-600">{{ customer.remark }}</text>
  75. </view>
  76. <!-- 关联数据提示 -->
  77. <view class="card p-4">
  78. <view class="detail-section-title">
  79. <text class="uni-icons uniui-info-filled text-primary mr-2"></text>
  80. <text>关联项目 / 任务</text>
  81. </view>
  82. <text class="text-xs text-gray-400">后端暂未提供按客户筛选的接口,待补充后展示</text>
  83. </view>
  84. <!-- 操作按钮 -->
  85. <view class="px-4 pt-2 pb-6 flex gap-3">
  86. <button
  87. class="flex-1 py-4 btn-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
  88. @click="goToEdit"
  89. >
  90. <text class="uni-icons uniui-compose mr-2"></text>
  91. 编辑
  92. </button>
  93. <button
  94. class="flex-1 py-4 btn-danger-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
  95. @click="onDelete"
  96. >
  97. <text class="uni-icons uniui-trash mr-2"></text>
  98. 删除
  99. </button>
  100. </view>
  101. </view>
  102. </view>
  103. </template>
  104. <script setup lang="ts">
  105. import { ref, computed } from 'vue'
  106. import { onLoad } from '@dcloudio/uni-app'
  107. import type { Customer } from '../../types'
  108. import { getCustomerDetail, deleteCustomer } from '../../api/customer'
  109. import {
  110. formatDate,
  111. getCustomerStatusText,
  112. getCustomerStatusColor,
  113. getCustomerStatusBgColor
  114. } from '../../utils'
  115. import TopBar from '../../components/common/TopBar.vue'
  116. import EmptyState from '../../components/common/EmptyState.vue'
  117. const customer = ref<Customer | null>(null)
  118. const loading = ref(true)
  119. const customerId = ref<number | string>('')
  120. const statusText = getCustomerStatusText
  121. const statusColor = getCustomerStatusColor
  122. const statusBg = getCustomerStatusBgColor
  123. const region = computed(() => {
  124. if (!customer.value) return ''
  125. const parts = [customer.value.province, customer.value.city, customer.value.district].filter(Boolean)
  126. return parts.join('')
  127. })
  128. function callPhone(phone?: string) {
  129. if (!phone) return
  130. uni.makePhoneCall({ phoneNumber: phone })
  131. }
  132. function goToEdit() {
  133. uni.navigateTo({ url: `/subPackages/pages-customer/customerForm?id=${customerId.value}` })
  134. }
  135. function onDelete() {
  136. uni.showModal({
  137. title: '确认删除',
  138. content: '删除后无法恢复,是否继续?',
  139. success: async (res) => {
  140. if (res.confirm) {
  141. try {
  142. await deleteCustomer(customerId.value)
  143. uni.showToast({ title: '已删除', icon: 'success' })
  144. setTimeout(() => uni.navigateBack(), 800)
  145. } catch (e) {
  146. uni.showToast({ title: '删除失败', icon: 'none' })
  147. }
  148. }
  149. }
  150. })
  151. }
  152. async function loadDetail(id: number | string) {
  153. loading.value = true
  154. try {
  155. customer.value = await getCustomerDetail(id)
  156. } catch (e) {
  157. customer.value = null
  158. } finally {
  159. loading.value = false
  160. }
  161. }
  162. onLoad((options) => {
  163. const id = options?.id
  164. if (id) {
  165. customerId.value = id
  166. loadDetail(id)
  167. } else {
  168. loading.value = false
  169. }
  170. })
  171. </script>
  172. <style scoped>
  173. .detail-page {
  174. background-color: #f6fbf9;
  175. }
  176. .card {
  177. border-radius: 20px;
  178. margin-bottom: 0;
  179. border: 1px solid rgba(164, 216, 152, 0.28);
  180. box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
  181. }
  182. .detail-section-title {
  183. padding: 0;
  184. border-bottom: none;
  185. margin-bottom: 12px;
  186. }
  187. .text-primary {
  188. color: #368f6f;
  189. }
  190. .status-pill {
  191. font-weight: 500;
  192. }
  193. .btn-ghost {
  194. background-color: #ffffff;
  195. border: 1px solid #368f6f;
  196. color: #368f6f;
  197. }
  198. .btn-ghost:active {
  199. background-color: rgba(164, 216, 152, 0.12);
  200. }
  201. .btn-danger-ghost {
  202. background-color: #ffffff;
  203. border: 1px solid #fca5a5;
  204. color: #ef4444;
  205. }
  206. .btn-danger-ghost:active {
  207. background-color: #fef2f2;
  208. }
  209. </style>