PageOne.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. <template>
  2. <view class="page-one-container">
  3. <!-- 图片资料标题 -->
  4. <view class="page-header">
  5. <!-- <text class="page-title">图片资料</text>
  6. <u-button
  7. size="small"
  8. @click="handleSaveAllImages"
  9. class="save-all-btn"
  10. >
  11. 保存实物图
  12. </u-button> -->
  13. <view class="detail-image-header">
  14. <text class="detail-image-title">图片资料</text>
  15. <view class="copy-btn" @click="handleSaveAllImages">
  16. <text>一键复制</text>
  17. </view>
  18. </view>
  19. </view>
  20. <!-- 聊天记录/通话记录卡片 -->
  21. <view class="card-wrap">
  22. <view class="card-title">
  23. <text :class="{ 'active': recordType === 'chat' }" @click="recordType = 'chat'">
  24. 聊天记录
  25. </text>
  26. <text class="divider">|</text>
  27. <text :class="{ 'active': recordType === 'call' }" @click="recordType = 'call'">
  28. 通话记录
  29. </text>
  30. </view>
  31. <!-- 聊天记录 -->
  32. <view v-if="recordType === 'chat'" class="image-upload-container">
  33. <view class="image-list">
  34. <view v-for="(item, index) in chatRecordsList" :key="`chat-${index}`" class="image-item">
  35. <PicComp :src="item.fileUrl" @needPreviewPic="previewImage" />
  36. <view class="delete-btn" @click="handleDeleteImage(item)">×</view>
  37. </view>
  38. <view class="upload-btn" @click="handleUploadImage('chatRecords')">
  39. <u-icon name="plus" size="40" color="#999" />
  40. </view>
  41. </view>
  42. </view>
  43. <!-- 通话录音 -->
  44. <view v-if="recordType === 'call'" class="call-records-container">
  45. <sound-recorder v-for="item in soundRecordList" :key="item.fileName" :data="item"
  46. @handleDelectThisSoundRecord="handleDeleteSoundRecord" />
  47. </view>
  48. </view>
  49. <!-- 实物图卡片 -->
  50. <view class="card-wrap">
  51. <view class="card-title">实物图</view>
  52. <view class="image-upload-container">
  53. <view class="image-list">
  54. <view v-for="(item, index) in truePicList" :key="`truePic-${index}`" class="image-item">
  55. <PicComp :src="item.fileUrl" @needPreviewPic="previewTrueImage" />
  56. <view class="delete-btn" @click="handleDeleteImage(item)">×</view>
  57. </view>
  58. <view class="upload-btn" @click="handleUploadImage('truePic')">
  59. <u-icon name="plus" size="40" color="#999" />
  60. </view>
  61. </view>
  62. </view>
  63. </view>
  64. <!-- 基本信息卡片 -->
  65. <view class="info-card">
  66. <view class="info-card-title">基本信息</view>
  67. <u-row class="info-row">
  68. <u-col span="6">
  69. <view class="info-label">发单人</view>
  70. <view class="info-value">{{ orderDetail.createNickName || '未填写' }}</view>
  71. </u-col>
  72. <u-col span="6">
  73. <view class="info-label">型号</view>
  74. <view class="info-value">{{ orderDetail.model || '未填写' }}</view>
  75. </u-col>
  76. </u-row>
  77. <u-row class="info-row">
  78. <u-col span="6">
  79. <view class="info-label">上门时间</view>
  80. <view class="info-value">{{ orderDetail.visitTime || '未填写' }}</view>
  81. </u-col>
  82. <u-col span="6">
  83. <view class="info-label">地址</view>
  84. <view class="info-value">{{ orderDetail.address || '未填写' }}</view>
  85. </u-col>
  86. </u-row>
  87. </view>
  88. <!-- 联系方式卡片 -->
  89. <view class="contact-card">
  90. <view class="contact-item phone-card" @click="handlePhoneClick">
  91. <u-icon name="phone" size="40" color="#07C160" />
  92. <view class="contact-title">电话</view>
  93. <view v-if="orderDetail.phone" class="red-dot"></view>
  94. </view>
  95. <view class="contact-item wechat-card" @click="handleWechatClick">
  96. <u-icon name="chat" size="40" color="#07C160" />
  97. <view class="contact-title">微信</view>
  98. <view v-if="orderDetail.wechat" class="red-dot"></view>
  99. </view>
  100. </view>
  101. <!-- 下一步按钮 -->
  102. <view class="space-block"></view>
  103. <u-button class="next-btn" @click="handleNext" type="primary" size="middle">
  104. 下一步
  105. </u-button>
  106. </view>
  107. </template>
  108. <script>
  109. import PicComp from './PicComp.vue'
  110. import soundRecorder from '@/components/soundRecorder/soundRecorder.vue'
  111. import imageUpload from '../utils/imageUpload.js'
  112. export default {
  113. name: 'PageOne',
  114. components: {
  115. PicComp,
  116. soundRecorder
  117. },
  118. props: {
  119. orderDetail: {
  120. type: Object,
  121. default: () => ({})
  122. },
  123. orderId: {
  124. type: String,
  125. default: ''
  126. },
  127. currentReceipt: {
  128. type: Object,
  129. default: () => ({})
  130. }
  131. },
  132. data() {
  133. return {
  134. recordType: 'chat', // 'chat' | 'call'
  135. chatRecordsList: [],
  136. truePicList: [],
  137. soundRecordList: []
  138. }
  139. },
  140. watch: {
  141. currentReceipt: {
  142. handler(newVal) {
  143. if (newVal && newVal.id) {
  144. this.loadImageList()
  145. this.loadCallRecords()
  146. }
  147. },
  148. immediate: true,
  149. deep: true
  150. }
  151. },
  152. methods: {
  153. /**
  154. * 加载图片列表
  155. */
  156. async loadImageList() {
  157. if (!this.currentReceipt.id || !this.orderDetail.itemBrand) return
  158. try {
  159. // 加载聊天记录
  160. const chatList = await imageUpload.getFileList(
  161. '2',
  162. '1',
  163. this.currentReceipt.id,
  164. this.orderDetail.itemBrand,
  165. this.currentReceipt.clueId
  166. )
  167. this.chatRecordsList = chatList || []
  168. // 加载实物图
  169. const truePicList = await imageUpload.getFileList(
  170. '2',
  171. '2',
  172. this.currentReceipt.id,
  173. this.orderDetail.itemBrand,
  174. this.currentReceipt.clueId
  175. )
  176. this.truePicList = truePicList || []
  177. } catch (error) {
  178. console.error('加载图片列表失败:', error)
  179. }
  180. },
  181. /**
  182. * 加载通话记录
  183. */
  184. async loadCallRecords() {
  185. if (!this.currentReceipt.clueId) return
  186. try {
  187. const { data } = await uni.$u.api.getCallClueFileByClueId({
  188. clueId: this.currentReceipt.clueId
  189. })
  190. this.soundRecordList = data || []
  191. } catch (error) {
  192. console.error('加载通话记录失败:', error)
  193. }
  194. },
  195. /**
  196. * 上传图片
  197. */
  198. async handleUploadImage(type) {
  199. try {
  200. const filePaths = await imageUpload.chooseImage(9)
  201. const uploadResults = await imageUpload.uploadFiles(filePaths)
  202. // 绑定订单文件
  203. const orderFileType = type === 'truePic' ? '2' : '1'
  204. await imageUpload.bindOrderFile(
  205. this.currentReceipt.clueId,
  206. this.currentReceipt.id,
  207. orderFileType,
  208. uploadResults
  209. )
  210. // 刷新列表
  211. this.loadImageList()
  212. } catch (error) {
  213. console.error('上传图片失败:', error)
  214. }
  215. },
  216. /**
  217. * 删除图片
  218. */
  219. async handleDeleteImage(item) {
  220. uni.showModal({
  221. title: '提示',
  222. content: '确定要删除这张图片吗?',
  223. success: async (res) => {
  224. if (res.confirm) {
  225. try {
  226. await imageUpload.deleteFile(item.id)
  227. this.loadImageList()
  228. } catch (error) {
  229. console.error('删除图片失败:', error)
  230. }
  231. }
  232. }
  233. })
  234. },
  235. /**
  236. * 删除录音
  237. */
  238. async handleDeleteSoundRecord({ id }) {
  239. uni.showModal({
  240. title: '提示',
  241. content: '是否确定删除?',
  242. success: async (res) => {
  243. if (res.confirm) {
  244. try {
  245. await uni.$u.api.deleteClueFile([id])
  246. uni.showToast({
  247. title: '删除成功',
  248. icon: 'success'
  249. })
  250. this.loadCallRecords()
  251. } catch (error) {
  252. uni.showToast({
  253. title: '删除失败',
  254. icon: 'error'
  255. })
  256. }
  257. }
  258. }
  259. })
  260. },
  261. /**
  262. * 预览图片
  263. */
  264. previewImage(src) {
  265. const urlList = this.chatRecordsList.map(item => item.fileUrl)
  266. uni.previewImage({
  267. urls: urlList,
  268. current: src
  269. })
  270. },
  271. /**
  272. * 预览实物图
  273. */
  274. previewTrueImage(src) {
  275. const urlList = this.truePicList.map(item => item.fileUrl)
  276. uni.previewImage({
  277. urls: urlList,
  278. current: src
  279. })
  280. },
  281. /**
  282. * 保存全部图片
  283. */
  284. async handleSaveAllImages() {
  285. const allUrls = this.truePicList.map(item => item.fileUrl)
  286. if (allUrls.length === 0) {
  287. uni.showToast({
  288. title: '没有图片可保存',
  289. icon: 'none'
  290. })
  291. return
  292. }
  293. uni.showModal({
  294. title: '保存图片',
  295. content: `是否将 ${allUrls.length} 张图片保存到本地相册?`,
  296. confirmText: '保存',
  297. success: (res) => {
  298. if (res.confirm) {
  299. imageUpload.saveImagesToLocal(allUrls)
  300. }
  301. }
  302. })
  303. },
  304. /**
  305. * 电话点击
  306. */
  307. handlePhoneClick() {
  308. if (!this.orderDetail.phone) {
  309. uni.showToast({
  310. title: '该订单暂时没有电话号码',
  311. icon: 'none'
  312. })
  313. return
  314. }
  315. uni.makePhoneCall({
  316. phoneNumber: this.orderDetail.phone,
  317. success: () => {
  318. this.$store.commit('call/SET_FORM', {
  319. clueId: this.orderDetail.clueId,
  320. type: '3',
  321. callee: this.orderDetail.phone
  322. })
  323. }
  324. })
  325. },
  326. /**
  327. * 微信点击
  328. */
  329. handleWechatClick() {
  330. if (!this.orderDetail.wechat) {
  331. uni.showToast({
  332. title: '该订单暂时没有微信号',
  333. icon: 'none'
  334. })
  335. return
  336. }
  337. uni.setClipboardData({
  338. data: this.orderDetail.wechat,
  339. success: () => {
  340. uni.showToast({
  341. title: '微信号已复制',
  342. icon: 'none'
  343. })
  344. }
  345. })
  346. },
  347. /**
  348. * 下一步
  349. */
  350. handleNext() {
  351. this.$emit('next', {
  352. nowPage: 'formOne',
  353. form: {}
  354. })
  355. }
  356. }
  357. }
  358. </script>
  359. <style scoped lang="scss">
  360. @import '../styles/common.scss';
  361. .page-one-container {
  362. @extend .page-container;
  363. padding-bottom: 100rpx;
  364. }
  365. .page-header {
  366. display: flex;
  367. justify-content: space-between;
  368. align-items: center;
  369. margin-bottom: 20rpx;
  370. }
  371. .page-title {
  372. @include font-styles($size: title, $weight: bold, $color: primary);
  373. }
  374. .save-all-btn {
  375. border-radius: 20rpx;
  376. border-color: #007AFF;
  377. color: #007AFF;
  378. }
  379. .card-wrap {
  380. @extend .card-wrap;
  381. margin-top: 20rpx;
  382. }
  383. .card-title {
  384. padding: 20rpx;
  385. border-bottom: 1rpx solid map-get($colors, border);
  386. display: flex;
  387. align-items: center;
  388. text {
  389. padding: 0 10rpx;
  390. cursor: pointer;
  391. &.active {
  392. color: map-get($colors, primary);
  393. font-weight: bold;
  394. }
  395. }
  396. .divider {
  397. margin: 0 10rpx;
  398. color: #ddd;
  399. }
  400. }
  401. .image-upload-container {
  402. padding: 20rpx;
  403. }
  404. .image-list {
  405. display: flex;
  406. flex-wrap: wrap;
  407. gap: 20rpx;
  408. }
  409. .image-item {
  410. position: relative;
  411. width: 200rpx;
  412. height: 200rpx;
  413. box-sizing: border-box;
  414. }
  415. .delete-btn {
  416. position: absolute;
  417. top: -10rpx;
  418. right: -10rpx;
  419. width: 40rpx;
  420. height: 40rpx;
  421. background-color: #ff4d4f;
  422. color: #fff;
  423. border-radius: 50%;
  424. display: flex;
  425. align-items: center;
  426. justify-content: center;
  427. font-size: 30rpx;
  428. font-weight: bold;
  429. z-index: 10;
  430. cursor: pointer;
  431. }
  432. .upload-btn {
  433. width: 200rpx;
  434. height: 200rpx;
  435. border: 8rpx dashed #ddd;
  436. border-radius: 30rpx;
  437. display: flex;
  438. align-items: center;
  439. justify-content: center;
  440. background-color: #f9f9f9;
  441. box-sizing: border-box;
  442. cursor: pointer;
  443. }
  444. .call-records-container {
  445. padding: 20rpx;
  446. }
  447. .info-card {
  448. @extend .card-wrap;
  449. padding: 20rpx;
  450. margin-top: 20rpx;
  451. box-sizing: border-box;
  452. width: 100%;
  453. max-width: 100%;
  454. }
  455. .info-card-title {
  456. @include font-styles($size: title, $weight: bold, $color: primary);
  457. margin-bottom: 25rpx;
  458. padding-bottom: 15rpx;
  459. border-bottom: 1rpx solid map-get($colors, border);
  460. }
  461. .info-row {
  462. margin-bottom: 20rpx;
  463. }
  464. .info-label {
  465. @include font-styles($size: tiny, $weight: regular, $color: tertiary);
  466. margin-bottom: 8rpx;
  467. }
  468. .info-value {
  469. @include font-styles($size: small, $weight: regular, $color: secondary);
  470. word-break: break-all;
  471. }
  472. .contact-card {
  473. display: flex;
  474. justify-content: space-between;
  475. margin: 20rpx 0;
  476. gap: 20rpx;
  477. }
  478. .contact-item {
  479. flex: 1;
  480. @extend .card-wrap;
  481. padding: 20rpx;
  482. display: flex;
  483. flex-direction: column;
  484. align-items: center;
  485. position: relative;
  486. cursor: pointer;
  487. }
  488. .contact-title {
  489. @include font-styles($size: tiny, $weight: regular, $color: tertiary);
  490. margin-top: 10rpx;
  491. }
  492. .red-dot {
  493. position: absolute;
  494. top: 15rpx;
  495. right: 15rpx;
  496. width: 25rpx;
  497. height: 25rpx;
  498. background-color: #ff4d4f;
  499. border-radius: 50%;
  500. box-shadow: 0 0 4rpx rgba(255, 77, 79, 0.3);
  501. }
  502. .space-block {
  503. height: 100rpx;
  504. }
  505. .next-btn {
  506. position: fixed;
  507. bottom: 10rpx;
  508. left: 2.5%;
  509. width: 95%;
  510. height: 80rpx;
  511. line-height: 80rpx;
  512. text-align: center;
  513. border-radius: 20rpx;
  514. }
  515. .detail-image-header {
  516. display: flex;
  517. justify-content: space-between;
  518. align-items: center;
  519. width: 100%;
  520. border-bottom: 1rpx solid map-get($colors, border);
  521. }
  522. .detail-image-title {
  523. @include font-styles($size: content, $weight: bold, $color: primary);
  524. }
  525. .copy-btn {
  526. border-radius: 20rpx;
  527. border: 1rpx solid #007AFF;
  528. background-color: transparent;
  529. color: #007AFF;
  530. padding: 0 24rpx;
  531. height: 64rpx;
  532. line-height: 64rpx;
  533. cursor: pointer;
  534. }
  535. </style>