index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. <template>
  2. <view class="oder_form_wrap">
  3. <u-navbar placeholder :autoBack="true" title="发单表单">
  4. </u-navbar>
  5. <view class="form_wrap">
  6. <u--form labelPosition="top" labelWidth="100" :model="form" :rules="rules" ref="form" class="form_wrap"
  7. :errorType="'toast'">
  8. <u-form-item label="品牌" prop="brand" class="brand_wrap">
  9. <ld-select :list="brandDict" label-key="dictLabel" value-key="dictValue" placeholder="请选择物品品牌"
  10. v-model="form.brand" :border="false" border></ld-select>
  11. <u-icon slot="right" name="arrow-right"></u-icon>
  12. </u-form-item>
  13. <u-row gutter="5" justify="space-between">
  14. <u-col span="6">
  15. <u-form-item label="型号" prop="model">
  16. <u--input v-model="form.model" placeholder="例如:CF小号" border="surround"></u--input>
  17. </u-form-item>
  18. </u-col>
  19. <u-col span="6">
  20. <u-form-item label="实价" prop="price">
  21. <u--input v-model="form.price" placeholder="0.00" border="surround"></u--input>
  22. </u-form-item>
  23. </u-col>
  24. </u-row>
  25. <u-row gutter="5" justify="space-between">
  26. <u-col span="6">
  27. <u-form-item label="客户电话" prop="phone">
  28. <u--input v-model="form.phone" placeholder="电话号" border="surround"></u--input>
  29. </u-form-item>
  30. </u-col>
  31. <u-col span="6">
  32. <u-form-item label="客户微信" prop="wechat">
  33. <u--input v-model="form.wechat" placeholder="微信号" border="surround"></u--input>
  34. </u-form-item>
  35. </u-col>
  36. </u-row>
  37. <view class="address_wrap">
  38. <view class="address_title">
  39. <u-icon name="map" color="#3b82f6" size="16"></u-icon>
  40. <view>地址信息</view>
  41. </view>
  42. <u-form-item label="详细地址" prop="address" class='form_card'>
  43. <pick-regions :defaultRegion="defaultRegion" @getRegion="handleGetRegion" clearable class="region-picker">
  44. <view>
  45. <template v-if="form.province">
  46. <u--input :value="form.province + '/' + form.city + '/' + form.area" placeholder="点击选择"
  47. readonly suffixIcon="arrow-right" suffixIconStyle="color : #c0c4cc"></u--input>
  48. </template>
  49. <template v-else>
  50. <u--input placeholder="点击选择" readonly suffixIcon="arrow-right"
  51. suffixIconStyle="color : #c0c4cc"></u--input>
  52. </template>
  53. </view>
  54. </pick-regions>
  55. </u-form-item>
  56. <u--textarea v-model="form.address" placeholder="粘贴地址自动识别(例如:浙江省杭州市...)" confirmType="done" @blur="handleAddressBlur"></u--textarea>
  57. </view>
  58. <u-form-item label="上门时间" prop="visitTime" class="visit_time_wrap">
  59. <date-time-picker v-model="form.visitTime" :value-format="'YYYY-MM-DD HH:mm:ss'" :type="'datetime'">
  60. </date-time-picker>
  61. </u-form-item>
  62. <u-form-item label="类型" prop="category" class="category_wrap">
  63. <view class="category_select_wrap">
  64. <ld-select :list="categoryDict" label-key="dictLabel" value-key="dictValue" placeholder="请选择类型"
  65. v-model="form.category" :border="false"></ld-select>
  66. <u-icon slot="right" name="arrow-right"></u-icon>
  67. </view>
  68. </u-form-item>
  69. <u-form-item label="价格范围" prop="priceRange">
  70. <u--input v-model="form.priceRange" placeholder="请输入价格范围" border="surround"></u--input>
  71. </u-form-item>
  72. <u-form-item label="战术选择" prop="tactic">
  73. <view class="tactic-buttons">
  74. <view v-for="item in tacticDict" :key="item.dictValue" class="tactic-button"
  75. :class="{ 'active': form.tactic === item.dictValue }" @click="form.tactic = item.dictValue">
  76. {{ item.dictLabel }}
  77. </view>
  78. </view>
  79. </u-form-item>
  80. <u-form-item label="备注信息" prop="remarks" class="remarks_wrap">
  81. <u--textarea v-model="form.remarks" placeholder="填写线索来源、客户特殊要求等..." count confirmType="done"></u--textarea>
  82. </u-form-item>
  83. </u--form>
  84. </view>
  85. <!-- 文件上传区域 -->
  86. <view class="upload_area" v-if="!sendFormId">
  87. <!-- 聊天记录附件 -->
  88. <order-file-upload title="聊天记录附件" orderFileType="1" fileType="image" :file-list="form.chatAttachmentList"
  89. @update:fileList="updateChatFiles" tip-text="上传聊天截图、录音等文件"></order-file-upload>
  90. <!-- 报价附件 -->
  91. <order-file-upload title="报价附件" orderFileType="2" fileType="image" :file-list="form.quoteAttachmentList"
  92. @update:fileList="updateQuoteFiles" tip-text="上传报价附件"></order-file-upload>
  93. <!-- 高清图附件 -->
  94. <order-file-upload title="高清图附件" orderFileType="3" fileType="image" :file-list="form.hdImageAttachmentList"
  95. @update:fileList="updateHdImageFiles" tip-text="上传物品高清图片"></order-file-upload>
  96. <!-- 其他附件 -->
  97. <order-file-upload title="其他附件" orderFileType="4" fileType="image" :file-list="form.otherAttachmentList"
  98. @update:fileList="updateOtherFiles" tip-text="上传其他相关文件"></order-file-upload>
  99. </view>
  100. <u-button @click="handleNavSaveClick" type="primary">提交订单</u-button>
  101. </view>
  102. </template>
  103. <script>
  104. import orderFileUpload from '@/components/order-file-upload/order-file-upload.vue'
  105. import dateTimePicker from "@/components/dateTimePicker/dateTimePicker.vue"
  106. import ldSelect from "@/components/ld-select/ld-select.vue"
  107. export default {
  108. components: {
  109. orderFileUpload,
  110. dateTimePicker,
  111. ldSelect
  112. },
  113. data() {
  114. return {
  115. defaultRegion: ['广东省', '广州市', '番禺区'],
  116. categoryDict: [],
  117. tacticDict: [],
  118. brandDict: [],
  119. sendFormId: undefined,
  120. form: {
  121. clueId: '',
  122. sendDate: '',
  123. website: '',
  124. item: '',
  125. phone: '',
  126. wechat: '',
  127. address: '',
  128. visitTime: '',
  129. remarks: '',
  130. category: '',
  131. brand: '',
  132. model: '',
  133. price: '',
  134. priceRange: '',
  135. tactic: '',
  136. // 地区信息
  137. province: '',
  138. city: '',
  139. area: '',
  140. // 附件列表
  141. chatAttachmentList: [], // 聊天记录附件
  142. quoteAttachmentList: [], // 报价附件
  143. hdImageAttachmentList: [], // 高清图附件
  144. otherAttachmentList: [] // 其他附件
  145. },
  146. rules: {
  147. sendDate: {
  148. type: 'string',
  149. required: true,
  150. message: '请输入发单日期',
  151. trigger: ['blur', 'change']
  152. },
  153. model: {
  154. type: 'string',
  155. required: true,
  156. message: '请输入型号',
  157. trigger: ['blur', 'change']
  158. },
  159. price: {
  160. type: 'string',
  161. required: true,
  162. message: '请输入实价',
  163. trigger: ['blur', 'change']
  164. },
  165. website: {
  166. type: 'url',
  167. message: '请输入正确的网址格式',
  168. trigger: ['blur', 'change']
  169. },
  170. item: {
  171. type: 'string',
  172. required: true,
  173. message: '请输入物品描述',
  174. trigger: ['blur', 'change']
  175. },
  176. wechat: {
  177. type: 'string',
  178. required: true,
  179. message: '请输入客户微信',
  180. trigger: ['blur', 'change']
  181. },
  182. phone: {
  183. type: 'string',
  184. required: true,
  185. message: '请输入联系电话',
  186. trigger: ['blur', 'change']
  187. },
  188. address: {
  189. type: 'string',
  190. required: true,
  191. message: '请输入详细地址',
  192. trigger: ['blur', 'change']
  193. },
  194. visitTime: {
  195. type: 'string',
  196. required: true,
  197. message: '请输入上门时间',
  198. trigger: ['blur', 'change']
  199. },
  200. category: {
  201. type: 'string',
  202. required: true,
  203. message: '请选择类型',
  204. trigger: ['blur', 'change']
  205. },
  206. brand: {
  207. type: 'string',
  208. required: true,
  209. message: '请选择物品品牌',
  210. trigger: ['blur', 'change']
  211. },
  212. tactic: {
  213. type: 'string',
  214. required: true,
  215. message: '请选择战术',
  216. trigger: ['blur', 'change']
  217. }
  218. }
  219. }
  220. },
  221. methods: {
  222. // 加载表单数据
  223. async loadFormData(sendFormId) {
  224. try {
  225. const res = await uni.$u.api.getClueSendFormVoByOrderId({
  226. id: sendFormId
  227. });
  228. if (res.code === 200) {
  229. this.form = res.data;
  230. }
  231. } catch (error) {
  232. console.error('加载表单数据失败:', error);
  233. uni.$u.toast('加载表单数据失败');
  234. }
  235. },
  236. // 获取地区选择
  237. handleGetRegion(region) {
  238. const [provinceData, cityData, areaData] = region;
  239. this.form.province = provinceData.name;
  240. this.form.city = cityData.name;
  241. this.form.area = areaData.name;
  242. // 构建完整的地区信息
  243. const regionText = this.form.province + this.form.city + this.form.area;
  244. // 检查当前地址内容是否为纯地区信息(没有具体街道等)
  245. const isPureRegion = !this.form.address ||
  246. this.form.address.trim() === '' ||
  247. this.form.address.trim().includes(this.form.province) ||
  248. this.form.address.trim().includes(this.form.city) ||
  249. this.form.address.trim().includes(this.form.area);
  250. // 如果是纯地区信息或者是空地址,则更新为选择的地区
  251. if (isPureRegion) {
  252. this.form.address = regionText;
  253. } else {
  254. // 如果有具体地址信息,保留原有地址,但更新地区部分
  255. // 这里可以保持原有地址不变,让用户自己选择是否更新
  256. // 或者可以选择性地更新地区信息
  257. }
  258. },
  259. // 地址失焦时自动解析地址信息
  260. handleAddressBlur() {
  261. },
  262. // 更新各类附件
  263. updateChatFiles(fileList) {
  264. this.form.chatAttachmentList = fileList;
  265. },
  266. updateQuoteFiles(fileList) {
  267. this.form.quoteAttachmentList = fileList;
  268. },
  269. updateHdImageFiles(fileList) {
  270. this.form.hdImageAttachmentList = fileList;
  271. },
  272. updateOtherFiles(fileList) {
  273. this.form.otherAttachmentList = fileList;
  274. },
  275. // 文件变化处理
  276. handleFileChange(data) {
  277. console.log('文件上传变化:', data);
  278. },
  279. // 获取字典数据
  280. async getDicts() {
  281. try {
  282. const [categoryRes, tacticRes, brandRes] = await Promise.all([
  283. this.$getDicts('crm_form_category'),
  284. this.$getDicts('crm_form_tactic'),
  285. this.$getDicts('crm_form_brand')
  286. ]);
  287. this.categoryDict = categoryRes;
  288. this.tacticDict = tacticRes;
  289. this.brandDict = brandRes;
  290. } catch (error) {
  291. console.error('获取字典数据失败:', error);
  292. }
  293. },
  294. // 处理保存
  295. async handleNavSaveClick() {
  296. try {
  297. // 表单验证
  298. await this.$refs.form.validate();
  299. if (this.form.id) {
  300. const updatedForm = {
  301. clueId: this.form.clueId,
  302. model: this.form.model,
  303. price: this.form.price,
  304. wechat: this.form.wechat,
  305. id: this.form.id,
  306. item: this.form.item,
  307. phone: this.form.phone,
  308. authenticateUserId: this.form.authenticateUserId,
  309. category: this.form.category,
  310. brand: this.form.brand,
  311. idCard: this.form.idCard,
  312. customName: this.form.customName,
  313. bankCardNumber: this.form.bankCardNumber,
  314. bankName: this.form.bankName,
  315. paymentMethod: this.form.paymentMethod,
  316. visitTime: this.form.visitTime,
  317. remarks: this.form.remarks,
  318. priceRange: this.form.priceRange,
  319. tactic: this.form.tactic
  320. }
  321. await uni.$u.api.updateClueOrderForm(updatedForm);
  322. uni.$emit('updateSendFormSuccess');
  323. uni.$u.toast('发单记录更新成功');
  324. } else {
  325. // 合并所有附件
  326. const allAttachments = [
  327. ...this.form.chatAttachmentList,
  328. ...this.form.quoteAttachmentList,
  329. ...this.form.hdImageAttachmentList,
  330. ...this.form.otherAttachmentList
  331. ];
  332. // 准备提交数据
  333. const submitData = {
  334. ...this.form,
  335. uploadList: allAttachments
  336. };
  337. await uni.$u.api.saveClueOrderForm(submitData);
  338. uni.$u.toast('发单记录添加成功');
  339. }
  340. // 延迟返回
  341. setTimeout(() => {
  342. uni.navigateBack();
  343. }, 1500);
  344. } catch (error) {
  345. console.error('保存失败:', error);
  346. if (error.message) {
  347. uni.$u.toast(error.message);
  348. }
  349. }
  350. }
  351. },
  352. onLoad(option) {
  353. console.log('option', option);
  354. const clueId = option.clueId;
  355. this.sendFormId = option.sendFormId;
  356. this.form.clueId = clueId;
  357. this.getDicts();
  358. if (option.sendFormId) {
  359. this.loadFormData(option.sendFormId);
  360. }
  361. }
  362. }
  363. </script>
  364. <style lang="scss" scoped>
  365. .oder_form_wrap {
  366. background-color: #f8f9fa;
  367. min-height: 100vh;
  368. padding-bottom: 10rpx;
  369. padding-top: 10px;
  370. padding-left: 20rpx;
  371. padding-right: 20rpx;
  372. }
  373. .upload_area {
  374. padding: 0 10rpx;
  375. }
  376. .bottom_btn {
  377. position: fixed;
  378. bottom: 0;
  379. left: 0;
  380. right: 0;
  381. padding: 20rpx;
  382. background: #fff;
  383. box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
  384. }
  385. ::v-deep .u-form-item {
  386. /* 移除底部边框 */
  387. border-bottom: none;
  388. /* 添加卡片样式 */
  389. // background-color: #fff;
  390. border-radius: 16rpx;
  391. /* 添加内边距 */
  392. padding: 10rpx;
  393. /* 确保内容不会溢出 */
  394. overflow: hidden;
  395. .u-input--square{
  396. border-radius: 20rpx;
  397. }
  398. .u-input--square:focus-within,
  399. .u-input--square.u-input--focus {
  400. border: 2rpx solid #2563eb !important;
  401. background-color: #fff !important;
  402. }
  403. }
  404. .brand_wrap{
  405. ::v-deep .u-form-item__body__right__content{
  406. border:1px solid #dadbde;
  407. padding:10rpx;
  408. border-radius: 20rpx;
  409. .inputWrap{
  410. border:none;
  411. }
  412. }
  413. }
  414. .address_wrap {
  415. border-radius: 20rpx;
  416. padding:24rpx;
  417. display: flex;
  418. flex-direction: column;
  419. background-color: #fff;
  420. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  421. margin: 0 10rpx;
  422. .address_title {
  423. display: flex;
  424. align-items: center;
  425. background-color: #fff;
  426. padding-bottom: 12rpx;
  427. gap:16rpx;
  428. font-size:26rpx;
  429. color: #6b7280;
  430. font-weight: 600;
  431. }
  432. ::v-deep .u-form-item{
  433. margin-bottom:0 !important;
  434. padding:0;
  435. .u-input--square{
  436. background-color: #f9fafb;
  437. }
  438. }
  439. ::v-deep .u-textarea--radius{
  440. border-radius: 20rpx;
  441. }
  442. .region-picker {
  443. width: 100%;
  444. }
  445. ::v-deep .u-textarea{
  446. background-color: #f9fafb;
  447. }
  448. }
  449. .visit_time_wrap{
  450. ::v-deep .uni-date-x{
  451. border-radius: 10px;
  452. padding: 8rpx 10rpx;
  453. border:1px solid #dadbde;
  454. background-color: #f8f9fa;
  455. }
  456. }
  457. .category_wrap{
  458. .category_select_wrap{
  459. border-radius: 20rpx;
  460. border:1px solid #dadbde;
  461. padding:6rpx;
  462. width:100%;
  463. ::v-deep .ldSelectInput{
  464. font-size: 28rpx !important;
  465. }
  466. }
  467. .category_select_wrap:focus-within,
  468. .category_select_wrap:focus {
  469. border: 2rpx solid #2563eb !important;
  470. background-color: #fff !important;
  471. }
  472. }
  473. .remarks_wrap{
  474. .u-textarea{
  475. background-color: #fff;
  476. }
  477. ::v-deep .u-textarea--radius{
  478. border-radius: 20rpx;
  479. }
  480. }
  481. ::v-deep .u-form-item__body {
  482. padding: 10rpx 0rpx;
  483. }
  484. ::v-deep .u-form-item__body__left__content__label {
  485. color: rgb(107 114 128 / 1) !important;
  486. font-size: 28rpx;
  487. font-weight: 700;
  488. }
  489. .u-button {
  490. border-radius: 40rpx;
  491. background-color:#2563eb;
  492. border:none;
  493. color:#fff;
  494. font-weight: 700;
  495. font-size: 40rpx;
  496. height: 100rpx;
  497. }
  498. .tactic-buttons {
  499. display: flex;
  500. justify-content: space-between;
  501. margin-top: 10rpx;
  502. width: 100%;
  503. }
  504. .tactic-button {
  505. position: relative;
  506. border-width: 3rpx;
  507. border-style: solid;
  508. border-color: #e5e7eb;
  509. width: 30%;
  510. height: 80rpx;
  511. line-height: 80rpx;
  512. text-align: center;
  513. border-radius: 30rpx;
  514. background-color: #f9fafb;
  515. color: rgb(107 114 128 / 1);
  516. font-size: 28rpx;
  517. transition: all 0.3s ease;
  518. font-weight: 700;
  519. }
  520. .tactic-button.active {
  521. border-color: #3b82f6;
  522. color: #3b82f6;
  523. background-color: #eff6ff;
  524. }
  525. .tactic-button::after {
  526. content: '';
  527. position: absolute;
  528. border-width: 5rpx;
  529. border-style: solid;
  530. border-color: #ffffff;
  531. top: 4%;
  532. right: -12%;
  533. transform: translate(-50%, -50%);
  534. width: 25rpx;
  535. height: 25rpx;
  536. border-radius: 50%;
  537. background-color: #3b82f6;
  538. opacity: 0;
  539. transition: all 0.3s ease;
  540. }
  541. .tactic-button.active::after {
  542. opacity: 1;
  543. }
  544. .u-textarea{
  545. background-color: #f5f5f5;
  546. }
  547. </style>